Built-in classes such as Date, Array, DOM etc cannot be properly subclassed due to limitations in ES5 (for the es2015-classesplugin). You can try to use babel-plugin-transform-builtin-extend based on Object.setPrototypeOf and Reflect.construct, but it also has some limitations.
测试:
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
console.log(arr.length) // 理想输出:1 实际输出:0
arr.length = 0;
console.log(arr[0]) // 理想输出:undefined 实际输出:12
前言
ES2015+ 有各种新特性(语法糖),尽管有很多特性尚未纳入标准或浏览器还没有原生支持,但是 Babel 的出现让前端可以不用担心兼容性问题而使用处于各种 stage 的 ES2015+ 语法。其实 class 关键字目前只是实现类的语法糖,但是可以帮助我们屏蔽掉每次实现类时的样本代码,逻辑更加清晰,并且阻止我们踩可能存在的坑,本篇文章从 ES5 的类实现到 ES6 中class 的 Babel 转码来分析类的实现与继承。
类
在 JavaScript 中,我们希望一个类能有以下特性:
Object.getPrototypeOf
能拿到constructor
的prototype
instanceof
构造函数返回 trueES5 实现类
ES5 中的类是通过 构造函数模式 + 原型模式 实现的。
以上几点都是实现了的,但是缺点就是封装性不好,样本代码多,这只是简陋版的实现,不过思路就是这样。
ES6 的 class
相当简洁了,整个类的声明都在一起,接下来我们看一下 Babel 编译出来的代码:
下面开始分析:
使用严格模式的原因阮老师在 ECMAScript 6 入门 中有解释,这里直接贴一下:
先从主函数入手
先执行构造函数,首先调用
_classCallCheck
用来确保类是通过new
作为构造函数调用而不是直接调用,如果是直接调用则直接报错。然后是在构造函数里绑定实例的属性和方法 ——
age
(直接写入类的定义的实例属性),sayAge
(直接写入类的定义的实例方法),name
(构造函数中的实例属性)。这里要注意,直接写入类的定义的实例属性/方法要先于构造函数中的实例属性/方法执行,所以如果在直接写入类的定义的实例方法中获取构造函数中定义的属性/方法,会返回undefined
。然后就是用
_createClass
,接受两个参数:类
、[绑定在类的prototype上的方法, 绑定在类上的静态方法]
,作用是把方法绑定在对应的对象上。通过
Object.defineProperty
将各个方法绑定到 类的 prototype/类 上,Object.defineProperty
可以指定对象的属性,让类的原型方法/静态方法无法被枚举。还有一点比较有意思的是,这里
_createClass
是 IIFE 的返回值,这样能做到不污染全局作用域,但是最后还是会有一个_createClass
,那是不是直接可以在声明一个 class 后调用_createClass
呢?答案当然是不可以,如果你在源代码里访问或操作_createClass
,这个默认叫_createClass
函数就会被改成_createClass2
,总之就是不让你访问到。最后,再补上一个类的静态属性就完事大吉了:
但是类的静态属性也可以写成函数表达式的形式,这样的话类的静态方法就是可以枚举的了。
继承
ES5 的继承
ES6 的继承
Babel 编译后的代码太长了 ,我们只需要关注继承的类比不继承的类多了那些功能即可:
所以一共就多了三个函数
_inherits
、_possibleConstructorReturn
和_get
先看
_inherits
这个函数完成了三个重要的任务:
至此,子类原型已近能够访问父类原型的方法了,子类也能够访问父类的静态方法。
再来看
_possibleConstructorReturn
作用是生成并返回一个调用父类的构造函数的this,再在主函数中用子类的构造函数进行加工。
再来看
_get
_get
接受三个参数,父类原型/父类,子类要 override 父类的方法,还有当前的子类实例。但是要注意,再次强调,ES6 的
class
只是用 ES5 来实现的话就只是语法糖,因为还是无法完成原生构造函数的继承。来自 Babel 的说明:
测试:
总结
到这里,Babel 编译的代码就分析完了,下面来看一下阮老师的ES6教程中的知识点,看看是不是能做到完全理解:
在子类
constructor()
中,super
指向Parent
,super
中的this
指向 Child 类的实例,所以相当于Parent.call(this)
在子类方法中,
super
指向Parent.prototype
,super
中的this
指向子类的实例,所以如果有super
调用就是Parent.prototype.func.call(this)
super
在静态方法之中指向父类,而不是父类的原型对象。在子类的静态方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类,而不是子类的实例。子类的原型指向父类
子类的 prototype 的原型指向父类的原型
吐槽
写到一半Typora崩溃了把我保存的内容都吞了是真的坑,在心态崩了的情况下再重写一次真是磨练心智 😭