在瞭解 Babel 是如何編譯 class 前,咱們先看看 ES6 的 class 和 ES5 的構造函數是如何對應的。畢竟,ES6 的 class 能夠看做一個語法糖,它的絕大部分功能,ES5 均可以作到,新的 class 寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。react
ES6 中:git
class Person { constructor(name) { this.name = name; } sayHello() { return 'hello, I am ' + this.name; } } var kevin = new Person('Kevin'); kevin.sayHello(); // hello, I am Kevin
對應到 ES5 中就是:github
function Person(name) { this.name = name; } Person.prototype.sayHello = function () { return 'hello, I am ' + this.name; }; var kevin = new Person('Kevin'); kevin.sayHello(); // hello, I am Kevin
咱們能夠看到 ES5 的構造函數 Person,對應 ES6 的 Person 類的 constructor 方法。編程
值得注意的是:類的內部全部定義的方法,都是不可枚舉的(non-enumerable)數組
以上面的例子爲例,在 ES6 中:babel
Object.keys(Person.prototype); // [] Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
然而在 ES5 中:異步
Object.keys(Person.prototype); // ['sayHello'] Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
之前,咱們定義實例屬性,只能寫在類的 constructor 方法裏面。好比:函數
class Person { constructor() { this.state = { count: 0 }; } }
然而如今有一個提案,對實例屬性和靜態屬性都規定了新的寫法,並且 Babel 已經支持。如今咱們能夠寫成:ui
class Person { state = { count: 0 }; }
對應到 ES5 都是:this
function Person() { this.state = { count: 0 }; }
全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上 static 關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。
ES6 中:
class Person { static sayHello() { return 'hello'; } } Person.sayHello() // 'hello' var kevin = new Person(); kevin.sayHello(); // TypeError: kevin.sayHello is not a function
對應 ES5:
function Person() {} Person.sayHello = function() { return 'hello'; }; Person.sayHello(); // 'hello' var kevin = new Person(); kevin.sayHello(); // TypeError: kevin.sayHello is not a function
靜態屬性指的是 Class 自己的屬性,即 Class.propName,而不是定義在實例對象(this)上的屬性。之前,咱們添加靜態屬性只能夠這樣:
class Person {} Person.name = 'kevin';
由於上面提到的提案,如今能夠寫成:
class Person { static name = 'kevin'; }
對應到 ES5 都是:
function Person() {}; Person.name = 'kevin';
值得注意的是:類必須使用 new 調用,不然會報錯。這是它跟普通構造函數的一個主要區別,後者不用 new 也能夠執行。
class Person {} Person(); // TypeError: Class constructor Foo cannot be invoked without 'new'
與 ES5 同樣,在「類」的內部可使用 get 和 set 關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。
class Person { get name() { return 'kevin'; } set name(newName) { console.log('new name 爲:' + newName) } } let person = new Person(); person.name = 'daisy'; // new name 爲:daisy console.log(person.name); // kevin
對應到 ES5 中:
function Person(name) {} Person.prototype = { get name() { return 'kevin'; }, set name(newName) { console.log('new name 爲:' + newName) } } let person = new Person(); person.name = 'daisy'; // new name 爲:daisy console.log(person.name); // kevin
至此,咱們已經知道了有關「類」的方法中,ES6 與 ES5 是如何對應的,實際上 Babel 在編譯時並不會直接就轉成這種形式,Babel 會本身生成一些輔助函數,幫助實現 ES6 的特性。
咱們能夠在 Babel 官網的 Try it out 頁面查看 ES6 的代碼編譯成什麼樣子。
ES6 代碼爲:
class Person { constructor(name) { this.name = name; } }
Babel 編譯爲:
"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person(name) { _classCallCheck(this, Person); this.name = name; };
_classCallCheck 的做用是檢查 Person 是不是經過 new 的方式調用,在上面,咱們也說過,類必須使用 new 調用,不然會報錯。
當咱們使用 var person = Person()
的形式調用的時候,this 指向 window,因此 instance instanceof Constructor
就會爲 false,與 ES6 的要求一致。
ES6 代碼爲:
class Person { // 實例屬性 foo = 'foo'; // 靜態屬性 static bar = 'bar'; constructor(name) { this.name = name; } }
Babel 編譯爲:
'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person(name) { _classCallCheck(this, Person); this.foo = 'foo'; this.name = name; }; Person.bar = 'bar';
ES6 代碼爲:
class Person { constructor(name) { this.name = name; } sayHello() { return 'hello, I am ' + this.name; } static onlySayHello() { return 'hello' } get name() { return 'kevin'; } set name(newName) { console.log('new name 爲:' + newName) } }
對應到 ES5 的代碼應該是:
function Person(name) { this.name = name; } Person.prototype = { sayHello: function () { return 'hello, I am ' + this.name; }, get name() { return 'kevin'; }, set name(newName) { console.log('new name 爲:' + newName) } } Person.onlySayHello = function () { return 'hello' };
Babel 編譯後爲:
'use strict'; 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; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function() { function Person(name) { _classCallCheck(this, Person); this.name = name; } _createClass(Person, [{ key: 'sayHello', value: function sayHello() { return 'hello, I am ' + this.name; } }, { key: 'name', get: function get() { return 'kevin'; }, set: function set(newName) { console.log('new name 爲:' + newName); } }], [{ key: 'onlySayHello', value: function onlySayHello() { return 'hello'; } }]); return Person; }();
咱們能夠看到 Babel 生成了一個 _createClass 輔助函數,該函數傳入三個參數,第一個是構造函數,在這個例子中也就是 Person,第二個是要添加到原型上的函數數組,第三個是要添加到構造函數自己的函數數組,也就是全部添加 static 關鍵字的函數。該函數的做用就是將函數數組中的方法添加到構造函數或者構造函數的原型中,最後返回這個構造函數。
在其中,又生成了一個 defineProperties 輔助函數,使用 Object.defineProperty 方法添加屬性。
默認 enumerable 爲 false,configurable 爲 true,這個在上面也有強調過,是爲了防止 Object.keys() 之類的方法遍歷到。而後經過判斷 value 是否存在,來判斷是不是 getter 和 setter。若是存在 value,就爲 descriptor 添加 value 和 writable 屬性,若是不存在,就直接使用 get 和 set 屬性。
至此,咱們已經瞭解了 Babel 是如何編譯一個 Class 的,然而,Class 還有一個重要的特性就是繼承,Class 如何繼承,Babel 又該如何編譯,歡迎期待下一篇《 ES6 系列之 Babel 是如何編譯 Class 的(下)》
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級做用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。