常见的7种继承方式

535 阅读3分钟
  • 例子: 比如说我们生活中的汽车就是一个类
  • 轿车 和 货车 分别 继承汽车的属性 只不过 轿车 后面加的是 后备箱, 货车后面接的是大货箱, 使得轿车 和 货车 具备与父类不同的方法
  • 定义: 继承可以是的子类别具有父类的各种方法和属性

第一种 原型链继承

  • 原型链继承是比较常见的继承方式之一,其中涉及的构造函数,原型和实例三者之间的关系
  • 每一个构造函数都有一个原型对象
  • 原型对象有包含一个指向 构造函数的指针
  • 而实例包含一个原型对象的指针
function Parent1 () {
    this.name = 'parent1';
    this.play = [1,2,3]
}
function Child1() {
    this.type = 'child2'
}
Child1.prototype = new Parent1()
console.log(new Child1())
var s1 = new Child1()
var s2 = new Child1()
s1.play.push(4)
console.log(s1.play, s2.play)

原型继承会有一个问题

  • 明明我只改变了 s1 为什么 s2 也发生了改变
  • 因为两个实例使用的是一个原型对象,因此内存空间是共享的,当一个发生变化的时候,另一个也随之进行了变化

第二种 构造函数继承 (借助 call)

function Parent1() {
    this.name = 'parent1'
}
Parent1.prototype.getName = function () {
    return this.name
}
function Child1() {
    Parent1.call(this);
    this.type = 'child1'
}
let child = new Child1()
console.log(child)
console.log(child.getName())

  • 子类拿到父类的属性值,避免了原型链继承的弊端,父类原型对象前,出现自己定义的方法,那么子类将无法继承这些方法
  • 构造函数的优缺点
  • 父类的引用属性不会被共享,解决了原型链继承的弊端,但随之而来的缺点也比较明显, 只能继承父类的实例属性和方法, 不能继承原型属性和方法

第三种 组合继承

function Parent3 () {
    this.name = 'parent3';
    this.play = [1,2,3]
}
Parent3.prototype.getName = function () {
    return this.name
}
function Child3 () {
    Parent3.call(this);
    this.type = 'child3'
}
Child3.prototype = new Parent3()
Child3.prototype.constructor = Child3()
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4)
console.log(s3.play, s4.play)
console.log(s3.getName())
console.log(s4.getName())

  • parent3 多构造了一次 会造成 内存上额外的开销

第四种 原型式继承

let parent4 = {
    name: 'parent4',
    friends: ['p1','p2','p3'],
    getName: function () {
        return this.name
    }
}
let person4 = Object.create(parent4)
person4.name = 'tom'
parent4.friends.push('jerry')
let person5 = Object.create(parent4)
person5.friends.push('lucy')
console.log(person4.name)
console.log(person4.name === person4.getName())
console.log(person5.name)
console.log(person4.friends)
console.log(person5.friends)
  • 不仅能继承属性 还能继承 getName 方法
  • 但是 引用数组是共享的
  • Object.create() 可以对一些对象实现浅拷贝的
  • 那么这种继承方式的缺点也很明显
  • 实例的引用,类型属性指向相同内存, 存在篡改的可能

第五种 寄生式继承

  • 原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增添一些方法
  • 优点:
  • 寄生式继承相比原型式继承,还是在父类基础上添加了更多的方法
let parent5 = {
    name: 'parent5',
    friends: ['p1','p2','p3'],
    getName() {
        return this.name
    }
}
function clone (original) {
    let clone = Object.create(original)
    clone.getFriends = function () {
        return this.friends
    }
    return clone
}
let person5 = clone(parent5)
console.log(person5.getName())
console.log(person5.getFriends())

第六种 寄生组合式继承

function clone(parent,child) {
    // 这里用 Object.create 就可以减少组合式继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype)
    child.prototype.constructor = child
}
function Parent6 () {
    this.name = 'parent6'
    this.play = [1,2,3]
}
Parent6.prototype.getName = function () {
    return this.name
}
function Child6() {
    Parent6.call(this)
    this.friends = 'child5'
}
clone(Parent6,Child6)
Child6.prototype.getFriends = function () {
    return this.friends
}
let person6 = new Child6()
console.log(person6)
console.log(person6.getName())
console.log(person6.getFriends())

第七种 可以通过 ES6 的 extends 关键是实现继承

class Person {
    constructor(name) {
        this.name = name
    }
    // 原型方法
    // 既 Person.prototype.getName = function() {}
    // 可以简写成 getName(){}
    getName () {
        console.log('Person',this.name)
    }
}
class Gamer extends Person {
    constructor(name,age) {
        super(name);
        this.age = age
    }
}
const asuna = new Gamer ('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

extends 底层也是使用的寄生组合式继承

总结