ES6 中 class 和 extends 的es5實現

Tianlikai發表於2018-12-26

ES6 中 class 和 extends 實現原理

在學習中,我們通常會遇到這種場景,在閱讀某段實現原始碼時我們往往能看懂大部分程式碼,但是卻卡在其中的一兩個點,導致無法繼續閱讀。 所以在這裡我會先列出 class 和 extends 需要的預備知識。

屬性描述符

Object.getOwnPropertyDescriptor() 方法獲取元素的屬性描述,

/**
 * 知識點:屬性描述符
 * 從 ES5 開始,所有的屬性都具備了屬性描述符
 *
 */
var obj = {
  name: "jason"
};

/**
 * getOwnPropertyDescriptor
 * 獲取屬性描述符
 */
var des = Object.getOwnPropertyDescriptor(obj, "name");
console.log(des);

/**
 * {
 *  value: 'jason',
 *  writable: true, // 可寫
 *  enumerable: true, // 可列舉
 *  configurable: true // 可配置
 * }
 */

複製程式碼

Object.defineProperty() 定義物件上屬性的一些特性


/**
 * Object.defineProperty
 * 該方法定義物件上屬性的一些特性
 *
 * 第一個引數,目標物件
 * 第二個引數,要新增或編輯的屬性
 * 第三個引數,描述
 */
var obj2 = {};
Object.defineProperty(obj2, "age", {
  value: 2,
  writable: true,
  configurable: true,
  enumerable: true
});
console.log(obj2.age); // 2


複製程式碼

writable:物件上的屬性是否可以修改。相容模式下編輯不可寫元素會編輯失敗,嚴格模式下會報錯。

var obj3 = {};
Object.defineProperty(obj3, "age", {
  value: 2,
  writable: false,
  configurable: true,
  enumerable: true
});
obj3.age = 23;
console.log(obj3.age); // 2 不可修改

複製程式碼

configurable:物件上的屬性是否可以配置

var obj4 = {};
Object.defineProperty(obj4, "age", {
  value: 22,
  writable: true,
  configurable: false, // 不可配置
  enumerable: true
});

/**
 * 再次定義
 * 此時會報錯
 */
// Object.defineProperty(obj4, "age", {
//   value: 22,
//   writable: true,
//   configurable: false,
//   enumerable: true
// });

delete obj4.age;
console.log(obj4.age); // 22 屬性刪除失敗,應為屬性不可配置

複製程式碼

enumerable: 當 enumerable 為 false 之後,for...in 不會遍歷到該物件


/**
 * enumerable
 * 當 enumerable 為 false 之後,for...in 不會遍歷到該物件
 */
var obj5 = {
  weight: "66kg"
};
Object.defineProperty(obj5, "height", {
  value: "176cm",
  writable: true,
  configurable: true,
  enumerable: false // 不可列舉
});
let keys = Object.keys(obj5);
console.log(keys); // [ 'weight' ] 屬性 height 不可遍歷

複製程式碼

prototype,constructor,__proto__ 三者之間的關係

ES6 中 class 和 extends 的es5實現

es6 中類的實現 和 es5 比較

es6 中類的實現


class Parent {
  constructor(name) {
    this.name = name;
  }

  /**
   * 靜態方法,通過 Parent 呼叫
   */
  static getMe() {
    console.log("this is super");
  }

  /**
   * 公共方法,在 prototype 上
   */
  getName() {
    console.log(this.name);
  }
}

複製程式碼

es5 實現方式


/**
 * 立即執行閉包
 * @param {*} Constructor 建構函式
 * @param {*} protoProps 原型屬性
 * @param {*} staticProps 靜態屬性
 * @return {function}
 */
var _createClass = (function() {
  /**
   * 為屬性新增描述符
   * @param {object} target 目標物件
   * @param {Array} props 屬性
   */
  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);
    }
  }
  /**
   * @param {Object} Constructor
   * @param {array} protoProps 原型屬性
   * @param {array} staticProps 靜態屬性
   */
  return function(Constructor, protoProps, staticProps) {
    // 原型上屬性, 新增到Constructor 的原型上
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    // 類上的屬性, 新增到Constructor 類上
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
})();

/**
 * 檢測函式
 * es6 中 類不能直接呼叫
 * @param {*} instance 例項
 * @param {*} Constructor 建構函式
 */
function _classCallCheck(instance, Constructor) {
  console.log(instance.__proto__);
  console.log(Constructor.prototype);

  // instance 是否是 Constructor 的例項
  console.log(instance.__proto__ === Constructor.prototype);

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

/**
 * 立即執行閉包
 * @return {function}
 */
var Parent = (function() {
  function Parent(name) {
    // 驗證是否是例項化類
    // es6不允許類直接呼叫,直接呼叫丟擲異常
    _classCallCheck(this, Parent);

    this.name = name;
  }

  _createClass(
    Parent,
    [
      {
        key: "getName",
        value: function getName() {
          console.log(this.name);
        }
      }
    ],
    [
      {
        key: "getMe",
        value: function getMe() {
          console.log("this is super");
        }
      }
    ]
  );

  return Parent;
})();

複製程式碼

es6 中繼承的實現 和 es5 比較

es6 中繼承的實現


class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  sayHello() {
    console.log(`hello my age is ${this.age}`);
  }
}


複製程式碼

es5 繼承關鍵字實現


/**
 * 繼承
 * @param {*} subClass 子類
 * @param {*} superClass 父類
 */
function _inherits(subClass, superClass) {
  // 型別檢測
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError(
      "Super expression must either be null or a function, not " +
        typeof superClass
    );
  }

  /**
   * Object.create 接受兩個引數
   * 指定原型建立物件
   * @param {*} 目標原型
   * @param {*} 新增屬性
   */
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass, // subClass.prototype.constructor 指向 subClass
      enumerable: false, // constructor 不可列舉
      writable: true,
      configurable: true
    }
  });

  /**
   * Object.setPrototypeOf 方法
   * 設定子類的 __proto__ 屬性指向父類
   * @param {*} 子類
   * @param {*} 父類
   */
  if (superClass) {
    // 設定子類的__proto__ 讓 Child 能訪問父類靜態屬性
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : (subClass.__proto__ = superClass);
  }
}

複製程式碼

es5 中繼承的實現


/**
 * 呼叫父類的 Constructor 建構函式
 */
function _possibleConstructorReturn(self, call) {
  if (!self) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return call && (typeof call === "object" || typeof call === "function")
    ? call
    : self;
}

/**
 * @param {*} _Parent 傳入父類
 */
var Child = (function(_Parent) {
  _inherits(Child, _Parent);

  function Child(name, age) {
    _classCallCheck(this, Child);

    /**
     * 呼叫父類的 Constructor 建構函式
     */
    var _this = _possibleConstructorReturn(
      this,
      (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name) // 呼叫父類Constructor
    );

    _this.age = age;
    return _this;
  }

  _createClass(Child, [
    {
      key: "sayHello",
      value: function sayHello() {
        console.log("hello my age is " + this.age);
      }
    }
  ]);

  return Child;
})(Parent);


複製程式碼

完結

以上就是 es6 class 和 extends 的 es5 實現方式,以上程式碼有一些隱藏細節還需要自學,以下是用到的知識點

  • 屬性描述符
  • Object.create 引用原型鏈需要指定 Constructor 屬性
  • new 關鍵字的實現方式 其中需要設定 __proto__ 屬性
  • Object.setPrototypeOf 實現原理 讓子類的__proto__ = 父類
  • instanceof 判斷 子類的__proto__ === 父類
  • 呼叫子類的 Constructor 也會呼叫父類的 Constructor
  • 設定子類的 __proto__ = 父類,讓子類可以呼叫父類靜態方法

相關文章