ES6-Class如何優雅的進行「糖化」

這是一篇番外篇,其實他的主線是由一篇ES6-Class科普文章引起的「慘案」。雖然是番外篇,可是有些劇情仍是不容錯過的。javascript

如今咱們來進入番外篇的主線:ES6的Class是ES5構造函數的語法糖java

那仍是讓咱們簡單的迴歸一下ES5的是如何構建的:es6

//構造函數
var Circle = function(name){
     this.name = name;
    Circle.circlesMade++;
}
//定義一個類屬性
Object.defineProperty(Circle, "circlesMade", {
    get: function() {
        return !this._count ? 0 : this._count;
    },

    set: function(val) {
        this._count = val;
    }
});
//定義構造函數的prototype
Circle.prototype = {
    constructor:Circle,
    toString:function(){
        return this.name+'南蓁';
    }
}

複製代碼

雖然是封裝(OOP編程三大特色之一:封裝性)了用於表明一系列數據和操做的Circle類。可是這段代碼有些許的冗餘。也就是說,實例相關的屬性和方法,須要一坨代碼,prototype相關的也須要一坨,定義在類上的又雙叒一坨。可是寫一個類,須要照顧不少(n>3)的情緒。作一個ES5的好藍啊。編程

因此,他的彩霞仙子來了。用最優雅的方式來裝X。數組

Talk is cheap ,show you the code.bash

class Circle {
    construcotr(name){
        this.name = name;
        Circle.circlesMade++
    }
    
    static get circlesMade() {
        return !this._count ? 0 : this._count;
    };
    static set circlesMade(val) {
        this._count = val;
    };
    
    toString(){
        return this.name+'南蓁';
    }
}
複製代碼

經過對Prototype糖化處理,代碼看起來簡潔不少。babel

可是具體如何實現的呢。咱們來進行脫糖處理。函數

一口吃不成胖子,咱們對上面的代碼,進行由淺入深的分析一下。可是在分析以前,須要對JS的Class有一個基本的認識。其實JS中的Class和其餘傳統OOP語言(C++,JAVA)是不同的。是基於Prototype的類的構造和繼承的實現的。post

它的公式以下:ui

JsClass = Constructor Function + Prototype
其中Constructor Function用於定義實例中行爲(屬性+方法) Prototype用於定義實例共有的行爲

因此咱們來構建半個類,只擁有Constructor Function

Note:這裏須要解釋半個類,因爲爲了更好的解釋如何進行糖化的,就按ES5的結構類比介紹。可是須要注意的就是,在ES6class中定義的方法都是掛載prototype上的。(不包含static方法)

"半個"類

糖化代碼

class Test {
    constructor(x,y){
        this.x =x;
        this.y = y;
    }
}

複製代碼

脫糖代碼

來直接看看脫糖以後的效果。

"use strict";

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Test = function Test(x, y) {
  _classCallCheck(this, Test);

  this.x = x;
  this.y = y;
};

複製代碼

會發現這裏的僅僅比常規的Constructor Function多了一步_classCallCheck(this, Test); 其實就是多了這一步,就制約了ES6的Class不能像ES5的構造函數同樣,進行直接調用。(雖然,ES6的Classtypeof Class == 'funtion' //ture) 具體緣由能夠參照主線劇情

而後咱們繼續完善一個完整的類: 可是在此以前,咱們須要明確一點就是

class中全部的方法,都是掛載prototype

沒有static屬性的"完整"類

糖化代碼

class Test {
    constructor(x,y){
        this.x =x;
        this.y = y;
    }
    toString(){
        return '北宸'
    }
}
複製代碼

脫糖代碼

脫糖處理以後的對應代碼以下:

爲了針對重點解釋,咱們就將_classCallCheck相關代碼簡化,只留下重點代碼:

"use strict";

function _instanceof(left, right) { }
function _classCallCheck(instance, Constructor) {}

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);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var Test =
  /*#__PURE__*/
  (function() {
    function Test(x, y) {
      _classCallCheck(this, Test);

      this.x = x;
      this.y = y;
    }

    _createClass(Test, [
      {
        key: "toString",
        value: function toString() {
          return "北宸";
        }
      }
    ]);

    return Test;
  })();


複製代碼

讓咱們分析一波:

首先映入眼簾的是一個IIFE(當即執行函數/自執行匿名函數):在定義時就會執行的函數。

那這個IIFE函數被執行以後,發生了啥。咦。有一段代碼很熟悉。

function Test(x, y) {
      _classCallCheck(this, Test);

      this.x = x;
      this.y = y;
    }
複製代碼

這不就是定義了一個構造函數嗎,並對這個函數進行_classCallCheck檢查。

其實這段代碼,最神奇的地方在於_createClass這個函數的調用。

先把謎底揭曉一下,這個方法就是在爲目標構造prototype的過程。

而後咱們來參考一下實現思路:

_createClass(Test, [
      {
        key: "toString",
        value: function toString() {
          return "北宸";
        }
      }
    ]);
                ||
                ||
                ||
                \/
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}
                ||
                ||
                ||
                \/
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);
  }
}
複製代碼

其實這段代碼中,_createClass(Constructor, protoProps, staticProps)核心。在函數內部,依次對protoPropsstaticProps進行_defineProperties處理。分別目標類進行prototype靜態屬性進行設置

Note:

  1. 前面說過,ES6中定義的非static方法都是掛載prototype上的。咱們在class中定義了一個constructor(),可是這個方法被加載在protoProps數組裏面了。這是由於在ES5Function的實例中的prototype,都有一個默認屬性constructor指向構造函數。
  2. 在ES6定義的非static方法都是enumerable=false也就是說,es6中定義的方法,是不會被for(let props in obj)察覺的,而ES5中prototype是能夠的。

擁有static屬性的"完整"類

直接擼一個實例:(代碼結構仍是沿用前面的例子)

ES6糖化代碼

class Test {
    //新增static屬性 
    static _count=1;
    constructor(x,y){
        this.x =x;
        this.y = y;
    }
    toString(){
        return '北宸'
    }
    //新增static方法
    static getAllName(){
        return '北宸南蓁'
    }
}

複製代碼

note:針對如今ES6的標準,是沒法對class新增static屬性的。只有在babelstage-0/1/2/3的協助上才能夠。

脫糖代碼

咱們也會將一些上面已經解釋過的代碼,簡化:

"use strict";

function _instanceof(left, right) {}

function _classCallCheck(instance, Constructor) {}

function _defineProperties(target, props) {}

function _createClass(Constructor, protoProps, staticProps) {}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

var Test =
  /*#__PURE__*/
  (function() {
    //新增static屬性
    function Test(x, y) {
      _classCallCheck(this, Test);

      this.x = x;
      this.y = y;
    }

    _createClass(
      Test,
      [
        {
          key: "toString",
          value: function toString() {
            return "北宸";
          } //新增static方法
        }
      ],
      [
        {
          key: "getAllName",
          value: function getAllName() {
            return "北宸南蓁";
          }
        }
      ]
    );

    return Test;
  })();

_defineProperty(Test, "_count", 1);

複製代碼

這裏有幾個地方須要提醒的是:

  1. 在IIFE中的_createClass中多了第三個參數staticProps,也就是說,static方法是和prototype的信息存貯是分開的。
  2. _createClass中針對staticProps信息也是通過_defineProperties處理的。也就是說,掛載在Test上的方法也是enumerable=false。不能被for(let propsName in Class)捕獲
  3. 靜態屬性是不在IIFE的做用域中的。也就說,這個屬性是相對於prototype中的方法和屬性是全局的。這也就證明了,主線情節中,Circle能根據掛載在類上的靜態屬性(circlesMade)計算被實例化了多少次
  4. 同時掛載在類上的staticenumerable=true的。
  5. 與 ES5 同樣,類的全部實例共享一個原型對象

實例屬性的新寫法

這個特性和在class中直接static classPropsName ='value'同樣,是須要babelstage-0及以上才能夠實現。

代碼糖化

class Test {
    count=count;
	name =name;
}

複製代碼

脫糖處理

"use strict";

function _instanceof(left, right) {}

function _classCallCheck(instance, Constructor) {}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

var Test = function Test() {
  _classCallCheck(this, Test);

  _defineProperty(this, "count", count);

  _defineProperty(this, "name", name);
};

複製代碼

其實這段代碼就是在每次進行一個實例屬性的定義的時候,在脫糖以後,在構造函數中調用_defineProperty(this, key, value);進行值的綁定。

因爲是定義的是實例屬性,因此如上代碼中count/name都是在實例化的時候傳入到類中的。new Test(1,'北宸')。因此在_defineProperty中判斷if(key in obj)的時候,是true。因此,就會進行Object.defineProperty的處理。同時在實例化的時候,this==實例對象。最終的結果就是在實例對象上新增了指定的屬性。

相關文章
相關標籤/搜索