出处:ObjectX 博客
原作者:ObjectX
通过 call 借用父类构造函数
原理
call
只会执行 Parent1
构造函数,并将 this
绑定到 Child1
实例上。因此,Parent1
构造函数内部的属性会被拷贝到 Child1
的实例中,但 Parent1.prototype
上的方法不会被继承
代码示例
function Parent1() {
this.name = 'parent1';
}
function Child1() {
Parent1.call(this);
this.type = 'child1';
}
console.log(new Child1());
解释
call
只会执行 Parent1
构造函数,并将 this
绑定到 Child1
实例上。因此,Parent1
构造函数内部的属性会被拷贝到 Child1
的实例中,但 Parent1.prototype
上的方法不会被继承
缺点
由于 call
只是在 Child1
的构造函数内执行 Parent1
,所以 Parent1.prototype
上的方法不会被继承
原型链继承
原理
通过 Child2.prototype = new Parent2();
让 Child2
继承 Parent2
,这样 Child2
的实例可以访问 Parent2.prototype
上的方法
代码示例
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3];
}
Parent2.prototype.greet = function() {
console.log('Hello from prototype');
};
function Child2() {
this.type = 'child2';
}
Child2.prototype = new Parent2();
const c1 = new Child2();
const c2 = new Child2();
c1.play.push(4);
console.log(c1.play); // [1, 2, 3, 4]
console.log(c2.play); // [1, 2, 3, 4] (预期每个实例应有独立的 play 数组)
解释
由于 Child2.prototype = new Parent2();
,Child2
的实例可以访问 Parent2.prototype
上的方法,同时 Child2
的所有实例会共享 Parent2
构造函数内定义的引用类型属性。
缺点
Child2.prototype = new Parent2();
会导致Parent2
的所有实例共享play
数组- 修改一个实例的
play
也会影响其他实例
组合继承
原理
- 通过
Parent3.call(this)
继承实例属性(避免父类构造函数的引用类型属性共享) - 通过
Child3.prototype = new Parent3()
继承原型属性和方法
代码示例
function Parent3() {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
const s3 = new Child3();
const s4 = new Child3();
s3.play.push(4);
console.log(s3.play); // [1, 2, 3, 4]
console.log(s4.play); // [1, 2, 3]
解释
通过 Parent3.call(this);
继承实例属性,Child3.prototype = new Parent3();
继承方法。这样每个实例都有自己的 play
数组,不会互相影响
缺点
Child3.prototype = new Parent3();
这一行会导致Parent3
的构造函数被执行两次
组合继承的优化 1
原理
直接让 Child4.prototype
指向 Parent4.prototype
,这样减少了一次 Parent4
的构造函数调用
代码示例
function Parent4() {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4() {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
const s3 = new Child4();
const s4 = new Child4();
console.log(s3);
解释
这样做的好处是减少了一次 Parent4
的构造函数调用,但 Child4
的 constructor
会错误地指向 Parent4
缺点
Child4.prototype.constructor
指向Parent4
,而不是Child4
- 还有
child
原型对象是同一个,还是有引用问题 - 在 JavaScript 中,继承机制是通过设置子类(子构造函数)的原型对象来实现的。当你执行
Child4.prototype = Parent4.prototype
; 时,Child4.prototype
被赋值为Parent4.prototype
,这意味着Child4
的原型对象指向Parent4
的原型 - 然而,在这种情况下,
Child4.prototype.constructor
的值也会被改变。因为原型对象Parent4.prototype
中有一个constructor
属性,指向的是Parent4
构造函数。由于Child4.prototype
被指向Parent4.prototype
,所以Child4.prototype.constructor
会继承Parent4.prototype.constructor
,而不再是Child4
构造函数
最推荐的方式:优化 2
原理
使用 Object.create
创建 Child5.prototype
,避免 Parent5
的构造函数被执行两次,并修正 constructor
代码示例
function Parent5() {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
const child = new Child5();
console.log(child);
console.log(child.constructor); // Child5
解释
Object.create(Parent5.prototype)
创建一个新的对象,并让其__proto__
指向Parent5.prototype
。这里就实现了原型链Child5.prototype.constructor = Child5;
修正constructor
指向
优势
- 解决了组合继承的两次调用父函数的问题
- 继承了父类的原型方法
constructor
指向正确
结论
- call 继承:只能继承父类构造函数的属性,不能继承原型方法
- 原型链继承:原型上的方法能被继承,但会共享父类的引用类型属性
- 组合继承:通过
call
继承实例属性,通过new Parent()
继承原型方法,但会导致父类构造函数执行两次 - 优化 1(原型赋值):直接将
Parent.prototype
赋值给Child.prototype
,但constructor
会指向错误 - 优化 2(推荐):使用
Object.create
继承原型,避免两次调用构造函数,同时修正constructor
,是 ES5 继承的最佳方案