從babel看class(上)

class 語法已經出來很長時間了,藉助 babel 咱們能夠在生產中使用,下面就經過一個例子來看看 babel 是怎麼處理 class 的es6

es6

下面是一段代碼,咱們分別爲實例、靜態、實例的原型添加方法和屬性數組

class Foo {
  static foo = "Foo";
  foo = "foo";
  static getName() {
    return this.foo;
  }
  getName() {
    return this.foo;
  }
  static get value() {
    return this.foo + "static";
  }
  get value() {
    return this.foo + "example";
  }
}
複製代碼

上面用到了staticstatic代表屬性或者方法添加到 Class 自己而不是實例上,下面再來對比一下 es5 的寫法babel

es5

function Foo() {
  this.name = "foo";
}
Foo.foo = "Foo";
Foo.getName = function() {
  return this.foo;
};
Foo.prototype.getName = function() {
  return this.foo;
};
Object.defineProperty(Foo, "value", {
  configurable: true,
  enumerable: true,
  get() {
    return this.foo + "static";
  }
});
Object.defineProperty(Foo.prototype, "value", {
  configurable: true,
  enumerable: true,
  get() {
    return this.foo + "example";
  }
});
複製代碼

上面咱們使用了Object.defineProperty來爲對象定義屬性,其中 getter/setter 是可選,不過在嚴格模式下中 getter/setter 必須同時出現,使用 getter/setter 後 value 和 writable 不可出現,不然報錯。函數

babel 轉碼

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

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

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 Foo =
  /*#__PURE__*/
  (function() {
    function Foo() {
      _classCallCheck(this, Foo);

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

    _createClass(
      Foo,
      [
        {
          key: "getName",
          value: function getName() {
            return this.foo;
          }
        },
        {
          key: "value",
          get: function get() {
            return this.foo + "example";
          }
        }
      ],
      [
        {
          key: "getName",
          value: function getName() {
            return this.foo;
          }
        },
        {
          key: "value",
          get: function get() {
            return this.foo + "static";
          }
        }
      ]
    );

    return Foo;
  })();

_defineProperty(Foo, "foo", "Foo");
複製代碼

轉碼過的代碼看起來有點多,不過咱們順着執行順序來看,首先var Foo =內部執行了一個_classCallCheck函數,那麼它有什麼用呢?post

其實這個函數是爲了保證 class 必須經過 new 來調用,ES6 規定 class 必須經過 new 調用,否則會顯式報錯。ui

必須爲 new 調用

判斷一個函數是否爲new調用能夠經過 instanceof 來進行判斷,例如:this

function F() {
  if (!(this instanceof F)) {
    throw new Error("必須經過new調用");
  }
}
複製代碼

在全局做用域下若是不經過 new 調用 this 爲 undefinedinstanceof與非對象對比老是返回false;es5

在 ES6 還能夠經過new.target來判斷spa

function F() {
  if (!new.target) {
    throw new Error("必須經過new調用");
  }
}
複製代碼

new.target 指向被 new 調用的構造函數,若是不存在返回 undefinedprototype

上面介紹了兩種判斷方法再來看下 babel 是怎麼處理這一過程的

  1. 將 this 和構造函數傳遞給_classCallCheck
  2. _classCallCheck將參數傳遞給_instanceof函數;

_instanceof函數負責了什麼事情呢?

其實也是判斷函數是否經過 new 調用,不過它同時對Symbol.hasInstance也進行了判斷。

Symbol.hasInstance

當其餘對象使用 instanceof 運算符,判斷是否爲該對象的實例時,會調用這個方法,

方法定義在類自己

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass(); // true
複製代碼

上面演示了一個例子,咱們書接上文看下_instanceof函數內部怎麼判斷這一過程

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

判斷構造函數存在且同時存在 Symbol.hasInstance方法,若是有就執行Symbol.hasInstance(),沒有的話就直接執行instanceof判斷。

撒花,這裏初始判斷講完,下面就是爲對象賦值

賦值

咱們繼續順着代碼看,發現調用了_defineProperty,它是爲對象輔助賦值,轉到函數內部咱們看到

if (key in obj) {
  Object.defineProperty(obj, key, {
    value: value,
    enumerable: true,
    configurable: true,
    writable: true
  });
} else {
  obj[key] = value;
}
複製代碼

不過爲何要經過key in obj來判斷呢,其實下面的_defineProperties函數也用到了這一判斷,簡單來講就是否是getter/setter就直接把 value 添加到Object.defineProperty的 value 上,不然就直接複製。

再來看代碼最後一句也調用了這個函數爲 Foo 靜態屬性賦值,對應的咱們已經走完了對應 class 的靜態和實例屬性的賦值

class Foo {
  static foo = "Foo";
  foo = "foo";
}
複製代碼

在順着代碼往下看發現調用了_createClass函數,同時還把構造函數以及咱們的靜態方法和實例方法經過數組傳遞,咱們轉到_createClass函數

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

這一步就是爲構造函數和構造函數的prototype屬性賦值,再來看_defineProperties函數

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);
  }
}
複製代碼

_defineProperties執行的步驟很簡單

  1. 遍歷數組,取到每次循環的數組下標對象;
  2. 爲對象添加enumerableconfigurable屬性描述符,注意enumerable賦值爲 false,這樣作的緣由是類的內部全部定義的方法,都是不可枚舉的;
  3. 判斷 value 存在,若是存在添加writable描述屬性
  4. 經過Object.defineProperty爲對象賦值

最後

上面把 class 轉化爲構造函數講完了,不過你是否好奇 babel 怎麼處理繼承的呢?

下一篇文章從babel看class(下)就講解這一過程,若是喜歡請點贊一下,^_^

相關文章
相關標籤/搜索