JavascriptES5 继承的优缺点对比

221 阅读6分钟

前言

又到了跳槽找工作的旺季,近几天整理了一些面试常考题,希望对你有所帮助,会的可以温习一下,不会的可以好好学习一下。欢迎点赞支持,共同学习成长!由于本文代码太多,掘金有总字符限制,分为多篇来写。

前端进阶这些技能你是否已经Get了,查漏补缺

动手实现一个符合Promise A+规范的Promise

ES5的继承方式有哪些,及优缺点

原型链继承

利用原型让一个引用类型继承另一个引用类型的属性和方法

function Super() {
    this.name='tom'
    this.hobby=['music','dance','skating']
}

Super.prototype.sayName = function () {
    console.log('super-name', this.name)
}

function Suber() {
    this.name='thomas'
    this.age=18
}

//原型链指向父类型
Suber.prototype = new Super()
Suber.prototype.myAge = function () {
    console.log('suber-age',this.age)
}

Suber.prototype.constructor = Suber


var suber1 = new Suber()
suber1.hobby.push('draw')
console.log(suber1.hobby) // ["music", "dance", "skating", "draw"]

var suber2 = new Suber()

console.log(suber2.hobby) // ["music", "dance", "skating", "draw"]

缺点

  1. 原型的引用类型的属性会被所有的实例共享;原先实例 suber1hobby 属性会被后创建的 suber2 实例共享。
  2. 在创建子类型实例时,无法在不影响所有实例的情况下给父类型的构造函数中传参。

借用构造函数继承

在子类型的构造函数中调用父类的构造函数

function Super(name) {
    this.name=name
    this.hobby=['music','dance','skating']
}

Super.prototype.sayName = function () {
    console.log('super-name', this.name)
}

function Suber(name,age) {
    Super.call(this,name)
    this.age=age
}

Suber.prototype.myAge = function () {
    console.log('suber-age',this.age)
}

var suber1 = new Suber('tom',18)
suber1.hobby.push('draw')
console.log(suber1.hobby) // ["music", "dance", "skating", "draw"]
suber1.sayName() // Uncaught TypeError: suber1.sayName is not a function

var suber2 = new Suber('thomas',20)
console.log(suber1,suber2)
console.log(suber2.hobby) // [ 'music', 'dance', 'skating' ]

优点

  1. 解决了在不影响所有实例的情况下向父类传参的问题
  2. 解决了原型中引用类型的属性被所有实例共享的问题

缺点

  1. 方法都在构造函数中定义,无法复用,父类型原型中的方法对于子类型是不可见的

组合继承 (原型链 + 借用构造函数)

是将原型链继承与借用构造函数继承结合在一起,发挥两种继承方式的优点

使用原型链实现对原型属性和方法的继承;借用构造函数实现实例属性的继承,即可以通过在原型上定义方法实现函数复用,又保证每个实例都有自己的属性。

function Super(name) {
    this.name=name
    this.hobby=['music','dance','skating']
}

Super.prototype.sayName = function () {
    console.log('super-name', this.name)
}

function Suber(name) {
    Super.call(this,name)
    this.age=18
}

//原型链指向父类型
Suber.prototype = new Super()
Suber.prototype.myAge = function () {
    console.log('suber-age',this.age)
}

Suber.prototype.constructor = Suber


var suber1 = new Suber('tom')
suber1.hobby.push('draw')
console.log(suber1.hobby) // ["music", "dance", "skating", "draw"]
suber1.sayName() // super-name tom

var suber2 = new Suber('thomas')
console.log(suber1,suber2)
console.log(suber2.hobby) // [ 'music', 'dance', 'skating' ]

优点

  1. 父类的构造函数执行两次;一次在子类构造函数内部,另一次在创建子类型原型的时候

缺点

  1. 可以向父类型传参
  2. 每个实例都有自己的属性
  3. 实现函数复用

原型式继承

借助原型可以基于已有对象创建新的对象,不必因此创建自定义类型

function object(target) {
    function Fun() { }

    Fun.prototype = target;

    return new Fun()
}

本质上 object() 对传入的对象做了一次浅拷贝

  1. object() 函数内部创建一个临时的构造函数
  2. 将传入的对象作为构造函数的原型
  3. 返回临时构造函数的实例

ES5 中新增 Object.create() 方法规范了原型式继承。

Object.create 接收两个参数, 返回一个新对象,带着指定的原型对象和属性。

Object.create(proto[, propertiesObject])

  1. proto: 新创建对象的原型对象。
  2. propertiesObject?: 可选参数,需要传入一个对象,该对象的属性类型参照 Object.defineProperties()的第二个参数。 如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。

在传入一个参数的情况下,Object.create()object() 方法的行为相同。

var person ={
    name:'tom',
    hobbys:["music", "dance", "skating"]
}

var person1 = Object.create(person)
person1.name = 'thomas';
person1.hobbys.push('draw');

var person2 = Object.create(person)
person2.name='Even';
person2.hobbys.push('run');

console.log(person1.hobbys) // [ 'music', 'dance', 'skating', 'draw', 'run' ]
console.log(person2.hobbys) // [ 'music', 'dance', 'skating', 'draw', 'run' ]

在没有必要创建构造函数,仅让一个对象与另一个对象保持相似的情况下,原型式继承是可以胜任的

缺点

  1. 同原型链实现的继承一样,包含引用类型的属性会被所有实例共享

寄生式继承

寄生式继承与原型式继承思路差不多,寄生式继承创建一个用于封装继承过程的函数,在函数内部以某种方式来增强对象。

function createObject(target) {
    var copy = object(target)
    copy.sayName = function () {
        console.log(this.name) // 'tom
    }

    return copy
}

function object(target) {
    function Fun() { }

    Fun.prototype = target;

    return new Fun()
}
var person ={
    name:'tom',
    hobbys:["music", "dance", "skating"]
}

var person1 = createObject(person)

person1.sayName() // 'tom

person1 不仅具有 person 的所有属性和方法,还有自己的 sayName 方法。在考虑对象而不是自定义类型和构造函数的情况下寄生式继承也是不错的。

缺点

  1. 同原型链式继承一样,包含引用类型的值的属性会被所有实例共享。
  2. 使用寄生式继承为对象添加函数,不能做到函数复用。

寄生组合式继承

通过借用构造函数、原型链、寄生混合成的继承方法

使用寄生式继承来继承父类的原型,而不用调用父类的构造函数,然后将结果指定给子类的原型

function inheritPrototype(suberType,superType) {
    var proto = object(superType.prototype) //创建父类型原型副本
    proto.constructor = suberType;  //修正子类原型的构造函数属性
    suberType.prototype = proto //将子类的原型替换为超类(父类)原型的(副本)浅复制
}
  1. 创建父类型原型副本
  2. 修正子类原型的构造函数属性
  3. 将子类的原型替换为超类(父类)原型的(副本)浅复制

完整代码

function Super(name) {
    this.name=name
    this.hobby=['music','dance','skating']
}

Super.prototype.sayName = function () {
    console.log('super-name', this.name)
}

function Suber(name) {
    Super.call(this,name)
    this.age=18
}

Suber.prototype.myAge = function () {
    console.log('suber-age',this.age)
}

function inheritPrototype(suberType,superType) {
    var proto = object(superType.prototype) //创建父类型原型副本
    proto.constructor = suberType;  //修正子类原型的构造函数属性
    suberType.prototype = proto //将子类的原型替换为超类(父类)原型的(副本)浅复制
}

function object(target) {
    function Fun() { }

    Fun.prototype = target;

    return new Fun()
}
inheritPrototype(Suber,Super)

var suber1 = new Suber('tom')
suber1.hobby.push('draw')
console.log(suber1.hobby) // ["music", "dance", "skating", "draw"]
suber1.sayName() // super-name tom

var suber2 = new Suber('thomas')
console.log(suber1,suber2)
console.log(suber2.hobby) // [ 'music', 'dance', 'skating' ]

优点

  1. 只调用一次父类的构造函数,效率更高,避免在 Suber.prototype 上面创建不必要的属性,并保持原型链不变;

总结

因此寄生组合式继承是引用类型最理想的继承方式

往期回顾

前端进阶这些技能你是否已经Get了,查漏补缺

动手实现一个符合Promise A+规范的Promise