前言
JavaScript 的继承不像类类型语言(如 Java)那样直观。它是通过原型链机制实现的。理解各种继承方式的演进过程,不仅能帮你应对面试,更能让你深入理解 JS 的底层设计。
一、 原型链继承(Prototype Chain Inheritance)
这是最基本的继承方式。
- 实现:
Child.prototype = new Father();
优点
- 父类构造函数内部的属性、方法以及父类原型上的方法,子类都能访问。
缺点(致命伤)
- 引用属性共享:父类中如果有引用类型属性(如
Array),一个子类修改了它,所有子类都会跟着变。 - 无法传参:创建子类实例时,无法向父类构造函数传递参数。
二、 借用构造函数继承(Constructor Stealing)
为了解决传参和引用共享问题,出现了这种方式。
- 实现:在子类构造函数中使用
Father.call(this, ...args)。
优点
- 独立性:每个子类实例都有自己的属性副本,互不干扰。
- 可传参:可以向父类构造函数传递参数。
缺点
- 方法无法复用:只能继承父类构造函数内的属性,无法继承父类原型(prototype)上的方法。
三、 组合继承(Combination Inheritance)⭐
这是最常用的继承方式,它结合了前两者的优点。
- 核心思想:先将子类构造函数的protype指向为父类构造函数的一个实例,大致步骤和原型链继承一样,只是多了在子类构造函数里面调用父类构造函数的过程,可总结为用构造函数继承属性,用原型链继承方法。
- 深度分析:组合继承子类的实例属性定义在构造函数中,而方法则定义在构造函数的新原型中,同时将新原型的constructor指向构造函数(为什么需要将construct指向构造函数,因为如果不指向构造函数的话,通过new类构造函数 构造出来这个实例对象是没有constructor属性的,我们要找子类constructor时,会通过原型链直接找到父类的constructor属性,而父类的constructor属性是指向父类构造函数的)
- 原理如下图所示:
代码实战
function Father(name) {
this.name = name;
this.nums = [1, 2, 3, 4];
}
Father.prototype.sayName = function() {
console.log(this.name);
}
function Child(name, age) {
// 1. 继承属性(构造函数继承)
Father.call(this, name);
this.age = age;
}
// 2. 继承方法(原型链继承)
Child.prototype = new Father();
// 3. 修复 constructor 指向
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
}
const child1 = new Child('ouyang', 16);
child1.nums.push(5);
console.log(child1.nums); // [1, 2, 3, 4, 5]
const child2 = new Child('hudie', 18);
console.log(child2.nums); // [1, 2, 3, 4] (互不影响!)
五、 面试模拟题
Q1:为什么 JavaScript 的继承要设计原型链?
参考回答: 原型链实现了属性和方法的共享。如果不使用原型链,每个实例都要单独存储一份方法,会造成极大的内存浪费。原型链允许所有实例共享同一套方法(存储在 prototype 中),同时通过构造函数保证每个实例有独立的属性。
Q2:new Father() 和 Object.create(Father.prototype) 在继承时有什么区别?
参考回答:
new Father()会执行Father构造函数内部的代码,并将属性挂载到原型上。Object.create(Father.prototype)创建一个新对象,其原型指向Father.prototype,但不会执行 Father 的构造函数。这避免了子类原型上出现不必要的父类实例属性,性能更优。
Q3:ES6 的 extends 继承和组合继承有什么区别?
参考回答:
- 底层机制:ES5 继承是先创建子类实例
this,再将父类属性添加到this上;ES6 继承是先创建父类实例this(通过super()),再用子类构造函数修改this。 - 静态方法继承:ES6 的
extends可以继承父类的静态方法(static),而 ES5 的组合继承需要手动处理才能实现。