揭祕babel的魔法之class魔法處理

2017年,不少人已經開始接觸ES6環境,而且早已經用在了生產當中。咱們知道ES6在大部分瀏覽器仍是跑不通的,所以咱們使用了偉大的Babel來進行編譯。不少人可能沒有關心過,通過Babel編譯以後,咱們華麗的ES6代碼究竟變成了什麼樣子?javascript

這篇文章,針對Babel對ES6裏面「類class」的編譯進行分析,你能夠在線測試編譯結果,畢竟紙上得來終覺淺,本身動手,才能真正體會其中的奧祕。前端

另外,若是你還不明白JS中原型鏈等OOP相關知識,建議出門左轉找到經典的《JS高級程序設計》來補課;若是你對JS中,經過原型鏈來實現繼承一直雲裏霧裏,安利一下個人同事,前端著名網紅顏海鏡大大早在2014年的文章java

爲何使用選擇Babel

Babel:The compiler for writing next generation JavaScript;
咱們知道,如今大部分瀏覽器或者相似NodeJS的javascript引擎還不能直接支持ES6語法。但這並不構成障礙,好比Babel的出現,使得咱們在生產環境中書寫ES6代碼成爲了現實,它工做原理是編譯ES6的新特性爲老版本的ES5,從而獲得宿主環境的支持。數組

Class例子

在這篇文章中,我會講解Babel如何處理ES6新特性:Class,這實際上是一系列語法糖的實現。瀏覽器

Old school方式實現繼承

在探究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

New school方式(ES6)實現繼承

在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編譯,咱們的代碼是什麼樣呢?測試

Babel transformation

咱們一步一步來看,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。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息