返回

剖析JS继承:见证幕后的奇妙运作

前端

JavaScript 继承揭秘:深入浅出剖析面向对象编程的基石

构造函数:新对象的诞生

每当我们使用 new 创建对象时,它会触发构造函数,这是一个专门用于初始化新对象属性和方法的函数。构造函数与类同名,当我们调用 new 时,它实际上在执行构造函数,并将创建的新对象返回给我们。

原型链:追溯祖先的足迹

每个对象都拥有一个称为原型链的属性,它指向对象的原型对象,而原型对象又指向它的原型对象,如此循环往复,最终会到达 Object.prototype,这是所有 JavaScript 对象的终极祖先。原型链的妙用在于,当我们访问对象属性或方法时,如果对象本身不具备,它会沿着原型链向上查找,直到找到具有该属性或方法的对象。

super 连接子父的纽带

super 关键字是 ES6 中引入的,允许我们在子类中访问父类的方法和属性。当我们在子类构造函数中使用 super 时,它实际上是在调用父类构造函数,并将子类的 this 指针传递给父类构造函数,从而实现子类对父类的继承。

new 的魔法:开启新生命

new 关键字是 JavaScript 中一个神奇的运算符,它可以创建新对象并自动执行构造函数。当我们使用 new 创建对象时,它实际上在执行以下步骤:

  1. 创建新对象。
  2. 将新对象的原型设置为构造函数的 prototype 属性。
  3. 执行构造函数,并将 this 指针指向新对象。
  4. 返回新对象。

通过这些步骤,我们就创建了一个新对象,并将其与父类关联起来。

继承的本质:代码的重用与扩展

继承的本质在于代码的重用与扩展。通过继承,我们可以基于已有的类创建新类,从而重用父类中已有的代码。同时,我们还可以扩展父类,为子类添加新的属性和方法,从而实现代码的扩展。

实例:揭秘原型链的作用

为了更好地理解原型链的作用,让我们举一个简单的例子:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

class Student extends Person {
  constructor(name, studentID) {
    super(name);
    this.studentID = studentID;
  }

  study() {
    console.log(`I'm studying hard!`);
  }
}

const student = new Student('John', '12345');

student.greet(); // Hello, my name is John
student.study(); // I'm studying hard!

在这个例子中,Student 类继承自 Person 类,因此 Student 类的原型对象指向 Person 类的原型对象。当我们调用 student.greet() 方法时,JavaScript 引擎会首先在 Student 类的原型对象中查找 greet 方法,但没有找到,因此它会沿着原型链向上查找,在 Person 类的原型对象中找到了 greet 方法,并将其执行。

同样的,当我们调用 student.study() 方法时,JavaScript 引擎也会首先在 Student 类的原型对象中查找 study 方法,但没有找到,因此它会沿着原型链向上查找,并最终在 Student 类的构造函数中找到了 study 方法,并将其执行。

通过这个例子,我们可以看到原型链是如何工作的,以及它在继承中发挥的作用。

常见问题解答

  1. 继承是否会共享内存?
    否,继承不会共享内存。子类拥有自己独立的属性和方法,即使它们继承自父类。

  2. 如何避免继承中的代码重复?
    通过使用抽象类和接口,我们可以定义公共接口,让子类实现,从而避免代码重复。

  3. 多重继承在 JavaScript 中是否可能?
    严格意义上讲,JavaScript 不支持多重继承。但我们可以通过使用 mixins 或其他模式来模拟多重继承。

  4. 继承有什么局限性?
    继承的一个主要局限性是它可能会导致“易碎的基类”问题,即修改父类可能会破坏子类。

  5. 我应该使用继承还是组合?
    继承适合于当子类与父类有“is-a”关系时。组合适合于当子类与父类有“has-a”关系时。