名词解释
prototype: 函数独有,叫原型或原型对象,用于给实例共享方法。__proto__ / [[Prototype]]:对象自身的隐式原型指针,也有叫内部槽,通常指向自身构造函数的原型(可以修改指向某个对象 或null), 是实现继承的重要途径,多个构成原型链,至于单个,一直以来没有统一叫法。
1. 原型链继承
特点:子类的原型指向了父类的实例
- 继承了父类原型上的公有属性
- 创建子类实例时,无法给父类构造函数按实例传参
- 子类原型上会挂着一份父类实例属性;若含引用类型,实例间会共享并相互影响
// 代码示例
function Parent(name, age) {
this.name = name;
this.age = age;
this.arr = [1, 2, 3];
}
Parent.prototype.say = function () {
console.log(`${this.name} 的年龄是 ${this.age}`);
};
function Child() {}
Child.prototype = new Parent("niko", 18);
let c1 = new Child("zoyi", 1000);
let c2 = new Child();
c2.arr.push(4);
c2.gender = "girl";
console.log(c1.arr); // [ 1, 2, 3, 4 ] 共享引用类型的改变会影响全局
console.log(c1.gender); // undefined 对于自身不存在的基本类型值,创建新属性并赋值,不修改原型链上的属性(除非原型上有 setter)
console.log(c2.gender); // girl
c1.say(); // niko 的年龄是 18
console.log(c1.constructor); // [Function: Parent]
- 由于
Child.prototype是一个Parent实例,c1调用say时,先在自身找name/age,找不到再沿原型链到Child.prototype上读取到它们。Child.prototype = new Parent()后,子类原型对象被替换,默认constructor不再指向Child,沿链查找通常会得到Parent。
2. 构造函数继承
特点:在子类构造函数中,将父类当普通函数执行,通过 call 改变 this 指向子类实例
仅继承了父类的实例属性,没有继承父类原型上的公有属性(原型链没有发生变化)
// 代码示例
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.say = function () {
console.log(`${this.name} 的年龄是 ${this.age}`);
};
function Child(name, age, gender) {
Parent.call(this, name, age);
this.gender = gender;
}
let c1 = new Child("zoyi", 1000, "girl");
console.log(c1); // { name: 'zoyi', age: 1000, gender: 'girl' }
c1.say(); // c1.say is not a function
3. 组合继承
特点:原型链继承 和 构造函数继承 的结合
优点:同时继承父类实例属性与公有属性
缺点:
- 父类构造函数被重复执行(一次给子类原型,一次给子类实例),效率和语义都不够理想。
- 子类构造函数执行时,会再次在实例上创建同名父类实例属性,导致原型上那份属性长期被遮蔽并造成额外开销。
4. 寄生组合继承
- 同时继承父类实例属性与公有属性
- 只调用了一次父类构造函数
- 使用
Object.create()作为桥梁,连接Child.prototype与Parent.prototype。- 注意点: 若在重设原型前已给
Child.prototype添加成员,会被覆盖;通常需要手动修复constructor指向。
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.say = function () {
console.log(`${this.name} 的年龄是 ${this.age}`);
};
function Child(name, age) {
Parent.call(this, name, age);
}
// Child.prototype ——> newObj ——> Parent.prototype
Child.prototype = Object.create(Parent.prototype);
//修复子类原型对象(prototype)被覆盖的 constructor 指向
Child.prototype.constructor = Child;
let c1 = new Child("zoyi", 1000);
c1.say(); // zoyi 的年龄是 1000
console.log(c1.constructor); // [Function: Child]
ES6 类继承的关键字 extends
语义上可理解为“寄生组合继承”的标准化写法,关键点如下:
- 构造函数中通过
super()调用父类构造逻辑,完成当前实例初始化后才能使用this。它的行为可类比
Parent.call(this, ...),但并不等同于简单函数调用。 super 在实例方法里指向Parent.prototype,在静态方法里指向 Parent 本身 - 建立
Child.prototype与Parent.prototype之间的原型链。 - 建立
Child与Parent的静态继承链(Child.__proto__ === Parent),使子类可直接访问父类静态成员。这通常可理解为
Object.setPrototypeOf(Child, Parent)的效果;通过instance.constructor间接访问静态方法只是访问路径,不是实例继承。