本文探究ES6class
语法糖的一些行为及babel实现。
PS:关于ES5继承的细节不在本文讨论范围
PPS:最好有ES5的面向对象知识
what the hell is class
class
是一个语法糖,好不好吃就要看各位的口味了,毕竟甲之蜜糖乙之砒霜。
既然是语法糖,那么就可以用现有的语言特性实现它(就是ES5的原型式继承),本文中我们直接分析babel编译出的ES5代码。
坠简单滴例子
1 | class Person { |
额,经过babel编译出来好大一坨玩意儿。
1 | ; |
太长不看右上角!
别急啊大爷,我带你一点点看。
首先最下面Person
的变量就是是我们的Person
类,它其实是一个由IIFE返回的函数。
1 | function Person(name) { |
很眼熟吧,这个函数其实就是我们在ES5中的构造函数。
就是构造函数里多了点什么东西->_classCallCheck
。
为什么要有_classCallCheck
ES5中构造函数就是一个普通的函数,因此可以直接调用,只不过与通过new
关键字调用有不同的行为,而ES标准要求class
只能通过new
调用,因此需要一个验证机制,在不当调用时报错。
1 | // _classCallCheck |
_classCallCheck
函数很简单,接收两个参数,然后用instanceof
检测instance
是不是Constructor
的实例,如果不是就报错。
当通过new
调用时,Person
内部的this
为新创建的实例(也就是说this
的__proto__
已经指向了Person
的prototype
),此时能过通过检测。
当Person
作为函数调用时就会报错。
可以了解一下ES6的
new.target
实例的静态属性
常写React
的童鞋应该很熟悉这种写法
1 | class App extends PureComponent { |
那么这种写法与我们在constructor
中定义的属性有什么区别吗?
我们将代码修改如下,注释处是修改的地方。
1 | class Person { |
打包出来的代码如下
1 | function Person(name) { |
emmmm,好像并没有什么卵区别。
其实确实没有什么卵区别。
按我的理解,所谓的静态实例属性,就是指不能在新建对象时指定值的属性,因而称为静态。
那么既然是实例属性,那么它必然是定义在实例上,并没有什么魔法。
其实这也是有些人不喜欢语法糖的原因:遮盖了实现的细节,容易让人产生疑惑。
_createClass是什么鬼???
在ES5中,我们将属性放在对象中,将方法放在原型中。
也就是定义了构造函数后再去修改构造函数的prototype
。
但是这样写其实很烦人。。。
上面我们看到Person
函数就是定义好的构造函数,而_createClass
处理构造函数,向原型和构造函数本身添加方法:
- 实例方法:向
Person.prototype
添加sayHi
方法。 - 静态方法:向
Person
函数添加sayGoodbye
方法,称为静态方法。
看不懂的去补JS基础。。。
1 | // _createClass |
先看_createClass
的签名
Constructor
:就是我们的构造函数Person
protoProps
:是要放到原型上的方法(sayHi
)staticProps
是要放到Person
函数的静态方法(sayGoodbye
)。
类声明中,
static
关键字表示该方法是类的静态方法。
_createClass
接着用defineProperties
函数分别处理了Person
和它的prototype
。
也就是ES5中需要手动处理的步骤。
再来看_createClass
是怎么调用的
1 | _createClass(Person, [{ |
可以发现,这些方法已经不是函数了,而是一个个属性描述符对象,这些对象被交给了defineProperties
函数处理。
这又是为什么呢?
defineProperties
用于写入属性,为什么要有这一步呢?为什么不能直接把方法写入要搞的这么复杂呢?
其实还是ES标准的原因。
ES规定class
内部定义的所有方法都是不可枚举的,也就是说Object.keys(Person.prototype)
不会返回sayHi
方法。
defineProperties
用于修改属性描述符,并写入目标对象。
这样就与标准的规定一致了。
至此我们的Person
类已经被处理好了。
实例化一个类试试。
1 | const jack = new Person('jack') |
确实如我们预想的一样。
再看看Person
类
1 | console.dir(Person) |
可以看到Person
确实是一个函数,sayGoodbye
确实是该函数的一个属性。
What’s more?
为了解决this
指向丢失的问题,我们常常采用下面这种写法,但是很多人并不知道到底发生了什么。。。
其实写这文章的原因就是群里有人提问实例静态属性和
constructor
中初始化的属性的区别。。。万恶的语法糖!
1 | class Person { |
经过了上面的分析,我们可以看出来,这其实就是一个静态实例属性,而this
指向问题是用箭头函数解决的。
下面是babel编译出的代码
1 | function Person(name) { |
下面是实例的属性
可以看到,确实如我们预料的一样,waveArm
方法并不在原型中,而是作为实例的属性。
只不过babel用闭包实现了箭头函数的行为。
一点看法
个人还是挺喜欢这个语法糖的,毕竟少写很多代码。。。
其实实现继承并不一定要用class
或者ES5那一套做法,使用Object.create
一样可以做到,而且更加简洁。。。
不过都得对原型实现面向对象这一套东西有深入了解。
毕竟万变不离其宗。
再说了,函数式大法好!要啥面相对象(手动滑稽
下一篇文章讲extend
关键字干了什么。。。
EOF