ES6 系列之 Babel 是如何编译 Class 的(上)
前言
在了解 Babel 是如何編譯 class 前,我們先看看 ES6 的 class 和 ES5 的構(gòu)造函數(shù)是如何對應(yīng)的。畢竟,ES6 的 class 可以看作一個語法糖,它的絕大部分功能,ES5 都可以做到,新的 class 寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。
constructor
ES6 中:
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對應(yīng)到 ES5 中就是:
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 的構(gòu)造函數(shù) Person,對應(yīng) ES6 的 Person 類的 constructor 方法。
值得注意的是:類的內(nèi)部所有定義的方法,都是不可枚舉的(non-enumerable)
以上面的例子為例,在 ES6 中:
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};} }然而現(xiàn)在有一個提案,對實例屬性和靜態(tài)屬性都規(guī)定了新的寫法,而且 Babel 已經(jīng)支持。現(xiàn)在我們可以寫成:
class Person {state = {count: 0}; }對應(yīng)到 ES5 都是:
function Person() {this.state = {count: 0}; }靜態(tài)方法
所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上 static 關(guān)鍵字,就表示該方法不會被實例繼承,而是直接通過類來調(diào)用,這就稱為“靜態(tài)方法”。
ES6 中:
class Person {static sayHello() {return 'hello';} }Person.sayHello() // 'hello'var kevin = new Person(); kevin.sayHello(); // TypeError: kevin.sayHello is not a function對應(yīng) ES5:
function Person() {}Person.sayHello = function() {return 'hello'; };Person.sayHello(); // 'hello'var kevin = new Person(); kevin.sayHello(); // TypeError: kevin.sayHello is not a function靜態(tài)屬性
靜態(tài)屬性指的是 Class 本身的屬性,即 Class.propName,而不是定義在實例對象(this)上的屬性。以前,我們添加靜態(tài)屬性只可以這樣:
class Person {}Person.name = 'kevin';因為上面提到的提案,現(xiàn)在可以寫成:
class Person {static name = 'kevin'; }對應(yīng)到 ES5 都是:
function Person() {};Person.name = 'kevin';new 調(diào)用
值得注意的是:類必須使用 new 調(diào)用,否則會報錯。這是它跟普通構(gòu)造函數(shù)的一個主要區(qū)別,后者不用 new 也可以執(zhí)行。
class Person {}Person(); // TypeError: Class constructor Foo cannot be invoked without 'new'getter 和 setter
與 ES5 一樣,在“類”的內(nèi)部可以使用 get 和 set 關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。
class Person {get name() {return 'kevin';}set name(newName) {console.log('new name 為:' + newName)} }let person = new Person();person.name = 'daisy'; // new name 為:daisyconsole.log(person.name); // kevin對應(yīng)到 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 為:daisyconsole.log(person.name); // kevinBabel 編譯
至此,我們已經(jīng)知道了有關(guān)“類”的方法中,ES6 與 ES5 是如何對應(yīng)的,實際上 Babel 在編譯時并不會直接就轉(zhuǎn)成這種形式,Babel 會自己生成一些輔助函數(shù),幫助實現(xiàn) ES6 的特性。
我們可以在 Babel 官網(wǎng)的 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 的方式調(diào)用,在上面,我們也說過,類必須使用 new 調(diào)用,否則會報錯。
當(dāng)我們使用 var person = Person() 的形式調(diào)用的時候,this 指向 window,所以 instance instanceof Constructor 就會為 false,與 ES6 的要求一致。
編譯(二)
ES6 代碼為:
class Person {// 實例屬性foo = 'foo';// 靜態(tài)屬性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)} }對應(yīng)到 ES5 的代碼應(yīng)該是:
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 輔助函數(shù),該函數(shù)傳入三個參數(shù),第一個是構(gòu)造函數(shù),在這個例子中也就是 Person,第二個是要添加到原型上的函數(shù)數(shù)組,第三個是要添加到構(gòu)造函數(shù)本身的函數(shù)數(shù)組,也就是所有添加 static 關(guān)鍵字的函數(shù)。該函數(shù)的作用就是將函數(shù)數(shù)組中的方法添加到構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型中,最后返回這個構(gòu)造函數(shù)。
在其中,又生成了一個 defineProperties 輔助函數(shù),使用 Object.defineProperty 方法添加屬性。
默認(rèn) enumerable 為 false,configurable 為 true,這個在上面也有強調(diào)過,是為了防止 Object.keys() 之類的方法遍歷到。然后通過判斷 value 是否存在,來判斷是否是 getter 和 setter。如果存在 value,就為 descriptor 添加 value 和 writable 屬性,如果不存在,就直接使用 get 和 set 屬性。
寫在后面
至此,我們已經(jīng)了解了 Babel 是如何編譯一個 Class 的,然而,Class 還有一個重要的特性就是繼承,Class 如何繼承,Babel 又該如何編譯,歡迎期待下一篇《 ES6 系列之 Babel 是如何編譯 Class 的(下)》
ES6 系列
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預(yù)計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標(biāo)簽?zāi)0濉⒓^函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤?#xff0c;請務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
總結(jié)
以上是生活随笔為你收集整理的ES6 系列之 Babel 是如何编译 Class 的(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: elasticsearch中文分词器ik
- 下一篇: 【对讲机的那点事】如何使用阿里通信云对讲