2017年,不少人已经开始接触ES6环境,而且早已经用在了生产当中。咱们知道ES6在大部分浏览器仍是跑不通的,所以咱们使用了伟大的Babel来进行编译。不少人可能没有关心过,通过Babel编译以后,咱们华丽的ES6代码究竟变成了什么样子?javascript
这篇文章,针对Babel对ES6里面“类class”的编译进行分析,你能够在线测试编译结果,毕竟纸上得来终觉浅,本身动手,才能真正体会其中的奥秘。前端
另外,若是你还不明白JS中原型链等OOP相关知识,建议出门左转找到经典的《JS高级程序设计》来补课;若是你对JS中,经过原型链来实现继承一直云里雾里,安利一下个人同事,前端著名网红颜海镜大大早在2014年的文章java
Babel:The compiler for writing next generation JavaScript;
咱们知道,如今大部分浏览器或者相似NodeJS的javascript引擎还不能直接支持ES6语法。但这并不构成障碍,好比Babel的出现,使得咱们在生产环境中书写ES6代码成为了现实,它工做原理是编译ES6的新特性为老版本的ES5,从而获得宿主环境的支持。数组
在这篇文章中,我会讲解Babel如何处理ES6新特性:Class,这实际上是一系列语法糖的实现。浏览器
在探究ES6以前,咱们先来回顾一下ES5环境下,咱们如何实现类的继承:安全
// Person是一个构造器 function Person(name) { this.type = 'Person'; this.name = name; } // 咱们能够经过prototype的方式来加一条实例方法 Person.prototype.hello = function() { console.log('hello ' + this.name); } // 对于私有属性(Static method),咱们固然不能放在原型链上了。咱们能够直接放在构造函数上面 Person.fn = function() { console.log('static'); };
咱们能够这么应用:babel
var julien = new Person('julien'); var darul = new Person('darul'); julien.hello(); // 'hello julien' darul.hello(); // 'hello darul' Person.fn(); // 'static' // 这样会报错,由于fn是一个私有属性 julien.fn(); //Uncaught TypeError: julien.fn is not a function
在ES6环境下,咱们固然火烧眉毛地试一试Class:函数
class Person { constructor(name) { this.name = name; this.type="person" } hello() { console.log('hello ' + this.name); } static fn() { console.log('static'); }; }
这样写起来固然很cool,可是通过Babel编译,咱们的代码是什么样呢?测试
咱们一步一步来看,this
Step1: 定义
咱们从最简单开始,试试不加任何方法和属性的状况下,
Class Person{}
被编译为:
function _classCallCheck(instance, Constructor) { // 检查是否成功建立了一个对象 if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person() { _classCallCheck(this, Person); };
你可能会一头雾水,_classCallCheck是什么?其实很简单,它是为了保证调用的安全性。
好比咱们这么调用:
// ok var p = new Person();
是没有问题的,可是直接调用:
// Uncaught TypeError: Cannot call a class as a function Person();
就会报错,这就是_classCallCheck所起的做用。具体原理本身看代码就行了,很好理解。
咱们发现,Class关键字会被编译成构造函数,因而咱们即可以经过new来实现实例的生成。
Step2:Constructor探秘
咱们此次尝试加入constructor,再来看看编译结果:
class Person() { constructor(name) { this.name = name; this.type = 'person' } }
编译结果:
var Person = function Person(name) { _classCallCheck(this, Person); this.type = 'person'; this.name = name; };
看上去棒极了,咱们继续探索。
Step3:增长方法
咱们尝试给Person类添加一个方法:hello:
class Person { constructor(name) { this.name = name; this.type = 'person' } hello() { console.log('hello ' + this.name); } }
编译结果(已作适当省略):
// 如上,已经解释过 function _classCallCheck.... // MAIN FUNCTION var _createClass = (function () { 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var Person = (function () { function Person(name) { _classCallCheck(this, Person); this.name = name; } _createClass(Person, [{ key: 'hello', value: function hello() { console.log('hello ' + this.name); } }]); return Person; })();
Oh...no,看上去有不少须要消化!不要急,我尝试先把他精简一下,并加上注释,你就会明白核心思路:
var _createClass = (function () { function defineProperties(target, props) { // 对于每个定义的属性props,都要彻底拷贝它的descriptor,并扩展到target上 } return defineProperties(Constructor.prototype, protoProps); })(); var Person = (function () { function Person(name) { // 同以前... } _createClass(Person, [{ key: 'hello', value: function hello() { console.log('hello ' + this.name); } }]); return Person; })();
若是你不明白defineProperty方法, 请参考这里
如今,咱们知道咱们添加的方法:
hello() { console.log('hello ' + this.name); }
被编译为:
_createClass( Person, [{ key: 'hello', value: function hello() { console.log('hello ' + this.name); } }]);
而_createClass接受2个-3个参数,分别表示:
参数1 => 咱们要扩展属性的目标对象,这里其实就是咱们的Person 参数2 => 须要在目标对象原型链上添加的属性,这是一个数组 参数3 => 须要在目标对象上添加的属性,这是一个数组
这样,Babel的魔法就一步一步被揭穿了。
但愿这篇文章可以让你了解到Babel是如何初步把咱们ES6 Class语法编译成ES5的。下一篇文章我会继续介绍Babel如何处理子类的Super(), 并会经过一段函数桥梁,使得ES5环境下也可以继承ES6定义的Class。