JS之继承

一、概念

一个类获取另一个或者多个类的属性或者方法。继承可以使得子类具有父类的各种方法和属性。以免重复输出很多代码。

 

二、原理

复制父类的方法和属性来重写子类的原型对象。

下面总结了多种实现继承的方式及各自的优缺点——

 

三、原型链继承

实现

function Father () {
    this.text = '1';
}

Father.prototype.someFn = function () {
    console.log(1);
}

Father.prototype.someValue = '2';

function Son(){
    this.text1 = 'text1';
}

Son.prototype = new Father();    // 函数原型指向构造函数的实例,相当于修改了原型链实现继承
Son.prototype.constructor = Son; //  一定要把Son原型上的contructor重新指向Son

优点

简单易操作。

缺点

1、父类使用this声明的属性被所有实例共享。原因是实例化是父类一次性赋值到子类实例的原型上,它会将父类通过this声明的属性也赋值到子类原型上。例如在父类中一个数组值,在子类的多个实例中,无论哪一个实例去修改这个数组的值,都会影响到其他子类实例。

2、创建子类实例时,无法向父类构造函数传参,不够灵活。

 

四、借用构造函数(call)

实现

function Father (...arr) {
    this.some = '父类属性';
    this.params = arr;
}

Father.prototype.someFn = function () {
    console.log(1);
}

Father.prototype.someValue = '2';

function Son(fatherParams, ...sonParams) {
    // 使用call调用父类,Father将会立即被执行,并且将父类的Father的this执行Son
    // 的this。实例化子类,this将指向new期间创建的新对象,返回该新对象。
    Father.call(this, ...fatherParams);
    this.text = '子类属性';
    this.sonParams = sonParams;
}

var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);

优点

1、可以向父类传递参数。

2、解决父类this声明的属性会被实例共享的问题。

缺点

1、只能继承父类通过this声明的属性/方法。不能继承父类prototype上的属性/方法。

2、父类方法无法复用。每次实例化子类,都要执行父类函数。重新声明父类所定义的方法,无法复用。

 

五、组合继承(call+new)

原理:通过原型链继承来将prototype上的属性和方法继承制子类的原型对象上。借用构造函数来继承父类通过this声明的属性和方法。

实现

function Father(...arr) {
    this.some = '父类属性';
    this.params = arr;
}

Father.prototype.someFn = function() {
    console.log(1);
}

Father.prototype.someValue = '2';

function Son(fatherParams, ...sonParams) {
    // 借用构造函数继承父类this声明的属性和方法到子类实例属性上
    Father.call(this, ...fatherParams);
    this.text = '子类属性';
    this.sonParams = sonParams;
}

// 原型链继承,将`this`和`prototype`声明的属性/方法继承至子类的`prototype`上
Son.prototype = new Father('xxxxx');
Son.prototype.constructor = Son;

var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);

优点

1、解决原型链继承父类this声明的属性或者方法被共享的问题。

2、解决借用构造函数解决不能继承父类prototype对象上的属性/方法问题。

缺点

1、调用了父类函数两次,造成一定的性能问题。

2、因调用两次父类,导出父类通过this声明的属性和方法被生成两份的问题。

 

六、原型式继承

实现

function cloneObj(obj) {
    function F(){};
    // 将被继承的对象作为空函数的prototype
    F.prototype = obj;
    // 返回new期间创建的新对象,此对象的原型为被继承的对象,
    // 通过原型链查找可以拿到被继承对象的属性
    return new F();
}

优点

1、兼容性好,最简单的对象继承。

缺点

1、多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。

 

七、寄生式继承(继承过程封装)

创建一个仅用于封装继承过程的函数,改函数在内部已某种方式类增强对象,最后返回对象。在原型式继承的基础上进行增强对象。

实现

function createAnother(original){
    var clone = cloneObject(original); // 继承一个对象 返回新函数
    // do something 以某种方式来增强对象
    clone.some = function(){}; // 方法
    clone.obkoro1 = '封装继承过程'; // 属性
    return clone; // 返回这个对象
}

优点

1、兼容性好,最简单的对象继承。

缺点

1、多个实例共享被继承的属性,存在被篡改的情况,不能传递参数。

 

八、寄生组合式继承

实现

function Son(){
    Father.call(this)
}

Son.prototype = createObject(Father.prototype)
Son.prototype.constructor = Son;

function createObject(o){
    function fn(){}
    fn.prototype = o;
    return new fn;
}

优点

1、同组合继承,且调用两次父类的构造的时候,就不会初始化两次实例方法/属性,同时也避免了组合继承的缺点

缺点

1、实现较为复杂。

优化

function Son(){
    Father.call(this)
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son;

 

九、寄生组合式继承(call+寄生式封装)

1、使用借用构造函数来继承父类this声明的属性和方法。

2、使用寄生式继承来设置父类prototype为子类prototype的原型来继承父类的属性和方法。

实现

function Father(...arr) {
    this.some = '父类属性';
    this.params = arr;
}

Father.prototype.someFn = function() {
    console.log(1);
}

Father.prototype.someValue = '2';

function Son() {
    Father.call(this, 'xxxx');
    this.text = '2222';
}

function inhertPro(son, father){
    // 原型式继承
    var fatherPrototype = Object.create(father.prototype);
    // 设置Son.prototype的原型是Father.prototype
    son.prototype = fatherPrototype;
    // 修正constructor 指向
    // constructor的作用:返回创建实例对象的Object构造函数的引用。
    // 在这里保持constructor指向的一致性
    son.prototype.constructor = son;
}

inhertPro(Son, Father);
var sonInstance = new Son();

优点

1、寄生组合式继承是当前最成熟的继承方法,也是先也常用的继承方法,在大多数Js框架中都是用这个作为继承方案。

寄生组合式继承相对组合继承的优点:

1、只调用了父类构造函数一次,节约了性能。

2、避免生成了不必要的属性。

3、使用原型式继承保证了原型链上下文不变,子类的prototype只有子类通过prototype声明的属性和方法,父类的prototype只有父类通过prototype声明的属性和方法。

 

十、ES6-extends继承

实现

ES6可以用过extends关键字实现继承,这比通过ES5的修改原型链实现继承,要清晰和方法很多。

class Point{} class ColorPoint extends Point{}

注意

子类必须在constructor方法中代用super方法,否则新建实例将会报错,这是因为子类自己的this对象,必须先通过父类的构造函数完成塑性,得到父类的属性和方法,然后对其加工,加上子类自己的属性和方法。如果不调用super方法,子类将得不到this对象。如果没有定义constructor方法,这个方法会被默认的添加。

转换

ES6继承的原理跟寄生组合式继承是一样的。优缺点也相仿。

把ES6的代码装换为ES5

转换前:

class Point {
    //构造函数,里面写上对象的属性
    constructor (props) {
      this.name = props.name || 'Unknown';
    }
    //方法写在后面
    run () {
      console.log(1);
    }
}
class ColorPoint extends Point {
    //构造函数
    constructor (props,myAttribute) {    //props是继承过来的属性,myAttribute是自己的属性
        super(props);    //相当于获得父类的this指向
        this.name = props.name || 'Unknown';
        //父类的属性,也可写在父类中
        this.type = props.type || "Unknown";
        //自己的私有属性
        this.attr = myAttribute;
    }
    //自己私有的方法
    fly () {
      console.log(1);
    }
}

转换后:

转换的结果核心代码如下:用于子类的prototype继承父类的prototype方法。

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass, writable: true, configurable: true
        }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
}

区别

ES5的继承实质是先创建子类的实例对象this,然后将父类的方法添加到this上。

ES6的继承实质是先将父类实例对象的方法和属性加到this上面,然后在用子类的构造函数修改this。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值