面试之多种继承方式

132 阅读3分钟

常年混迹于茶水间的前端攻城狮日常记录

最近在看一个系列的文章,里面特别讲到多种继承方式,之前对继承这块的总觉得不够清晰。在这里记录整理一下,方便之后复习(内容上和原文章大部分是一样的,毕竟这是篇读书笔记的读书笔记)

这一系列文章都不错,好在讲的够明白。

原文章:👉 JavaScript深入之继承的多种方式和优缺点

原型链继承

🌼 总结:Child 的原型指向 Person 的实例

function Person() {
    this.name = 'Alice'
}

Person.prototype.say = function() {
    console.log(`I'm ${this.name}`)
}

function Child() { }

Child.prototype = new Person()

let child = new Child()

console.log(child.name) // Alice
child.say() // I'm Alice

⚠️ 缺点

  1. 引用类型的属性会被所有实例共享

    function Person() {
        this.name = 'Alice'
        this.firend = ['Bonnie', 'Stanfen', 'Wenry']
    }
    
    Person.prototype.say = function() {
        console.log(`I'm ${this.name}`)
    }
    
    function Child() { }
    
    Child.prototype = new Person()
    
    let child = new Child()
    // 引用类型
    child.firend.push('Bob')
    // 我们看看值类型
    child.name = 'newName'
    
    let child2 = new Child()
    console.log(child2.firend) // ["Bonnie", "Stanfen", "Wenry", "Bob"]
    // 你看值类型就不会
    console.log(child2.name) // Alice
    
  2. 在创建 Child 的实例时,不能向 Person 传参

借用构造函数(经典继承)

🌼 总结:通过使用 call 或者 apply 来将父类引入子类

function Person() {
    this.name = 'Alice'
    this.firend = ['Bonnie', 'Stanfen', 'Wenry']
}


function Child() { 
    Person.call(this)
}

let child = new Child()
console.log(child.name) // Alice
console.log(child.firend) // ["Bonnie", "Stanfen", "Wenry"]

✔️ 优点

  1. 避免引用类型属性值被所有实例共享问题

    function Person() {
        this.name = 'Alice'
        this.firend = ['Bonnie', 'Stanfen', 'Wenry']
    }   
    
    function Child() { 
        Person.call(this)
    }
    
    let child = new Child()
    child.name = 'newName'
    child.firend.push('Bob')
    
    let child2 = new Child()
    // 这次引用类型也没有改变
    console.log(child2.firend) // ["Bonnie", "Stanfen", "Wenry"]
    console.log(child2.name) // Alice
    
  2. 可以在 Child 中向 Person 传参

    function Person(age) {
        this.name = 'Alice'
        this.firend = ['Bonnie', 'Stanfen', 'Wenry']
        this.age = age
    }
    
    function Child(age) { 
        Person.call(this, age)
    }
    
    let child = new Child(19)
    console.log(child.age) // 19
    

⚠️ 缺点

此方法都在构造函数中定义,每次创建实例都会创建一遍方法

组合继承

🌼 总结:原型链继承 + 经典继承

function Person(name) {
    this.name = name
    this.firend = ['Bonnie', 'Stanfen', 'Wenry']
}

Person.prototype.say = function() {
    console.log(`I'm ${this.name}`)
}

function Child(name, age) { 
    // 经典继承
    Person.call(this, name)
    this.age = age
}

// 原型链继承
Child.prototype = new Person()
// 根据原型的关系图,这个时候 Child 原型指向的构造函数变成了 Person ,这是原型链继承的副作用
// 我们需要指回来
Child.prototype.constructor = Child

let child = new Child('Alice', 19)
console.log(child.name) // Alice
console.log(child.age) // 19
child.say() // I'm Alice

✔️ 优点

解决了传参问题和引用类型被所有实例共享的问题

function Person(name) {
    this.name = name
    this.firend = ['Bonnie', 'Stanfen', 'Wenry']
}

Person.prototype.say = function() {
    console.log(`I'm ${this.name}`)
}

function Child(name, age) { 
    // 经典继承
    Person.call(this, name)
    this.age = age
}

Child.prototype = new Person()
Child.prototype.constructor = Child

let child = new Child('Alice', 19)
child.firend.push('Bob')

let child2 = new Child('Bob', 20)
console.log(child2.firend) // ["Bonnie", "Stanfen", "Wenry"]

原型式继承

🌼 总结:将传入的对象作为创建的对象的原型。其实想想特别像在创建对象(除 null)的时候引擎会自动关联一个对象一样,只是这里我们手动给关联。这也是 Object.create() 的模拟实现。

function createObj(o) {
    function F() {}
    F.prototype = o
    return new F()
}

let person = {
    name: 'Alice',
    colors: ['pink', 'red', 'blue']
}

let p1 = createObj(person)
console.log(p1.name) // Alice
console.log(p1.colors) // ["pink", "red", "blue"]

⚠️ 缺点

引用类型会被共享

function createObj(o) {
    function F() {}
    F.prototype = o
    return new F()
}

let person = {
    name: 'Alice',
    colors: ['pink', 'red', 'blue']
}

let p1 = createObj(person)
p1.colors.push('green')

let p2 = createObj(person)
console.log(p2.colors) // ["pink", "red", "blue", "green"]

寄生式继承

🌼 总结:创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,然后返回对象。

function createObj(o) {
    let clone = Object.create(o)
    clone.say = function() {
        console.log('Hi~')
    }
    return clone
}

let person = {
    name: 'Alice',
    colors: ['blue', 'red', 'pink']
}

let p1 = createObj(person)
console.log(p1.name) // Alice
p1.say() // Hi~

⚠️ 缺点

  1. 每次创建都会创建一遍方法,经典继承一样
  2. 引用类型共享
function createObj(o) {
    let clone = Object.create(o)
    clone.say = function() {
        console.log('Hi~')
    }
    return clone
}

let person = {
    name: 'Alice',
    colors: ['blue', 'red', 'pink']
}

let p1 = createObj(person)
p1.colors.push('green')

let p2 = createObj(person)
console.log(p2.colors) // ["blue", "red", "pink", "green"]

记录完毕!