ES6 Class的原理,一篇文章讲明白super

该篇文章参考以下博文,本文只用于记录学习
https://segmentfault.com/a/1190000015565616

ES6中的extends原理

class的继承语法源于寄生组合继承,下方代码是通过babel转化的部分class继承

//转化前
class A extends B {}
//转化后
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);
}

function _setPrototypeOf(o, p) {
	_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
		o.__proto__ = p;//原型链继承关键
		return o;
	};
	return _setPrototypeOf(o, p);
}

var Rabbit = /*#__PURE__*/ function(_Animal) {
	_inherits(Rabbit, _Animal);

	var _super = _createSuper(Rabbit);

	function Rabbit() {
		_classCallCheck(this, Rabbit);

		return _super.apply(this, arguments);//组合继承关键
	}

	return Rabbit;
}(Animal);

原型链继承的关键在于,改变子构造函数原型的__prototype__指向,,这样通过子构造函数new的实例可以访问到Animal的方法
在这里插入图片描述

extends的使用方法

  • 第一种,直接继承类
class Rabbit extends Animal {}
  • 第二种,可以跟经过函数处理后的类
function Animal(behavior) {
  return class {
    run() { alert(behavior) }
  }
}


class Rabbit extends Animal("I can run") {}


new User().run(); // I can run

上面方法适用于,当类需要条件生成时,很方便。

使用父组件方法

继承,自然是为了可以使用父当中的方法,或者继承属性,不然费这么大的劲儿干啥

class Animal {
    constructor(name) {
        this.speed = 0;
        this.name = name;
    }
    run (speed) {
        this.speed += speed;
        debugger
        alert(`${this.name} runs with speed ${this.speed}.`);
    }
}

class Rabbit extends Animal {
    jump() {
        alert(`${this.name} jump`);
    }
    sports () {
        super.run(5);
        this.jump();
    }
}

let rabbit = new Rabbit('White Rabbit');

rabbit.run(10); //White Rabbit runs with speed 10.
rabbit.sports(); //White Rabbit runs with speed 15.White Rabbit jump

箭头函数中可以从外部访问到super

正常来讲,由于super是父类提供给子类使用父方法的媒介,在一般函数中因为this的指向会指向调用它的那个对象,所以无法使用super。

class Animal {
            constructor(name) {
                this.speed = 0;
                this.name = name;
            }
            run(speed) {
                this.speed += speed;
            }
        }

        class Rabbit extends Animal {
            jump() {
                console.log(`${this.name} jump`);
                console.log('jump中的this', this); //Rabbit
               
                setTimeout(function () {
                    console.log('function中的this', this)//Window 
                    super.run(5) //报错,由于this指向改变,无法获取到Rabbit,进而无法获取到Animal
                }, 1000)
                setTimeout(() => {
                    console.log('箭头function中的this', this)//Rabbit
                }, 1000)

            }
            sports() {
                super.run(5);
                this.jump();
            }
        }

        let rabbit = new Rabbit('White Rabbit');

        rabbit.sports(); 

子类的构造函数

子类的constructor在添加新的属性的时候,如果直接添加,会报错this没有定义

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }


  // ...
}
let rabbit = new Rabbit("White Rabbit", 10);// Error: this is not defined.

上面代码中,因为通过继承得来的构造函数,内部会被标记为
[[ConstructorKind]]:“derived”构造函数类型:派生的
而派生的构造函数内部的this不会被创建,而是希望父类来创建。这个时候就需要调用super()来帮助子类创建一个构造函数,而且创建必须发生在使用this之前。

Super的实现原理

Super的实现不同于我们现在已知的任何方法,当一个对象运行的时候,将当前对象作为this

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {

    // call改变了this的指向,如果不加call,那么最终输出将为Animal eats.而不是下面的Rabbit eats.
    this.__proto__.eat.call(this); // (*)

  }
};

rabbit.eat(); // Rabbit eats.

上面的(*)如果不理解,可以看一下这篇文章里面的第4点
JavaScript各种继承,原型继承,构造函数继承,组合继承,寄生组合,ES6继承,我能学会你也可以
上面的构造函数继承理解后,接下来我们追加一个继承,

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    this.__proto__.eat.call(this); // (**)
  }
};


longEar.eat(); // Error: Maximum call stack size exceeded 超过最大调用堆栈

一般来说,如果报了上面那个错,基本上就是陷入死循环或者回调地狱了。
具体原因我们一起来看一下,跟踪一下longEar.eat()

  • 首先longEar.eat()中,this.__proto__代表的是rabbit,后面的this代表的是longEar
  • 然后在rabbit中,this.__proto__代表的也是rabbit,后面的this代表的也是longEar(call改变了this指向,让父构造函数中的this变为子中的this)
  • 再然后,rabbit中,的eat就变成了下方这个样子
let rabbit = {
  __proto__: animal,
  eat() {
    rabbit.eat.call(longEar); //这里是为了理解做了个转化,正常代码不能这样写
  }
};

上面代码先不看call(longEar),只看rabbit.eat的话,如下

let rabbit = {
  __proto__: animal,
  eat() {
    rabbit.eat()//这里出现了一个自调用,导致eat无限调用自己,陷入循环
  }
};

上面代码陷入循环不要紧,还有call,说不定call可以帮助脱离循环。

我们知道call是可以改变this的指向,那改变了this后,循环自身执行eat的时候,rabbit (this._ _proto__)里面跟的this被call改变成了longEar,所以执行的还是rabbit.eat,并且无限循环下去,导致崩溃
盗用一张图片说明下:
在这里插入图片描述
1.在 longEar.eat()里面,(**) 行中调用了 rabbit.eat,并且this = longEar
2.然后在rabbit.eat的 (*)行中,我们希望传到原型链的下一层,但是this = longEar,所以 this .__ proto __.eat又是rabbit.eat

[[HomeObject]]

上面的问题,不能单纯的通过this来解决,还需要还需要一个特殊的内部属性[[HomeObject]]

当函数被指定为类或对象方法时,其 [[HomeObject]] 属性为该对象。
通过这个内部属性,我们就可以记住this,而不会丢失

let animal = {
  name: "Animal",
  eat() {         // [[HomeObject]] == animal
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {         // [[HomeObject]] == rabbit
    super.eat();
  }
};

let longEar = {
  __proto__: rabbit,
  name: "Long Ear",
  eat() {         // [[HomeObject]] == longEar
    super.eat();
  }
};


longEar.eat();  // Long Ear eats.

整个super的实现就完成了,有兴趣的同学可以尝试一下用babel把super转化成commonJS,也很有趣。
下面是继承过程中使用super,转化为commonJS后的部分源码

function _createSuper(Derived) { //创建super,入参Derived是子类,其中子类中的constructor是派生而来
    return function () {
        var Super = _getPrototypeOf(Derived), result;//获取子类构造函数的原型
        if (_isNativeReflectConstruct()) { //判断是否为自有属性构造,通过上面我们可知,类的继承属于派生构造
            var NewTarget = _getPrototypeOf(this).constructor;
            result = Reflect.construct(Super, arguments, NewTarget);
        }
        else { result = Super.apply(this, arguments); }
        return _possibleConstructorReturn(this, result);
    };
}

function _possibleConstructorReturn(self, call) { //可能的构造返回值
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
    }
    return _assertThisInitialized(self);
}

function _assertThisInitialized(self) { //已初始化
    if (self === void 0) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
}

function _isNativeReflectConstruct() { 
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false; 
    if (typeof Proxy === "function") return true;
    try {
        Date.prototype.toString.call(Reflect.construct(Date, [], function () { }));
        return true;
    } catch (e) { return false; }
}

function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf :
        function _getPrototypeOf(o) {
            return o.__proto__ || Object.getPrototypeOf(o);
        };
    return _getPrototypeOf(o);
}

var Rabbit = /*#__PURE__*/function (_Animal) {
    _inherits(Rabbit, _Animal);

    var _super = _createSuper(Rabbit); //创建了super

    function Rabbit(age) {
        var _this;

        _classCallCheck(this, Rabbit);

        _this = _super.call(this); //通过super来记录this,并赋值给子类的this
        _this.gae = age;
        return _this;
    }

    return Rabbit;
}(Animal);
发布了30 篇原创文章 · 获赞 6 · 访问量 4713

猜你喜欢

转载自blog.csdn.net/EcbJS/article/details/105286140