深入继承:一步步捋清五种继承方式

681 阅读2分钟
原文链接: github.com

总览

c5925056-aa27-4b97-9f5d-2ec786ea5125

一、借助构造函数

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();

原型图如下
f9311957-d401-4bc0-961b-65f3f49d65ea

缺点

引用类型的属性被所有实例共享,实例之间会互相影响

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);

c79f3ffe-030d-4753-9f32-361a2dffb9d2

三、组合(构造+原型链)

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;

3dcc9c3e-5e1d-45bc-bcb5-72aaea635cc2

缺点

无法判断实例的构造函数是父类还是子类

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只是把Child4prototype属性指针指向了Parent4.prototype这个引用对象而已,实际上Parent4.prototype.constructor = Parent4,这里说的有点绕,可以结合图好好理解一下
af779508-30f5-43ea-8af8-ff25a308ccf9

五、组合优化二

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

f611911f-9fc1-4ff8-8da6-eb7874eeb335

所以需要重新指定实例对象的构造器

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