Blog

Keep up to date with the latest news

一文看懂 JS 继承

一文看懂 JS 继承最近回顾 js 继承的时候,发现还是对一些概念不是很清晰。这里再梳理一下 JS 中继承的几种主要的方式,构造函数继承、原型链继承、组合继承以及原型式继承、寄生式继承、寄生组合式继承和 ES6 的 Class:

构造函数继承构造函数继承没有用到 prototype 这种方式比较常见,定义和使用也较为简单,下面是一个例子?:

? 可以定义私有属性方法? 子类可以传递参数给父类❌ 不能定义共享属性方法/或写在外面失去了封装性代码语言:javascript代码运行次数:0运行复制function Parent(name, friends) {

this.name = name

this.friends = friends // ? 可以定义私有 引用类型不会被共享

this.share = share // ❌ 可以定义公有 但需要放在外部

this.log = log // ❌ 避免重复声明,为了复用需要放在外面

}

// ❌ 公有属性和方法定义在外面失去了封装性

let share = [1, 2, 3]

function log() {

return this.name

}

function Child(name, friends, gender) {

Parent.call(this, name, friends) // ? 可以在子类传递参数给父类

this.gender = gender

}原型链继承原型链模式需要手动重新绑定 constructor 而且不能定义私有变量

? 可以定义公有属性方法❌ 无论是定义还是继承都需要手动修改 constructor❌ 封装性一般❌ 不能定义私有属性方法❌ 没办法向父类传递参数代码语言:javascript代码运行次数:0运行复制function Parent() {}

Parent.prototype = {

constructor: Parent, // ❌ 需要手动绑定 constructor

name: 'oli', // ❌ 不能定义私有属性,全部都是公有

friends: ['alice', 'troy'], // ? 可以定义公有属性 所有实例都引用这个

log: function() { // ? 方法被共享了

return this.name

}

}

// 也可以写成多个 Parent.prototype.func1 = function(){} 封装性更差 但不用修改 constructor

// ❌ 封装性一般

function Child() {} // ❌ 没办法向父类传递参数

Child.prototype = new Parent() // 使用 new 操作符创建并重写 prototype

Child.prototype.constructor = Child // ❌ 每次继承都需要手动修改 constructor 谁叫你是覆盖 prototype 属性呢组合继承上面两者结合即成为组合继承模式,这个是结合了两者的优势,在 ES6 的 class 出现之前的常用方法,??看看例子:

? 公有的写在原型? 私有的写在构造函数? 可以向父类传递参数❌ 需要手动绑定 constructor❌ 封装性一般⚡ 重复调用父类性能损耗代码语言:javascript代码运行次数:0运行复制function Parent(name, friends) {

// ? 私有的写这里

this.name = name // ? 可以定义私有属性

this.friends = friends // ? 可以定义公有引用属性不会被共享

}

Parent.prototype = {

// ? 公有的写这里

constructor: Parent, // ❌ 需要手动绑定 constructor

share: [1, 2, 3], // ? 这里定义的公有属性会被共享

log: function() { // ? 方法被共享了

return this.name

}

}

// ❌ 封装性一般

function Child(name, friends, gender) {

Parent.call(this, name, friends) // ? 可以向父类传递参数 ⚡ 这里又调用了一次 Parent

this.gender = gender

}

Child.prototype = new Parent() // 使用 new 操作符创建并重写 prototype ⚡ 这里调用了一次 Parent

// 有方法避免多次调用直接去掉 new 操作符 转而写成 Child.prototype = Parent.prototype 这样并不好,虽然避免出现重复调用但导致修改子类 constructor 的时候父类也被修改了

Child.prototype.constructor = Child // ❌ 每次继承都需要手动修改 constructor 谁叫你是覆盖 prototype 属性呢

// 如果使用 Child.prototype = Parent.prototype 那么 constructor 子类父类是同一个原型式继承原型式继承直接使用 ES5 Object.create 方法,该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝

? 父类方法可以复用❌ 父类引用属性全部被共享❌ 子类不可传递参数给父类代码语言:javascript代码运行次数:0运行复制let parent = {

name: 'parent',

share: [1, 2, 3], // ❌ 父类的引用属性全部被子类所共享

log: function() { // ? 父类方法可以复用

return this.name

}

}

let child = Object.create(parent) // ❌ 子类不能向父类传递参数寄生式继承原型式继承的基础上为子类增加属性和方法

? 父类方法可以复用? 增加了别的属性和方法❌ 父类引用属性全部被共享❌ 子类不可传递参数给父类代码语言:javascript代码运行次数:0运行复制let parent = {

name: 'parent',

share: [1, 2, 3],

log: function() {

return this.name

}

}

function create(obj) {

let clone = Object.create(obj) // 本质上还是 Object.create

clone.print = function() { // 增加一些属性或方法

console.log(this.name)

}

return clone

}

let child = create(parent)寄生组合式继承杂糅了原型链式、构造函数式、组合式、原型式、寄生式而形成的一种方式:

组合继承的方法会调用两次 Parent,一次是在 Child.prototype = new Parent() ,一次是在 Parent.call()。这个是组合继承的唯一缺点,寄生组合式解决了这个问题:

? 公有的写在原型? 私有的写在构造函数? 可以向父类传递参数? 不会重复调用父类 ❌ 需要手动绑定 constructor (如果重写 prototype)❌ 需要调用额外的方法封装性一般代码语言:javascript代码运行次数:0运行复制function Parent(name, friends) {

this.name = name

this.friends = friends

}

Parent.prototype = {

constructor: Parent, // ❌ 需要手动绑定 constructor

share: [1, 2, 3],

log: function() {

return this.name

}

}

function Child(name, friends, gender) {

Parent.call(this, name, friends) // ⚡ 这里只需要调用一次 Parent

this.gender = gender

}

// 上半部分和组合继承一样

let F = function() {} // 创建一个中介函数

F.prototype = Parent.prototype // 这个中介的原型指向 Parent 的原型

Child.prototype = new F() // 注意这里没有使用 new 操作符调用 Parent

Child.prototype.constructor = Child对上述方法进行一个封装:

代码语言:javascript代码运行次数:0运行复制function Parent(name, friends) {

this.name = name // ? 可以定义私有属性

this.friends = friends // ? 可以定义公有引用属性不会被共享

}

Parent.prototype = {

constructor: Parent, // ❌ 需要手动绑定 constructor

share: [1, 2, 3], // ? 这里定义的公有属性会被共享

log: function() { // ? 方法被共享了

return this.name

}

}

function Child(name, friends, gender) {

Parent.call(this, name, friends) // ? 可以向父类传递参数 ⚡ 这里又调用了一次 Parent

this.gender = gender

}

function proto(child, parent) {

let clonePrototype = Object.create(parent.prototype)

child.prototype = clonePrototype

child.prototype.constructor = child

}

proto(Child, Parent)ES6 classclass 的语法,就比较清晰了,能用 class 就用 class 吧:

代码语言:javascript代码运行次数:0运行复制class Parent {

constructor(name, friends) { // 该属性在构造函数上,不共享

this.name = name

this.friends = friends

}

log() { // 该方法在原型上,共享

return this

}

}

Parent.prototype.share = [1, 2, 3] // 原型上的属性,共享

class Child extends Parent {

constructor(name, friends, gender) {

super(name, friends)

this.gender = gender

}

}另外可以使用 get set 方法将 share 属性写入到原型中去

另外,class 是一种语法糖使用 babel 将其转化一下看看:

小结最后上个图作为总结:

参考:

https://github.com/mqyqingfeng/Blog/issues/16https://segmentfault.com/a/1190000015727237https://segmentfault.com/a/1190000015216289