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。