利用babel完全搞懂es6的Class類(一)

前言

工做中有不少用到Class類的地方,一般是暴露出class類,或者暴露Class類的實例,好比商品有價格、規格、名稱等屬性,就能夠經過new一個Class類來初始化商品對象。程序員

對es6有了解的朋友應該知道es6是es5的語法糖,Class類其實是基於es5的構造函數實現的生成實例對象的方法,可是Class類的寫法更優雅,更趨近於傳統的面向對象編程。es6

雖然說平時經常使用Class類,但是也常會忽略掉細節而致使問題,出於好奇和爲了完全理解Class類,我利用babel工具降級es6語法,看看Class類的「廬山真面目」,以及理解babel到底作了什麼,寫文記錄以供本身複習。編程

一個Class類babel先後

定義一個Class類數組

class K {
    constructor(name) {
        this.name = name;
    }
    // 靜態方法
    static classMethod() {
        this.getname()
        return 'hello';
    }
    // setter
    set prop(value) {
        console.log('setter: '+ value);
    }
    // getter
    get prop() {
        return 'getter';
    }
    // 原型方法
    getName() {
        return "celeste";
    }
}

let k = new K("celeste")

上面的類利用babel降級語法後代碼以下:babel

var K = function () {
  function K(name) {
    _classCallCheck(this, K);
    
    this.name = name;
  }

  _createClass(K, [{
    key: 'getName',
    value: function getname() {
      return "celeste";
    }
  }, {
    key: 'prop',
    set: function set(value) {
      console.log('setter: ' + value);
    },
    get: function get() {
      return 'getter';
    }
  }], [{
    key: 'classMethod',
    value: function classMethod() {
      this.getName();
      return 'hello';
    }
  }]);

  return K;
}();

var k = new K("celeste");

能夠發現Class類本質上是個自執行函數。這個函數執行完畢返回一個構造函數K。
而且,這裏定義函數不是用函數聲明的形式,而是用變量聲明賦值var K這其實就是class類不存在變量提高的緣由,由於雖然js函數會先掃描整個函數體語句,將全部聲明的變量提高到函數的頂部,可是不會提高賦值,在console前變量K還未賦值因此打印結果是undefined。函數

// 變量賦值
console.log(Bb); // undefined
var Bb = function Bb () {};
// 函數聲明
console.log(Aa); // ƒ Aa () {}
function Aa () {};
_classCallCheck、_createClass函數

看完外層,再看看裏面的關鍵信息,主要看_classCallCheck、_createClass他們作了什麼,源碼以下:工具

"use strict";
// 爲了向前兼容,es6語法其實是嚴格模式的
// 類or模塊中只有嚴格模式可用

// 判斷right是否爲left的構造函數
function _instanceof(left, right) { 
    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { 
        return !!right[Symbol.hasInstance](left); 
    } else { 
        return left instanceof right; 
    } 
}

// 判斷Constructor是否instance的構造函數,若是不是則拋出錯誤
function _classCallCheck(instance, Constructor) {
    if (!_instanceof(instance, Constructor)) { 
        throw new TypeError("Cannot call a class as a function"); 
    } 
}

// 遍歷props,設置props裏每一項的屬性並掛載到target上
function _defineProperties(target, props) { 
    for (var i = 0; i < props.length; i++) { 
        var descriptor = props[i]; 
        // 定義是否可枚舉(否)
        descriptor.enumerable = descriptor.enumerable || false; 
        // 定義是否可刪除(可)
        descriptor.configurable = true; 
        // descriptor有value屬性的話(即除了set/get外的原型方法),可賦值
        if ("value" in descriptor) descriptor.writable = true; 
        // 將變量descriptor.key定義到target上
        Object.defineProperty(target, descriptor.key, descriptor); 
    } 
}

// 參數分別是:構造函數、原型方法、靜態方法
function _createClass(Constructor, protoProps, staticProps) { 
    // 原型方法掛載到構造函數的原型上
    if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
    // 靜態方法(用了static關鍵字定義的函數)會做爲第三個參數數組裏的項傳進來,會直接成爲構造函數下的一個屬性
    if (staticProps) _defineProperties(Constructor, staticProps); 
    return Constructor; 
}

因此constructor事實上就是初始化了一個構造函數:this

function K(name) {
    _classCallCheck(this, K);
    
    this.name = name;
}

_classCallCheck(this, K)的做用就是判斷K是否爲this的構造函數,不是的話拋出錯誤,確保萬無一失(依據是若是K是個構造函數那麼this必定是指向K的實例對象的)。es5

而_createClass函數的做用是就是將定義在類裏的方法掛載到函數的原型(針對原型方法)或者類自己(針對static靜態方法)上:prototype

把_createClass函數與函數調用直觀地放在一塊兒看:

// _createClass函數
function _createClass(Constructor, protoProps, staticProps) { 
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps); 
    return Constructor; 
}
  // 語法降級後自執行函數裏的函數執行
  _createClass(K, [{
    key: 'getName',
    value: function getname() {
      return "celeste";
    }
  }, {
    key: 'prop',
    set: function set(value) {
      console.log('setter: ' + value);
    },
    get: function get() {
      return 'getter';
    }
  }], [{
    key: 'classMethod',
    value: function classMethod() {
      this.getName();
      return 'hello';
    }
  }]);

能夠看到,setter、getter也在第二個參數數組裏,他們也是原型上的方法,傳參時有些許不一樣,value —— set/get,是爲了在掛載到原型上的時候加以區分的,把他們區分開的代碼就是_defineProperties函數裏的這句話:

if ("value" in descriptor) descriptor.writable = true;
一些結論:

1.類的全部方法都定義在類的prototype屬性上面

因此類的新方法能夠利用`Object.assign`添加在`prototype`對象上面
Object.assign(Person.prototype, {
  // add some functions ...
});

2.類的內部全部定義的方法,都是不可枚舉的(non-enumerable)
3.js引擎會自動爲空的類添加一個空的constructor方法(事實上就是會默認建立一個構造函數,將構造函數的this指向類的實例)
4.constructor函數能夠return Object.create(null)返回一個全新的對象,可致使實例對象不是類的實例。

下一節再看剩餘的問題啦,這兩天總結完畢或許會合併成一篇也可能新開一篇...——2020/06/06 01:20

小貼士:語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能並無影響,可是更方便程序員使用。一般來講使用語法糖可以增長程序的可讀性,從而減小程序代碼出錯的機會。

babel在線工具 https://babeljs.io/repl

相關文章
相關標籤/搜索