前言
- 本文对class与extends核心源码方法进行分析,并指出es6 class extends与es5 寄生组合继承的不同点。
- 本文源码来源于Babel。
- 本文为原创,未经同意请勿转载。
class 源码分析
1 2 3 4 5 6 7 8 9
| class Person{ constructor(name,age){ this.name = name this.age = age } eating(){ console.log(this.name + "eating~") } }
|
将以上代码放到Babel,得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var Person = (function () { function Person(name, age) {
_classCallCheck(this, Person);
this.name = name; this.age = age; } _createClass(Person, [ { key: "eating", value: function eating() { console.log(this.name + "eating~"); } } ]);
return Person; })();
|
/*#__PURE__*/
是纯函数标识。
- 立即执行函数执行完后,var Person指向了内部声明的Person。
- 有两个核心方法,分别为_classCallCheck和_createClass。
_classCallCheck(instance, Constructor)
这个方法判断是否是new调用,若不是,throw new TypeError(‘…’)抛出语法错误
1 2 3 4 5
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
- 传入的两个实参为,当前调用栈的this和构造函数(这里是Person)。
- 当执行new Person()时,当前调用栈的
this.__proto__
指向Person.prototype
,所以此时的this instanceof Person为true。
- 当执行Person(),当前调用栈的
this
指向window
,所以此时的this instanceof Person为false。
_createClass(Constructor, protoProps, staticProps)
这个方法为构造函数和其原型绑定属性,并将prototype的writable设为false。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
- 这个方法接受
Constructor
构造函数(Person),protoProps
构造函数原型上的描述符数组,staticProps
构造函数上的描述符数组。
_defineProperties(Constructor.prototype, protoProps);
:为原型绑定protoProps
。
_defineProperties(Constructor, staticProps);
:为构造函数绑定staticProps
。
protoProps
和staticProps
中的Prop
是带有key
的属性描述符或存取描述符。
_defineProperties(target, props)
这个方法是Object.defineProperty的封装,迭代props并写入到target。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
es6 class 与new的区别
_classCallCheck
对类的调用方式进行了判断,如果不为new调用,会提示TypeError。
class extends 源码分析
1 2 3 4 5 6 7 8 9
| class Student extends Person{ constructor(name,age,sno){ super(name,age); this.sno = sno; } running(){ console.log(this.name + "stu runnig~") } }
|
将以上代码放到Babel,得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var Student = function (_Person) { _inherits(Student, _Person);
var _super = _createSuper(Student);
function Student(name, age, sno) { var _this;
_classCallCheck(this, Student);
_this = _super.call(this, name, age); _this.sno = sno; return _this; }
_createClass(Student, [{ key: "running", value: function running() { console.log(this.name + "stu runnig~"); } }]);
return Student; }(Person);
|
- 这是个立即执行函数,内部的Student就是外部的Student。
- _classCallCheck和_createClass前面已经分析过,接下来分析_inherits和_createSuper两个核心方法。
_inherits(subClass, superClass)
- 将
subClass.prototype
的[[prototype]]
指向superClass.prototype
- 将
subClass
的[[prototype]]
指向了superClass
- 将
subClass.prototype.constructor
指向subClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
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 } }); Object.defineProperty(subClass, "prototype", { writable: false });
if (superClass) _setPrototypeOf(subClass, superClass); }
|
- 这个方法接受
subClass
子类构造函数(Student),superClass
父类构造函数(Person)。
if (typeof superClass !== "function" && superClass !== null)
判断父类类型是否为function或null,若不是则抛出错误。
subClass.prototype = Object.create(superClass.prototype)
与寄生式继承一样,创建一个[[prototype]]
指向superClass.prototype
的对象并赋值给subClass.prototype
。
Object.create
第二个参数将subClass.prototype.constructor
指向subClass
。
- 这里出现的方法
_setPrototypeOf
其实是Object.setPrototypeOf的兼容性封装,将subClass
的[[prototype]]
指向了superClass
。目的是class的static方法的原型继承。
_setPrototypeOf(o, p)
Object.setPrototypeOf的兼容性封装
1 2 3 4 5 6 7 8 9
| function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
_createSuper(Derived)
返回一个借用父类构造绑定了属性的对象,使用Reflect.constructor
支持es6 class的new.target
属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived),result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
- 这个方法接收
Derived
子类构造函数,返回的方法_createSuperInternal
就是外部的_super
。
_isNativeReflectConstruct()
返回是否支持内置方法Reflect.construct
的布尔值。
_getPrototypeOf(o)
是Object.getPrototypeOf
的兼容性封装,因为前面分析了_inherits
通过_setPrototype
设置子类的[[Prototype]]
指向父类,所以这里_getPrototypeOf(Derived)
获取到子类构造函数(Student)的[[Prototype]]
指向Person。
_createSuperInternal()
由_super.call(this,name,age)
调用时,call传入的this
就是new Student执行时当前调用栈的this,注意这里使用了_getPrototypeOf(this).constructor
而不使用Derived
,原因是前面设置了子类构造函数的constructor
属性可以修改writable: true
。
result = Reflect.construct(Super, arguments, NewTarget)
借用了Super
父类的构造函数并将属性绑定到result。result = Super.apply(this, arguments)
借用了Super
父类的构造函数并将属性绑定到this(new Student()当前调用栈的this)。
- 接下来分析_possibleConstructorReturn方法。
_isNativeReflectConstruct()
判断是否支持内置方法Reflect.construct
1 2 3 4 5 6 7 8 9 10 11 12 13
| function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call( Reflect.construct(Boolean, [], function () {}) ); return true; } catch (e) { return false; } }
|
_getPrototypeOf(o)
Object.getPrototypeOf
的兼容性封装。
1 2 3 4 5 6 7 8
| function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
_possibleConstructorReturn(self, call)
这个方法从self和call选择被父类构造函数绑定属性的那个值。
1 2 3 4 5 6 7 8 9 10
| function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError( "Derived constructors may only return object or undefined" ); } return _assertThisInitialized(self); }
|
- 这里的
_typeof
其实是针对Symbol类型对typeof做了额外处理,_typeof(call)
可以理解为typeof call
。
- 联系前面的内容,
self
值对应new Student()当前调用栈的this
,call
对应上文result
。
- 当
call
为object或function类型,那么call会作为_super.call(this,...args)
最终返回值。
_assertThisInitialized(self)
是判断self
值是否为undefined,若不是,则返回self
。
_assertThisInitialized(self)
判断self
值是否为undefined,若不是,则返回self
,若是则,抛出super()未调用错误。
1 2 3 4 5 6 7 8
| function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ); } return self; }
|
总结
es6 class其实本质还是es5的寄生组合继承,区别主要在于Object.setPrototypeOf,new.target
等兼容性的处理及声明方式,函数调用方式,class extends super()
等语法的不同。
es6 class 的特点
_classCallCheck
对类的调用方式进行了判断,如果不为new调用,会提示TypeError。
- class声明经过babel编译后是函数表达式,与function函数声明的预解析规则不同。
es6 class extends 与 es5寄生组合继承的区别
- subClass的
[[prototype]]
指向superClass
,以实现static
方法继承。
- 使用
_this
代替this
,当未调用_this=_super.call(this,...args)
时,_this
为undefined,最终通过_assertThisInitialized(self)
抛出super()
未调用错误。
- 对借用构造函数模式进行了额外的处理,使用
Reflect.constructor
支持es6 class的new.target
属性。
为什么使用_this代替this?
- 利于对super()是否已经调用的判断,若使用this,this为引用类型,也就无法通过_assertThisInitialized(self) 抛出错误。
- Function.prototype.apply(call)在非new调用时不支持es6的
new.target
属性。
参考资料