You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classParent{staticnation='China'isAdult=truegetthought(){console.log('Thought in head is translate to Chinese.')returnthis._thought}setthought(newVal){this._thought=newVal}constructor(name){this.name=name}staticlive(){console.log('live')}talk(){console.log('talk')}}
classParent{staticnation='China'isAdult=truegetthought(){console.log('Thought in head is translate to Chinese.')returnthis._thought}setthought(newVal){this._thought=newVal}constructor(name){this.name=name}staticlive(){console.log('live')}talk(){console.log('talk')}}
编译后:
'use strict'// 封装后的 instanceof 操作function_instanceof(left,right){// .....}// ES6 的 class,必须使用 new 操作来调用,// 这个方法的作用就是检查是否通过 new 操作调用,使用到了上面封装的 _instanceof 方法function_classCallCheck(instance,Constructor){// ......}// 封装 Object.defineProperty 来添加属性function_defineProperties(target,props){// 遍历 propsfor(vari=0;i<props.length;i++){vardescriptor=props[i]// enumerable 默认为 falsedescriptor.enumerable=descriptor.enumerable||falsedescriptor.configurable=trueif('value'indescriptor)descriptor.writable=trueObject.defineProperty(target,descriptor.key,descriptor)}}// 为 Constructor 添加原型属性或者静态属性并返回function_createClass(Constructor,protoProps,staticProps){// 如果是原型属性,添加到原型对象上if(protoProps)_defineProperties(Constructor.prototype,protoProps)// 如果是静态属性,添加到构造函数上if(staticProps)_defineProperties(Constructor,staticProps)returnConstructor}// 封装后的 Object.definePropertyfunction_defineProperty(obj,key,value){// ......}varParent=/*#__PURE__*/(function(){// 添加 getter/setter_createClass(Parent,[{key: 'thought',get: functionget(){console.log('Thought in head is translate to Chinese.')returnthis._thought},set: functionset(newVal){this._thought=newVal}}])functionParent(name){// 检查是否通过 new 操作调用_classCallCheck(this,Parent)// 初始化 isAdult_defineProperty(this,'isAdult',true)// 根据入参初始化 namethis.name=name}// 添加 talk 和 live 方法_createClass(Parent,[{key: 'talk',value: functiontalk(){console.log('talk')}}],[{key: 'live',value: functionlive(){console.log('live')}}])returnParent})()// 初始化静态属性 nation_defineProperty(Parent,'nation','China')
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。
'use strict'// 封装后的 typeoffunction_typeof(obj){if(typeofSymbol==='function'&&typeofSymbol.iterator==='symbol'){_typeof=function_typeof(obj){returntypeofobj}}else{_typeof=function_typeof(obj){returnobj&&typeofSymbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype
? 'symbol'
: typeofobj}}return_typeof(obj)}// 调用父类的 constructor(),并返回子类的 thisfunction_possibleConstructorReturn(self,call){if(call&&(_typeof(call)==='object'||typeofcall==='function')){returncall}return_assertThisInitialized(self)}// 检查 子类的 super() 是否被调用function_assertThisInitialized(self){if(self===void0){thrownewReferenceError("this hasn't been initialised - super() hasn't been called")}returnself}// 封装后的 getPrototypeOffunction_getPrototypeOf(o){_getPrototypeOf=Object.setPrototypeOf
? Object.getPrototypeOf
: function_getPrototypeOf(o){returno.__proto__||Object.getPrototypeOf(o)}return_getPrototypeOf(o)}// 实现继承的辅助函数function_inherits(subClass,superClass){if(typeofsuperClass!=='function'&&superClass!==null){thrownewTypeError('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)}// 封装后的 setPrototypeOffunction_setPrototypeOf(o,p){_setPrototypeOf=Object.setPrototypeOf||function_setPrototypeOf(o,p){o.__proto__=preturno}return_setPrototypeOf(o,p)}// 检查是否通过 new 操作调用function_classCallCheck(instance,Constructor){if(!_instanceof(instance,Constructor)){thrownewTypeError('Cannot call a class as a function')}}varChild=/*#__PURE__*/(function(_Parent){// 继承操作_inherits(Child,_Parent)functionChild(name,age){var_this_classCallCheck(this,Child)// 调用父类的 constructor(),并返回子类的 this_this=_possibleConstructorReturn(this,_getPrototypeOf(Child).call(this,name))// 根据入参初始化子类自己的属性_this.age=agereturn_this}returnChild})(Parent)
_inherits(subClass, superClass)
我们来细看一下这个实现继承的辅助函数的细节:
function_inherits(subClass,superClass){// 1. 检查 extends 的继承目标(即父类),必须是函数或者是 nullif(typeofsuperClass!=='function'&&superClass!==null){thrownewTypeError('Super expression must either be null or a function')}// 2. 类似于 ES5 的寄生组合式继承,使用 Object.create,// 设置子类 prototype 属性的 __proto__ 属性指向父类的 prototype 属性subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor: {value: subClass,writable: true,configurable: true}})// 3. 设置子类的 __proto__ 属性指向父类if(superClass)_setPrototypeOf(subClass,superClass)}
何为
class
众所周知,
JavaScript
是没有类的,class
也只是语法糖,这篇文章旨在于理清我们常常挂着嘴边的语法糖,究竟指的是什么。ES6
与ES5
写法对比这是一个很完整的写法,我们已经习惯于这么方便地写出一个类了,那么对应到
ES5
中的写法又是如何呢可以很清晰地看到
ES6
中Parent
类的constructor
对应的就是ES5
中的构造函数Parent
;name
和isAdult
,无论在ES6
中采用何种写法,在ES5
中依然都是挂在this
下;ES6
中通过关键字static
修饰的静态属性和方法nation
和live
,则都被直接挂在类Parent
上;tought
和 方法talk
是被挂在 原型对象Parent.prototype
上的。Babel
是如何进行编译的我们可以通过将代码输入到
Babel
官网的 Try it out 来查看编译后的代码,这个部分我们循序渐进,一步一步来进行编译,拆解Babel
的编译过程:过程一
我们此时只观察 属性 相关的编译结果,
编译前:
编译后:
从编译后的代码中可以发现,
Babel
为了其严谨度,封装了一些方法,其中 可能有点迷惑的是_instanceof(left, right)
这个方法里的Symbol.hasInsance
,从 MDN 和 ECMAScript6入门 中可以知道,这个属性可以用来自定义instanceof
操作符在某个类上的行为。这里还有一个重点关注对象_classCallCheck(instance, Constructor)
,这个方法用来检查是否通过 new 操作调用。过程二
编译前:
编译后:
与过程一相比,编译后的代码,
Babel
多生成了一个_defineProperties(target, props)
和_createClass(Constructor, protoProps, staticProps)
的辅助函数,这两个主要用来添加原型属性和静态属性,并且通过Object.defineProperty
的方法,对数据描述符和存取描述符都可以进行控制。值得注意的是,
ES6
中的class
里的所有方法都是不可遍历的(enumerable: false
),这里有一个小细节: 如果有使用TypeScript
,在设置compileOptions
中的target
时,如果设置为es5
,那么会发现编译后的 方法可以通过Object.keys()
遍历到,而设置为es6
时就无法被遍历。总结
Babel
通过AST
抽象语法树分析,然后添加以下_instanceof(left, right) // 封装后的 instanceof 操作
_classCallCheck(instance, Constructor) // 检查是否通过 new 操作调用
_defineProperties(target, props) // 封装 Object.defineProperty 来添加属性
_createClass(Constructor, protoProps, staticProps) // 为 Constructor 添加原型属性或者静态属性并返回
_defineProperty(obj, key, value) // // 封装后的 Object.defineProperty
五个辅助函数,来为
Parent
构造函数添加属性和方法,转换 名为class
的语法糖为ES5
的代码。何为
extends
既然
ES6
没有类,那又应该如何实现继承呢,相信聪明的你已经知道了,其实和class
一样,extends
也是语法糖,接下来我们一步一步接着把这层语法糖也拆开。ES5
的 寄生组合式继承从 从 Prototype 开始说起(上)—— 图解 ES5 继承相关 这里知道,相对完美的继承实现是 寄生组合式继承,为了方便阅读,这里再次附上源码和示意例图:
ES6
和ES5
写法对比如果参考上面的继承实现,我们可以轻松地写出两种版本的继承形式
Babel
是如何进行编译的一些细节
编译过程
同样的,我们将代码输入到
Babel
官网的 Try it out 来查看编译后的代码:_inherits(subClass, superClass)
我们来细看一下这个实现继承的辅助函数的细节:
这个方法主要分为3步,其中第2步,通过寄生组合式继承在实现继承的同时,新增了一个名为
constructor
的不可枚举的属性;第3步实现了上文说的第二条原型链,从而达到静态方法也能被继承的效果。_possibleConstructorReturn(self, call)
这个辅助函数主要是用来实现
super()
的效果,对应到寄生组合式继承上则是借用构造函数继承的部分,有所不同的是,该方法返回一个this
并赋给子类的this
。具体细节可以在 ES6 系列之 Babel 是如何编译 Class 的(下) 查看。总结
和
class
一样,Babel
通过AST
抽象语法树分析,然后添加一组辅助函数,在我看来可以分为两类,第一类:_typeof(obj) // 封装后的 typeof
_getPrototypeOf(o) // 封装后的 getPrototypeOf
_setPrototypeOf(o, p) // 封装后的 setPrototypeOf
这种为了健壮性的功能辅助函数
第二类:
_assertThisInitialized(self) // 检查 子类的 super() 是否被调用
_possibleConstructorReturn(self, call) // 调用父类的 constructor(),并返回子类的 this
_classCallCheck(instance, Constructor) // 检查是否通过 new 操作调用
_inherits(subClass, superClass) // 实现继承的辅助函数
这种为了实现主要功能的流程辅助函数,从而实现更完善的寄生组合式继承。
后记
从 Prototype 开始说起 一共分为两篇,从两个角度来讲述 JavaScript 原型相关的内容。
参考资料
The text was updated successfully, but these errors were encountered: