返回

JavaScript 揭秘:剖析构造函数与六种继承方案

前端

在 JavaScript 的世界里,构造函数是一个神奇的存在,它拥有将一个普通函数变为对象的魔法力量。然而,构造函数的本质是什么?继承又该如何实现?带着这些疑问,让我们踏上探索之旅,一步步揭开 JavaScript 继承的神秘面纱。

一、初识构造函数

构造函数其实就是普通的函数。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。比如,以下代码就定义了一个构造函数 Person:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

使用 new 操作符调用 Person 构造函数可以创建一个新的对象,如下所示:

const person1 = new Person('John', 30);
const person2 = new Person('Mary', 25);

person1 和 person2 都是 Person 构造函数创建的对象,它们都拥有 name 和 age 属性。

二、六种继承方案

在 JavaScript 中,继承是指一个对象从另一个对象那里获取属性和方法的能力。实现继承有六种常见方案:

  1. 原型链继承

原型链继承是最简单也是最常用的继承方式。原型链继承的原理是:每个对象都有一个内部属性 proto,指向它的原型对象。原型对象也是一个对象,它也有自己的 proto 属性,如此递归下去,直到遇到 null 为止。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

const person1 = new Person('John', 30);
person1.greet(); // Hello, my name is John and I am 30 years old.

在上面的代码中,Person.prototype 是 Person 构造函数的原型对象。person1 是 Person 构造函数创建的对象,它的 proto 属性指向 Person.prototype。因此,person1 可以访问 Person.prototype 上的 greet() 方法。

  1. 构造函数继承

构造函数继承的原理是:子构造函数调用父构造函数,然后子构造函数的 prototype 属性指向父构造函数的实例。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);

const student1 = new Student('John', 30, 'A');
student1.greet(); // Hello, my name is John and I am 30 years old.
console.log(student1.grade); // A

在上面的代码中,Student 构造函数调用 Person 构造函数,然后 Student.prototype 属性指向 Person.prototype 的一个新实例。因此,Student 构造函数创建的对象既拥有 Person 构造函数的属性和方法,也拥有 Student 构造函数的属性和方法。

  1. 组合继承

组合继承是原型链继承和构造函数继承的结合。组合继承的原理是:子构造函数先调用父构造函数,然后子构造函数的 prototype 属性指向父构造函数的原型对象。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(name, age, grade) {
  Person.call(this, name, age);
  Student.prototype = Object.create(Person.prototype);
  Student.prototype.constructor = Student;
}

const student1 = new Student('John', 30, 'A');
student1.greet(); // Hello, my name is John and I am 30 years old.
console.log(student1.grade); // A

在上面的代码中,Student 构造函数先调用 Person 构造函数,然后 Student.prototype 属性指向 Person.prototype 的一个新实例。因此,Student 构造函数创建的对象既拥有 Person 构造函数的属性和方法,也拥有 Student 构造函数的属性和方法。

  1. 原型式继承

原型式继承的原理是:创建一个对象,然后将这个对象作为子对象的原型对象。

const person = {
  name: 'John',
  age: 30,
  greet: function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
};

const student = Object.create(person);
student.grade = 'A';

student.greet(); // Hello, my name is John and I am 30 years old.
console.log(student.grade); // A

在上面的代码中,person 对象是 student 对象的原型对象。因此,student 对象拥有 person 对象的属性和方法,也拥有自己的属性和方法。

  1. 寄生式继承

寄生式继承的原理是:创建一个函数,该函数返回一个新对象,该对象继承了另一个对象的属性和方法。

function createStudent(name, age, grade) {
  const person = {
    name: name,
    age: age
  };

  const student = Object.create(person);
  student.grade = grade;

  return student;
}

const student1 = createStudent('John', 30, 'A');
student1.greet(); // Hello, my name is John and I am 30 years old.
console.log(student1.grade); // A

在上面的代码中,createStudent() 函数返回一个新对象,该对象继承了 person 对象的属性和方法,也拥有自己的属性和方法。

  1. 函数式继承

函数式继承的原理是:创建一个函数,该函数返回另一个函数,该函数返回一个新对象。

function createStudent(name, age, grade) {
  return function() {
    const person = {
      name: name,
      age: age
    };

    const student = Object.create(person);
    student.grade = grade;

    return student;
  };
}

const Student = createStudent('John', 30, 'A');
const student1 = Student();
student1.greet(); // Hello, my name is John and I am 30 years old.
console.log(student1.grade); // A

在上面的代码中,createStudent() 函数返回了一个函数,该函数返回了一个新对象。Student 是 createStudent() 函数返回的函数,student1 是 Student() 函数返回的对象。student1 对象继承了 person 对象的属性和方法,也拥有自己的属性和方法。

三、结语

继承是 JavaScript 中一项非常重要的概念,它允许我们创建新的对象,这些对象具有其他对象的属性和方法。本文介绍了六种常见的继承方案,每种方案都有自己的优缺点。在实际项目中,我们可以根据具体情况选择合适的继承方案。