前端学习系列——(十)JavaScript的继承

这里主要讲4种ES5继承以及ES6的class语法糖的继承。

这里给出父类的定义:

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

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

1、原型链继承

核心:将父类的实例作为子类的原型

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

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

此时原型对象的constructor的执行变成了SuperType,因为constructor指向构造函数,所以重新指向子类的构造函数即可,即Subtype.prototype.constructor = SubType;

优点

1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例,通过instanceof可以判断

2)父类新增原型方法/属性,子类都可以访问到

3)简单、易于实现

缺点

1)要想子类新增属性和方法,必须要在new SuperType()这样的语句之后执行,不能放到构造函数中

2)无法实现多继承

3)来自原型对象的引用属性是所有实例共享的

4)创建子类实例时,无法向父类构造函数传参

2、借用构造函数继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类,在子类的构造函数内使用SuperType.call(this);

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

优点

1、解决了原型链继承中,子类实例共享父类属性的问题

2、创建子类实例是,可以向父类传递参数

3、可以实现多继承(call多个父类对象)

缺点

1、实例并不是父类的实例,只是子类的实例(instanceof可以判断)

2、只能继承父类的实例属性和方法,不能继承原型属性/方法

3、无法实现函数服用,每个子类都有父类的实例函数的副本,影响性能

3、组合继承(借用构造函数继承和原型链继承的结合)

核心:在构造函数内利用call方法,直接调用属性,然后在原型prototype上绑定绑定方法(通过实例化new父对象),然后修改constructor指向子对象的构造函数。

function SubType(name){
    SuperType.call(this,name);//第二次调用父类
}
SubType.prototype = new SuperType();//第一调用父类
SubType.prototype.constructor = SubType;

优点

1、既是子类的实例,也是父类的实例

2、不存在引用属性共享问题

缺点

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

4、寄生组合式继承(目前最优)

核心:通过寄生方式,砍掉父类的实例属性,这样在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免了组合继承的缺点,如下

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

(function () {
    var Super = function () {};
    Super.prototype = SuperType.prototype;
    SubType.prototype = new Super();
    SubType.prototype.constructor = SubType;
})();

在《JS高程》第6章中实现了一个名叫object的函数,用于原型式继承

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

利用这个函数就可以将上面通过闭包的方式改成下面这样

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

SubType.prototype = object(SuperType.prototype);
SubType.prototype.constructor = SubType;

后来ES5提出了Object.create(prototype,[propertiesObject])方法,这个方法会创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。当只有一个参数时,它的行为和上面定义的object方法一样,所以就可以改写成下面的

function SubType(name) {
    SuperType.call(this,name);
}
//Object.create(obj),这个方法会对传入的obj对象进行浅拷贝
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;

至此,Object.create(prototype) 就广泛被用于实现类的继承。

5、ES6继承

核心:通过ES6语法糖class,使用extends关键字

class SubType extends SuperType{
    constructor(...args){
        super(...args);
    }
}

通过在class的constructor方法内调用super,调用父类的constructor方法,用于新建父类的this对象。

在ES5中每个对象都一个内置属性__proto__指向他的构造函数的原型对象prototype,在ES6中class作为构造函数的语法糖,也有__proto__和prototype这两个属性。

SubType.__proto__ === SuperType;//true
SubType.prototype.__proto__ === SuperType.prototype;//true

之所以会这样是因为在ES6的继承中,实际上是用的下面方式实现的:

class A {
}

class B {
}

// B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B继承A的静态属性
Object.setPrototypeOf(B, A);

而setPrototypeOf方法的实现如下:

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

所以就得到上面的结果

Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;

这两条继承链可以理解为:作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。

Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;

PS:只要带有prototype属性的函数就能被extends关键字继承

 

部分内容参考《JS高级程序设计第三版》和《ES6标准入门第三版》

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值