blog
blog copied to clipboard
深入继承:一步步捋清五种继承方式
总览
一、借助构造函数
function Parent1() {
this.name = 'parent1'
}
function Child1() {
// 将父类的执行上下文指向子类,父类执行时的实例属性都会指向子类
Parent1.call(this);// apply
this.type = 'child1'
}
缺点
子类没有继承父类的原型方法 只继承了父类构造函数中的属性和方法
Parent1.prototype.method = (arg) => console.log(arg);
console.log(new Child1().method); // undefined
二、借助原型链
function Parent2() {
this.name = 'parent2';
this.arr = [1, 2, 3];
this.method = (arg) => console.log(arg)
}
function Child2() {
this.type = 'child2'
}
Child2.prototype = new Parent2();
原型图如下
缺点
引用类型的属性被所有实例共享,实例之间会互相影响
let c21 = new Child2();
let c22 = new Child2();
c21.arr.push(4);
console.log(c21.arr, c22.arr);
// 注意,下面是直接给实例添加method属性
// 只是修改了method指针,没有修改原型链上的method方法
// 只有修改引用对象才是真正的修改
c21.method = 'c21';
console.log(Parent2);
console.log(c21, c22);
三、组合(构造+原型链)
function Parent3() {
this.name = 'parent3';
this.arr = [1, 2, 3]
}
function Child3() {
Parent3.call(this);
this.type = 'child3'
}
Child3.prototype = new Parent3();
优点
每个实例不会再互相影响
缺点
实例化时,父类被构造了两次,这没有必要 call一次,new一次
四、组合优化一
function Parent4() {
this.name = 'parent4';
this.arr = [1, 2, 3]
}
function Child4() {
Parent4.call(this);
this.type = 'child4'
}
Child4.prototype = Parent4.prototype;
缺点
无法判断实例的构造函数是父类还是子类
let c41 = new Child4();
let c42 = new Child4();
console.log(c41 instanceof Child4, c41 instanceof Parent4);
// true true
但其实,构造函数就是父类本身
console.log(c41.constructor); // Parent4
很难得才通过Parent4.call(this)
改变了构造函数的指向,现在又改回去了?天……不想看下去了行不行,兄dei,坚持一会就是胜利,别打瞌睡
Child4.prototype = Parent4.prototype
只是把Child4
的prototype
属性指针指向了Parent4.prototype
这个引用对象而已,实际上Parent4.prototype.constructor = Parent4
,这里说的有点绕,可以结合图好好理解一下
五、组合优化二
Object.create
请先移步
Object.create() - JavaScript | MDN
function Parent5() {
this.name = 'parent5';
this.arr = [1, 2, 3]
}
function Child5() {
Parent5.call(this);
this.type = 'child5'
}
// 组成原型链
Child5.prototype = Object.create(Parent5.prototype);
但是,这时候,实例对象的constructor
依然是Parent5
所以需要重新指定实例对象的构造器
Child5.prototype.constructor = Child5;
Good !
等下,还是验证一下吧
let c51 = new Child5();
let c52 = new Parent5();
console.log(c51 instanceof Child5, c51 instanceof Parent5);
console.log(c52 instanceof Child5, c52 instanceof Parent5);
console.log(c51.constructor, c52.constructor);
// true true
// false true
// Child5 Parent5
So perfect !
后记
感谢您耐心看到这里,希望有所收获!
如果不是很忙的话,麻烦点个star⭐【Github博客传送门】,举手之劳,却是对作者莫大的鼓励。
我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看【amandakelake的Github博客】
参考 继承与原型链 - JavaScript | MDN JavaScript inheritance by example by Dr.Axel Vjeux » Javascript – How Prototypal Inheritance really works How To Work with Prototypes and Inheritance in JavaScript | DigitalOcean
第四种有问题吧,Child4.prototype不能拓展了,不能在Child4的原型上新增方法,新增方法会影响到Parent4.
@yanguoyu 第四种方法本来就有这个问题,所以才会出现方法五,我上面解释了,你再认真看一下
哦,了解。
这个图有点问题吧,new Parent2()的constructor不是Child2,而是Parent2
我觉得最后一个图有误,组合继承调用两次,第一次是继承原型,第二次继承实例,Object.create(父原型)不就是建立一个父原型的一个副本么?子的原型为什么指向父的构造函数呢?