妈耶,此次终于了解继承了!

导读


相信许多小伙伴们对继承这个概念并不陌生,也是前端技术中较为基础和重点的地方,可是每每还有许多你并不真正了解的地方,先回答我下面这几个问题:前端

  • 实现继承有哪几种方式?
  • 了解寄生组合继承吗?它到底解决了什么问题?
  • 组合继承的优缺点是什么,和寄生组合继承差异在那?
  • 能够手写一个原型式继承吗?

    好,若是你能够很好的回答出上面这几个问题,那么你能够跳过这篇文章或者能够帮笔者去检查一下有没有出错或者有纰漏的地方,而那些没有回答上来的下伙伴,不要怀疑本身,快来和我一块儿在学一下吧。


构造函数继承

构造函数继承的思想特别简单,就是在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,所以能够经过使用apply()和call()方法也能够在新建立的对象上执行构造函数。数组

function SuperType(){
    this.colors = ['red', 'block', 'white'];
}

function SubType(){
    // 继承了SuperType
    SuperType.call(this);
}

let child1 = new SubType();

child1.colors.push('yellow');

console.log(child1.colors); // ['red', 'block', 'white','yellow']

let child2 = new SubType();

console.log(child2.colors); // ['red', 'block', 'white']

复制代码

对于原型链来讲,构造函数有一个比较大的优点,就是能够在子类型的构造函数中向超类型构造函数传递参数。bash

function SuperType(name){
    this.name = name;
}

function SubType(){
    SuperType.call(this, '周元');
    this.age = 24;
}

let child = new SubType();

console.log(child.name); // 周元

console.log(child.age); // 24

复制代码

缺点
app

方法都是在构造函数中定义的,没法函数复用,并且子类型不能继承超类型原型中定义的方法。实践中也会不多使用构造函数继承。函数



原型链继承

原型链继承的基本思想就是利用原型让一个引用类型继承另外一个引用类型的属性和方法。post

这里先回顾一下构造函数、原型和实例的关系。
每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。ui

function SuperType(){
    this.colors = ['red', 'block', 'white'];
}

function SubType(){}

//继承了SuperType
SubType.prototype = new SuperType();

let child1 = new SubType();

child1.colors.push('yellow');

console.log(child1.colors); // ['red', 'block', 'white','yellow']

let child2 = new SubType();

console.log(child2.colors); // ['red', 'block', 'white', 'yellow']

复制代码

这里SuperType构造函数定义了一个colors属性,该属性包含一个数组类型,SuperType的每一个实例都会有各自包含本身数组的colors属性。当SubType经过原型继承了SuperType以后,SubType.prototype就变成了Supertype的一个实例,所以它也拥有一个本身的colors属性。
原型链继承 解决了构造函数继承没法函数复用的问题,可是同时也出现了一些其余的问题。this

好比:不能够向超类型的构造函数传递参数以及实例共享的问题。实践中也会不多使用原型链继承spa



组合继承

组合继承其实也叫作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块儿,发挥两者之长的一种继承手段。prototype

具体思路就是使用原型链实现对原型属性和原型方法的继承,而经过构造函数来实现对实例属性的继承。

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'black', 'white']
}

SuperType.prototype.sayName = function () {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

SubType.prototype = new SuperType();

SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function () {
    console.log(this.age);
}

let child1 = new SubType('周元', 24);

child1.colors.push('yellow');

console.log(child1.colors); // [ 'red', 'black', 'white', 'yellow' ]

child1.sayName(); //周元

child1.sayAge(); // 24

let child2 = new SubType('夭夭', 23);

child2.colors.push('green');

console.log(child2.colors); // [ 'red', 'black', 'white', 'green' ]

child2.sayName(); //夭夭

child2.sayAge(); // 23

复制代码

组合继承的方式避免了原型链继承构造函数继承的缺陷,融合了他们的优势,成为JS最经常使用的继承方式。
可是这种继承方式也是存在着一点小缺点,就是不管什么状况下,都会调用两次超类型构造函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。


原型式继承

原型式继承的基本思想就是能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型。

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

复制代码

在object函数内部,先建立了一个临时的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时函数的一个实例。

在ECMAScript5中新增了object.create()方法规范了 原型式继承,这个方法接收两个参数:一个用做新对象原型的对象和(可选)一个为新对象定义额外的属性的对象。在传入一个参数的状况下和object()方法相同。

let SuperType = {
    name: '吞吞' ,
    friends: ['周元', '夭夭', '苍渊']
}

let SubType1 = Object.create(SuperType);

SubType1.name = '赵牧神';

SubType1.friends.push('九宫');

let SubType2 = Object.create(SuperType);

SubType2.name = '郗菁';

SubType2.friends.push('赵仙隼');

console.log(SuperType); // { name: '吞吞', friends: [ '周元', '夭夭', '苍渊', '九宫', '赵仙隼' ] }

复制代码

下面咱们再来看一下添加第二个参数的效果

var person = {
    name: '苏幼微',
    friends: ['武瑶', '武煌']
}

var child = Object.create(person, {
    name: {
        value: '叶冰凌'
    }
});

console.log(child); // {name: '叶冰凌'}

复制代码

关于原型式继承的缺点和原型链继承相同,就是会共享实例。


寄生式继承

寄生式继承的基本思路与寄生构造函数工厂模式相似,就是建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后在返回这个对象。

function createAnother(original){
    var clone = Object.create(original);  // 经过调用函数建立一个对象
    clone.sayHi = function(){ // 以某种方式来加强这个对象
        console.log('h1');
    }
    return clone; // 返回这个对象
}

var person = {
    name: '伊秋水'
}

var newPerson = createAnother(person);

newPerson.sayHi(); // hi

复制代码

在主要考虑对象而不是自定义类型和构造函数的状况下,寄生式继承也是一种有用的模式。可是使用寄生式继承来为对象添加函数,会因为不能作到函数复用而下降效率,这一点和构造函数继承相似。


寄生组合式继承

寄生组合继承的基本思想就是借用构造函数来继承属性,经过原型链的混合形式来继承方法,并且没必要为了指定子类型的原型而调用超类型的构造函数。咱们所须要的就是超类型原型的一个副本而已。本质上就是使用寄生式继承来继承超类型的原型,而后再将结果指定给子类型的原型。

function inheritprototype( subType, superType ) {
    var prototype = Object.create(superType.prototype); // 建立对象
    prototype.constructor = subType; // 指定原型
    subType.prototype = prototype; // 指定对象
}

复制代码

inheritprototype函数实现了寄生组合式继承的最简单形式,主要是分为三步:

1.建立超类型原型的一个副本。
2.为建立的副本添加constructor属性。
3.将新建立的对象赋值给子类型的原型。

而后咱们就能够这样使用:

function SuperType(name) {
    this.name = name;
    this.colors = ['周擎天', '秦玉'];
}

SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

inheritprototype(SubType, SuperType);

SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var subType1 = new SubType('绿萝', 22);

subType1.sayName(); // 绿萝

subType1.sayAge(); // 22

复制代码

寄生组合式继承只调用一次超类型构造函数,避免在SubType prototype上面建立没必要要的、多余的属性。开发者广泛认为寄生组合式继承是引用类型最理想的继承方式。


ES6继承

ES6继承的核心就是经过extends来实现继承。

class SuperType {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

class SubType extends SuperType {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

var subType1 = new SubType('周元', 1);

subType1.hello(); // Hello, 周元!

subType1.myGrade(); // I am at grade 1

复制代码

使用class继承的时候,咱们须要注意一下几点

  • 子类必须在constructor方法中调用super方法。
  • 只有在调用super()以后,才可使用this关键字

这里在稍微提一下class的特色

  • class 声明会提高,但不会初始化赋值。
  • class 声明内部会启用严格模式。
  • class 的全部方法(包括静态方法和实例方法)都是不可枚举的。
  • class 的全部方法(包括静态方法和实例方法)都没有原型对象 prototype,因此也没有[[construct]],不能使用 new 来调用.
  • 必须使用 new 调用 class。
  • class 内部没法重写类名。

综上 就是我对于前端实现继承的几种方式的一点小看法,文中若有错误,欢迎在评论区指正,若是这篇文章帮助到了你,欢迎点赞👍和关注,😀。

推荐阅读

参考


  • JavaScript高级程序设计
相关文章
相关标签/搜索