因为最近看到了js的几种继承方式,根据自己理解记录下来了
原型链继承
原型链继承就是将父元素的实例绑定到子元素的原型链上。这样子元素就继承了父元素和父元素原型链上的属性。
function Parent () {
this.a = 'a'
this.fn = function () {
console.log('fn')
}
}
Parent.prototype.arr = [1, 2, 3]
function Children () {
this.b = 'b'
}
// 继承之前添加的属性
Children.prototype.c = 'c'
// 原型链继承
Children.prototype = new Parent()
let children = new Children()
for (const key in children) {
console.log(key)
// 输出结果: b、a、fn、arr
// 可以看到我们在继承之前添加的属性 c 被覆盖了
}
console.log(children.arr) // [1, 2, 3]
children.arr.push(4)
let children2 = new Children()
// 可以看到子类实例修改从父类继承过来的引用属性会影响到所有的子类实例
console.log(children2.arr) // [1, 2, 3, 4]
console.log(children.arr) // [1, 2, 3, 4]
// 父类的引用属性也被修改了
console.log(new Parent().arr) // [1, 2, 3, 4]
// 父类新增属性,子类也可以访问
Parent.prototype.d = 'd'
console.log(`${children.d}-${children2.d}`) // 'd-d'
特点:
- 继承单一,不能给父类构造函数传递参数
- 在继承的时候会覆盖子类继承之前的属性,但是不会覆盖继承之后的属性
- 父类如果新增了属性,子类可以访问到
- 子类修改继承过来的父类引用属性会导致父类引用属性被修改
- 父类的属性会被子类所有实例共享
原型式继承
原型式继承就是 新建一个空的构造函数,将父类的实例绑定到这个空的构造函数中在返回这个构造函数,从而达到继承的效果
function Parent (...args) {
// 子类传递过来的参数
console.log(...args) // 1, 3, 4
// 父类本身的属性
this.a = 'a'
this.fnc = function () {
console.log('fnc')
}
this.arr = [1, 2, 3]
}
// 父类原型链上的方法
Parent.prototype.publicFunction = function () {
console.log(this.a)
}
/**
* @params { Function } parent 是父类实例
* @returns: Function 返回一个新的继承于 Parent 的构造函数
*/
function createClass (parent) {
// 新建一个空的函数
let fnc = function () {}
// 将当前函数的 prototype 指向 父类实例(这里就是原型链继承)
fnc.prototype = parent
return new fnc()
}
// 新生成的 children 就是继承与 父类的一个实例
let children = createClass(new Parent())
console.log(children instanceof Parent) // true
console.log(children.a) // 'a'
console.log(children.publicFunction()) // 'a'
特点:
- 这玩意继承和 原型链继承 一样
- es5新增了个 Object.create(obj, [propertiesObject]) 返回一个对象。这个对象继承与 obj
构造函数继承
通过call、apply 调用父类的构造函数并且修改父类构造函数的 this 指向 达到继承效果
function Parent (...args) {
// 子类传递过来的参数
console.log(...args) // 1, 3, 4
// 父类本身的属性
this.a = 'a'
this.fnc = function () {
console.log('fnc')
}
this.arr = [1, 2, 3]
}
// 父类原型链上的属性
Parent.prototype.b = 'b'
function Children () {
// 通过构造修改构造函数 Parent 的 this 指向,实现继承
// 可以向父类传递参数
let args = arguments
Parent.call(this, ...args)
// Parent.apply(this, args)
// 这里可以继承多个父类
// Parent2.call(this)
this.c = 'c'
}
// 给父类传递参数
let children = new Children(1, 3, 4)
for (const key in children) {
console.log(key)
// 输出结果: a、fnc、c
// 可以看到没有没有父类在原型链上添加的属性 b
}
console.log(children.b) // undefined
children.arr.push(4)
let children2 = new Children()
// 可以看到修改父类继承过来的引用属性不会导致其他实例/父类被修改
console.log(children.arr) // [1, 2, 3, 4]
console.log(children2.arr) // [1, 2, 3]
console.log(new Parent().arr) // [1, 2, 3]
// 只是子类的实例,不是父类的实例 而原型链继承是
console.log(children instanceof Parent) // false
特点:
- 无法继承父类通过 prototype 增加的属性(原型链上的属性),只能继承实例属性
- 解决了父类引用属性被多个子类实例共享
- 可以给父类传递参数
- 可以继承多个父类
- 只是子类的实例,不是父类的实例
- 无法实现方法的复用,因为每次需要继承的方法都只能写在父类里面
组合继承
组合继承就是 原型链继承 + 构造函数继承。简单来说就是:通过构造函数继承父类的实例方法/属性,通过原型链继承父类的原型方法/属性
注意:需要将子类的构造函数指向子类,否则会指向父类构造函数
function Parent (...args) {
// 子类传递过来的参数
console.log(...args) // 1, 3, 4
// 父类本身的属性
this.a = 'a'
this.fnc = function () {
console.log('fnc')
}
this.arr = [1, 2, 3]
}
// 父类原型链上的方法
Parent.prototype.publicFunction = function () {
console.log(this.a)
}
function Children () {
// 通过构造修改构造函数 Parent 的 this 指向,实现继承
// 可以像父类传递参数
let args = arguments
Parent.call(this, ...args)
// Parent.apply(this, args)
this.c = 'c'
}
// 1、将父类的实例挂载到子类的原型链上
Children.prototype = new Parent()
// 2、将父类的 prototype 复制给子类的 prototype (寄生式继承)
// Children.prototype = Parent.prototype
// 需要修改子类的构造函数的指向 否则会指向 父类的构造函数
Children.prototype.constructor = Children
// 给父类传递参数
let children = new Children(1, 3, 4)
for (const key in children) {
console.log(key)
// 输出结果: a、fnc、c
// 可以看到没有没有父类在原型链上添加的属性 b
}
console.log(children.b) // undefined
children.arr.push(4)
let children2 = new Children()
// 可以看到修改父类继承过来的引用属性不会导致其他实例/父类被修改
console.log(children.arr) // [1, 2, 3, 4]
console.log(children2.arr) // [1, 2, 3]
console.log(new Parent().arr) // [1, 2, 3]
// 子类实例是父类的实例
console.log(children instanceof Parent) // true
特点:
- 组合继承结合了 原型链继承 和 构造函数继承
- 实现了函数的复用。
- 子类可以调用父类原型链上的属性/方法
- 子类实例是父类的实例
- 调用了两次父类的构造函数,所以导致子类原型上有两份父类的实例属性/方法
将父类的 prototype 复制给子类的 prototype。这样可以避免调用了两次父类的构造函数导,子类原型上也不会有两份父类的实例方法(这就是寄生式继承)
拷贝继承
就是将父类的 prototype 上的属性 复制到子类上
function Parent (...args) {
// 子类传递过来的参数
console.log(...args) // 1, 3, 4
// 父类本身的属性
this.a = 'a'
this.fnc = function () {
console.log('fnc')
}
this.arr = [1, 2, 3]
}
// 父类原型链上的方法
Parent.prototype.publicFunction = function () {
console.log(this.a)
}
// 通过 for in 遍历父类来 copy 属性实现继承
/**
* @params { Function } parent 是父类实例
* @returns: Void
*/
function CreateClass (parent) {
for (const key in parent) {
this[key] = parent[key]
}
}
let children = new CreateClass(new Parent())
console.log(children.a) // a
特点:
- 可以实现多继承
- copy属性效率低
- 无法获取父类不可枚举对象 enumerable: false
还看到一种寄生式继承,但是这种寄生式继承方式于组合继承的区别就在于 在给子类添加父类的prototype属性的方式。已经在组合继承中写了通过修改 prototype 的方式来实现继承父类的 原型属性
es6 extends 继承
最后简单记录下 es6 类的继承
注意:class 不存在变量提升。所以必须先定义在调用
// 新建一个动物类
class Animal {
// 属性
food = ''
// 构造函数
constructor (food) {
this.food = food
}
eat () {
console.log(`吃${this.food}`)
}
// 静态方法直接调用
static hellow () {
console.log('hellow wold')
}
}
// 使用关键字 extends
// Dog 类 继承于 Animal
class Dog extends Animal {
constructor (food) {
// 给父类构造函数传递参数
super(food)
}
run () {
console.log('dog can run')
}
}
let dog = new Dog('小黄')
dog.run()
// 不会继承父类的静态方法
// dog.hellow() // dog.hellow is not a function
Animal.hellow()
dog.eat()
特点:
- 这个和其他语言的继承差不多
- 没有 provide 关键字。可以通过函数包裹模拟达到
- 可以实现多继承,使用 mixin 模式实现多继承。文档链接