ES5 如何實現 Class

原文地址:https://github.com/catchonme/blog/issues/3javascript

咱們知道Class最大的做用就是可以實現繼承,這樣子類經過繼承父類,從而儘量的對代碼進行復用。java

那麼問題來了git

  • 如何實現子類繼承父類的屬性和函數,
  • 子類繼承父類時,爲何子類的同名函數不會覆蓋父類的同名函數

咱們經過分析prototype.js 來看看ES5 是如何實現Classgithub

prototype.js 書寫Class 的方法app

var Animal = Class.create({
  initialize: function(name) {
    this.name = name;
  }
})

var myAnimal = new Animal('jack'); 
console.log(myAnimal.name);// jack

var Cat = Class.create(Animal, {
  initialize: function($super, name, age) {
    $super(name);
    this.age = age;
  }
})

var myCat = new Cat('mia', 13); 
console.log(myCat.name); // mia
console.log(myCat.age); // 13
複製代碼

建立類:經過Class.create建立Classinitialize 函數初始化Class 的屬性函數

繼承:子類在Class.create 的第一個參數傳入父類名稱,在函數中第一個參數傳入$super ,函數內使用$super(args) 來調用父類的同名函數工具

而後咱們來看prototype.js 內部實現Class ,如下是大體的結構,主體是三個函數,包含一個空函數ui

var Class = (function(){
  var subclass() {}; // 空函數
  function create() { // code... } 
  function addMethods() { // code... } 
  return {
    create: create,
    Methods: {
      addMethods: addMethods
    }
  }  
})();
複製代碼

咱們挨個解析,create 函數作了什麼呢?this

function create() {
  var parent = null, properties = [].slice.call(arguments);

  // 傳入的第一個參數是不是函數,若是是,就說明是當前類的父類
  if (isFunction(properties[0]))
    parent = properties.shift();

  // 新建 klass 函數,並執行initialize 函數
  function klass() {
    this.initialize.apply(this, arguments);
  }

  // extend 函數將 Class.Methods的屬性賦給 klass
  extend(klass, Class.Methods);
  // 設定當前類爲 parent
  klass.superclass = parent;
  klass.subclasses = [];

  // 若是當前類有父類,就須要把父類的屬性和函數賦給當前類
  if (parent) {
    // 父類的 prototype 賦給 空函數 subclass.prototype
    subclass.prototype = parent.prototype;
    // 經過實例化 subclass 再賦給 klass.prototype,這樣 klass 就可以擁有父類的屬性和函數了
    klass.prototype = new subclass;
    parent.subclasses.push(klass)
  }

  // addMethods 爲將全部傳入的參數賦給 klass 的 prototype 中,該函數做用後面講解
  for (var i=0, length=properties.length; i<length; i++) {
    klass.addMethods(properties[i]);
  }

  if (!klass.prototype.initialize) {
    klass.prototype.initialize = emptyFunction
  }

  klass.prototype.constructor = klass;
  return klass
}
複製代碼
  • create 函數作了哪些事呢
    • 新建klass 函數,執行initialize 函數
    • 判斷是否傳入了父類,若是是,就將父類的屬性和函數賦給當前類
    • 使用addMethods 方法,將用戶的函數賦給klassprototype

繼承父類的做用已經實現了,用戶建立類時的方法須要經過addMethods 賦給當前類的prototype 中,接下來的問題就是,父類和子類的同名函數,該如何執行呢,父類的同名函數已經賦給子類的prototype 中了,子類增長同名函數,爲何不會覆蓋掉呢?spa

咱們看看addMethods 函數

function addMethods(source) {
  var ancestor = this.superclass && this.superclass.prototype,
      properties = Object.keys(source);

  // 遍歷傳遞過來的函數,分別賦給當前的 prototype 中
  for (var i=0, length=properties.length; i<length; i++) {
    var property = properties[i], value = source[property];

    // 判斷當前類中的函數,第一個參數否是是 $super
    if (ancestor && isFunction(value)
        && value.argumentNames()[0] == "$super") {

      var method = value;

      // 這裏是爲何子類可以執行父類的同名函數的關鍵地方
      value = (function (m) {
        return function () {
          // 經過將函數名傳遞給父類,父類執行一次該同名函數,然
          return ancestor[m].apply(this, arguments);
        }
      })(property).wrap(method); // wrap(method) 又執行一次當前類的同名函數

      // 重寫當前函數的 valueOf
      value.valueOf = (function (method) {
        return function () {
          return method.valueOf.call(method);
        }
      })(method);

      // 重寫當前函數的 toString
      value.toString = (function (method) {
        return function () {
          return method.toString.call(method);
        }
      })(method);
    }

    // 將函數賦給當前類的 prototype
    this.prototype[property] = value;
  }
}
複製代碼
  • addMethods 又作了哪些事呢?
    • 遍歷傳過來的函數
      • 若是該函數的第一個參數爲$super ,就調用父類的同名函數執行一次,在調用當前類的同名函數執行一次,這樣就可以實現使用$super 調用父類的函數了
      • 若是該函數的第一個參數不爲$super ,就直接賦給當前類的prototype

上述函數中使用的其餘工具函數,如isFunction, extend 等,由於我已經把prototype,js 中的Class 單獨剝離出來了,經過點擊 這裏 查看,裏面會有更詳細的註釋。

因此咱們回到最初的問題

  • 如何繼承父類的屬性和函數?
    • 經過中間變量實例化父函數,而後賦給子函數的prototype 中,這樣子函數就擁有父函數的屬性和函數了
  • 子類爲什麼不會覆蓋掉父類的同名函數?
    • 子函數設定屬性superclass 爲父函數,在當前類中全部自定義的函數賦給當前類的prototype 時,會經過superclass 找到父類的同名函數,這樣執行子類的同名函數時,即爲執行父類的同名函數一次,子類的同名函數一次。
相關文章
相關標籤/搜索