JavaScript中的继承

前言

做为 JavaScript 中最重要的内容之一,继承问题一直是咱们关注的重点。那么你是否清晰地知道它的原理以及各类实现方式呢html

阅读这篇文章,你将知道:前端

  • 什么是继承
  • 实现继承有哪几种方式
  • 它们各有什么特色

这里默认你已经清楚的知道构造函数、实例和原型对象之间的关系,若是并非那么清晰,那么推荐你先阅读这篇文章 -- JavaScript 中的原型与原型链git

若是文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过github

如下↓web

概念

继承(inheritance)是面向对象软件技术当中的一个概念。若是一个类别 B 继承自 另外一个类别 A ,就把这个 B 称为 A的子类 ,而把 A 称为 B的父类别 也能够称 A是B的超类 。继承可使得子类具备父类别的各类属性和方法,而不须要再次编写相同的代码 ... 更多)

image

经过这些概念和图示咱们不难知道继承能够在咱们的开发中带来的便捷,那么在 JavaScript 中如何去实现继承呢?segmentfault

继承实现方式

原型链继承

利用原型让一个引用类型继承另外一个引用类型的属性和方法
function SuperType() {
    this.name = 'tt';
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    this.name = 'oo';
}
SubType.prototype = new SuperType()

var instance = new SubType()

instance.sayName() // oo
instance instanceof SubType // true
instance instanceof SuperType // ture

以上的试验中,咱们建立了两个构造函数 SuperTypeSubType ,而且让 SubType 的原型指向 SuperTypeSubType 也就继承了 SuperType 原型对象中的方法。因此在建立 instance 实例的时候,实例自己也就具备了 SuperType 中的方法,而且都处在它们的原型链中app

SubType.prototype.constructor == SubType // false
SubType.prototype.constructor == SuperType // true

须要注意的是:这个时候 SubType.prototype.constructor 是指向 SuperType 的,至关于重写了 SubType 的原型对象。函数

用一张图表示:学习

image

  • SubType.prototype 至关于 SuperType 的实例存在的,因此 SubType.prototype.constructor 就指向 SuperType

原型继承的特色

优势:this

  • 简单、易于实现
  • 父类新增原型方法/原型属性,子类都能访问到
  • 很是纯粹的继承关系,实例是子类的实例,也是父类的实例

缺点:

  • 没法实现多继承
  • 想要为子类 SubType 添加原型方法,就必须在 new SuperType 以后添加(会覆盖)
  • 来自原型对象的全部属性被全部实例共享(引用类型的值修改会反映在全部实例上面)
  • 建立子类实例时,没法向父类构造函数传参

借用构造函数

在子类构造函数的内部调用超类型构造函数,经过 applycall 实现
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}

function SubType() {
    SuperType.call(this, 'tt');
}

var instance = new SubType()
var instance1 = new SubType()

instance.colors // ['red', 'orange', 'black']
instance.name // tt

instance.colors.push('green');
instance.colors // ['red', 'orange', 'black', 'green']
instance1.colors // ['red', 'orange', 'black']

借用构造函数的特色

优势:

  • 解决了原型链继承不能传参的问题
  • 子类实例共享父类引用属性的问题
  • 能够实现多继承(call能够指定不一样的超类)

缺点:

  • 实例并非父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 没法实现函数复用

组合继承

伪经典继承(最经常使用的继承模式):将原型链和借用构造函数的技术组合到一块儿。使用原型链实现对原型属性和方法的继承,经过构造函数来实现对实例属性的继承
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, 'tt');
    this.name = 'oo';
}
// 这里的 SubType.prototype.constructor 仍是指向 SuperType
SubType.prototype = new SuperType();

var instance = new SubType();
var instance1 = new SubType();

instance.name // oo
instance.sayName() // oo

instance.colors.push('green');
instance.colors // ['red', 'orange', 'black', 'green']
instance1.colors // ['red', 'orange', 'black']

组合继承的特色

优势:

  • 能够继承实例属性/方法,也能够继承原型属性/方法
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺点:

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

原型式继承

借助原型链能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型
function obj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

var person = {
    name: 'tt',
    age: 18,
    colors: ['red', 'green']
}

var instance = obj(person);
var instance1 = obj(person);
instance.colors.push('black');
instance.name // tt
instance.colors // ['red', 'green', 'black']
instance1.colors // ['red', 'green', 'black']

建立一个临时的构造函数,而后将传入的对象当作这个构造函数的原型对象,最后返回这个临时构造函数的新实例。实际上,就是对传入的对象进行了一次浅复制

ES5 经过新增 Object.create() 规范化了原型式继承

更多 Object.create()语法请点击 这里

原型式继承特色

优势:

  • 支持多继承(传入的对象不一样)
  • 不须要兴师动众的建立不少构造函数

缺点: 和原型链继承基本一致,效率较低,内存占用高(由于要拷贝父类的属性)

寄生式继承

建立一个仅用于封装继承过程的函数,在函数内部对这个对象进行改变,最后返回这个对象
function createAnother(obj) {
    var clone = Object(obj);
    clone.sayHi = function() {
        alert('Hi');
    }
    return clone
}

var person = {
    name: 'tt',
    age: 18,
    friends: ['oo', 'aa', 'cc'],
    sayName() {
        return this.name
    }
}

var instance = createAnother(person)
var instance1 = createAnother(person)

instance.friends.push('yy')

instance.name // 'tt'
instance.sayHi() // Hi
instance.friends // ["oo", "aa", "cc", "yy"]
instance1.friends // ["oo", "aa", "cc", "yy"]

寄生式继承的特色

优势:

  • 支持多继承

缺点:

  • 实例并非父类的实例,只是子类的实例
  • 不能实现复用(与构造函数类似)
  • 实例之间会互相影响

寄生组合继承

借用构造函数来继承属性,经过原型链的混成形式来继承方法。经过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function inherit(subType, superType) {
    var obj = Object(superType.prototype); // 建立对象
    obj.constructor = subType;  // 指定constructor
    subType.prototype = obj;    // 指定对象
}

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'orange', 'black'];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, 'tt');
    this.name = 'oo';
}

inherit(SubType, SuperType)

var instance = new SubType()

instance.name // oo
instance.sayName // oo
instance instanceof SubType // true
instance instanceof SuperType // true
SubType.prototype.constructor == SubType // true

寄生组合继承的特色

堪称完美,只是实现稍微复杂一点

后记

做为 JavaScript 最重要的概念之一,对于继承实现的方式方法以及它们之间的差别咱们仍是颇有必要了解的。

在实现继承的时候,拷贝 也是一种颇有效的方式,因为 JavaScript 简单数据类型与引用类型的存在,衍生出了 浅拷贝深拷贝 的概念,那么它们又是什么,怎么去实现呢

且听下回分解,哈哈

周末愉快

最后,推荐一波前端学习历程,不按期分享一些前端问题和有意思的东西欢迎 star 关注 传送门

参考文档

JavaScript 高级程序设计

JavaScript实现继承的几种方式

相关文章
相关标签/搜索