深入解讀ember.js的對象體系

 

write by yinmingjun,引用請註明。

 

如果需要了解ember.js框架,最好的方式是閱讀它的代碼,作爲閱讀代碼的基礎,需要了解ember.js代碼的組織形式。ember.js採用的原型法的繼承體系,並且在內部維護着類型體系的元數據,並且通過引入觀察者的模式來關注各個對象數據的變更,對屬性的維護也提供了get/set基礎服務。ember的對象體系滲透在ember.js框架的各個環節,組織形式相對複雜,我們作爲解讀ember.js的開篇,從分析ember.js的對象體系開始入手。

 

一、class和instance

 

1、class的定義

 

在ember.js中,定義一個class是通過父類的extend類方法(請允許我這麼表達,是指那些寄存在類的類型上的方法,通過類的類型來引用和調用的方法,下同)來定義的。如果沒有父類,可以從Ember.Object開始。

我們看一個例子:

    App.Person = Ember.Object.extend({
        say: function(thing) {
            alert(thing);
        }
    });

 

上面這個例子創建了一個App.Person的類,從Ember.Object派生而來。基於這種方式,我們可以從任何一個類派生出新的class。

 

在派生的類中可以override父類的方法,如果需要訪問父類的實現,可以使用特殊的this._supper()方法,調用父類的實現。

例如:

    App.Person = Ember.Object.extend({
        say: function(thing) {
             var name = this.get('name');

             alert(name + " says: " + thing);
        }
    });

 

    App.Soldier = App.Person.extend({
          say: function(thing) {
                this._super(thing + ", sir!");
          }
    });

 

2、創建instance

 

定義類之後,可以創建類的實例,在ember.js中,創建類的實例是通過調用類的create類方法來創建類的實例。

例如:

    var person = App.Person.create();
    person.say("Hello") // alerts "Hello"

 

在使用create方法創建類的實例的時候,可以傳遞一個object(就是{},一個散列對象表)作爲參數,作爲實例的屬性的初值,建議只傳遞簡單的對象,改寫實例的方法還是通過派生類的方式來做。

例如:

    Person = Ember.Object.extend({
        helloWorld: function() {
            alert("Hi, my name is " + this.get('name'));
        }
    });

 

    var tom = Person.create({
        name: "Tom Dale"
    });

 

    tom.helloWorld() // alerts "Hi my name is Tom Dale"

 

3、類實例的初始化

 

在類的實例創建之後,它的init方法(如果存在)會被自動的調用,這是一個初始化類實例的理想的場所。

例如:

    Person = Ember.Object.extend({
        init: function() {
            var name = this.get('name');
            alert(name + ", reporting for duty!");
        }
    });

    Person.create({
        name: "Stefan Penner"
    });

    // alerts "Stefan Penner, reporting for duty!"

 

使用init方法初始化類的時候,需要注意不要忘記調用父類的init方法(通過this._supper()方法),一些類的實現依賴其init方法,如Ember.View或Ember.ArrayController。

 

4、類和實例的reopen的概念

 

在ember.js中,運行類不是一次的定義完畢,運行在其它的地方追加類的定義(動態語言特有的優勢),ember.js稱呼這種方式爲reopen。

我們還是通過例子來看類的reopen。

例子:

    Person.reopen({
        isPerson: true
    });

    Person.create().get('isPerson') // true


通過class的reopen類方法,向類追加isPerson屬性,並給出true的初值,這樣,創建的類的實例馬上就可以訪問到該屬性。

 

同樣,在reopen類方法中,可以override類的方法,並使用this._supper()方法來訪問前一個版本的實現。

例如:

    PersonBase = Ember.Object.extend({
        say: function(thing) {
            alert(thing);
        }
    });

    Person = PersonBase.extend({
        say: function(thing) {
            this._super(">>>"+thing);
        }
    });

    Person.reopen({
         // override `say` to add an ! at the end
         say: function(thing) {
             this._super(thing + "!");
         }
    });

 

    Person.create().say('hello');

    //alert message : '>>>hello!'

reopen中,this._super()是前一個版本的實現,而不是父類的實現,這對改寫類的實現很有幫助。

 

與類的reopen類方法類似的還有reopenClass類方法,這個方法是擴展類型上的方法和屬性。

例如:

    Person.reopenClass({
        createMan: function() {
           return Person.create({isMan: true})
        }
    });

    Person.createMan().get('isMan') // true

 

除了方法,也可以向類型上添加屬性,直接通過類型拉訪問。

 

 

 

二、類的屬性

 

1、類屬性的訪問

 

如果需要訪問類的屬性,需要通過this.get()this.set()訪問器來操作,直接修改對象的數據成員會繞過ember.js的觀察者模式。

例如:

    var person = App.Person.create();

    var name = person.get('name');
    person.set('name'"Tobias Fünke");

 

 

2、類的計算屬性(Computed Properties)

 

有些情況下,類的屬性值是來自類的其他屬性值的計算結果,這種屬性成爲計算屬性,ember.js對計算屬性提供了支持。

例如:

    Person = Ember.Object.extend({
        // these will be supplied by `create`
        firstName: null,
        lastName: null,

        fullName: function() {
            var firstName = this.get('firstName');
            var lastName = this.get('lastName');

            return firstName + ' ' + lastName;
        }.property('firstName', 'lastName')
    });

    var tom = Person.create({
         firstName: "Tom",
         lastName: "Dale"
    });

    tom.get('fullName') // "Tom Dale"

 

計算屬性是通過function的property方法來定義的,其返回值就是屬性的訪問結果。

 

還有些情況下,還會希望計算屬性能設置屬性值,可以

    Person = Ember.Object.extend({
        // these will be supplied by `create`
        firstName: null,
        lastName: null,

        fullName: function(key, value) {
             // getter
             if (arguments.length === 1) {
                 var firstName = this.get('firstName');
                 var lastName = this.get('lastName');

                 return firstName + ' ' + lastName;

            // setter
            } else {
                var name = value.split(" ");

                this.set('firstName', name[0]);
                this.set('lastName', name[1]);

                return value;
            }
       }.property('firstName', 'lastName')
   });

 

   var person = Person.create();
   person.set('fullName', "Peter Wagenet");
   person.get('firstName') // Peter
   person.get('lastName') // Wagenet
 
也就是說,對計算屬性的set會傳遞key和value作爲參數;get僅會傳遞key作爲參數。

 

註釋1:

由於計算屬性可能帶來的計算開銷,ember.js會緩存計算結果,加速對計算屬性的訪問。emer.js通過依賴項的變更通知來維護計算屬性的緩存,因此需要通過function的property方法來登記依賴的其他屬性。

 

註釋2:

估計很多人會希望瞭解ember.js的計算屬性的魔法的細節,稍稍透漏一點,大家可以按圖索驥。

function的property方法在ember.js中的代碼如下:

    Function.prototype.property = function() {
        var ret = Ember.computed(this);
        return ret.property.apply(ret, arguments);
    };

 

3、類的聚集屬性(Aggregate Properties)

 

嚴格意義上來說,聚集屬性是計算屬性的一種,不過大多使用者還是願意將聚集屬性單獨做一個分類。 

    App.todosController = Ember.Object.create({
        todos: [
            Ember.Object.create({ isDone: false })
        ],

        remaining: function() {
             var todos = this.get('todos');
             return todos.filterProperty('isDone', false).get('length');
        }.property('todos.@each.isDone')
    });

 

聚集屬性的魔法是通過@each來描述依賴的一個屬性的數組,獲取數組元素和數組本身的變更、增減,並保持計算結果緩存的有效性。

 

三、觀察者(Observers)模式

 

在ember.js的任何對象(從Ember.Object派生而來),都可以通過其addObserver方法addBeforeObserver來添加數據變更的觀察者。ember.js類的屬性,包括計算屬性,都可以添加觀察者。

例如:

    Person = Ember.Object.extend({
        // these will be supplied by `create`
        firstName: null,
        lastName: null,

        fullName: function() {
            var firstName = this.get('firstName');
            var lastName = this.get('lastName');

            return firstName + ' ' + lastName;
        }.property('firstName', 'lastName')
    });

    var person = Person.create({
         firstName: "Yehuda",
         lastName: "Katz"
    });


    person.addObserver('fullName', function() {
         // deal with the change
    });

    person.set('firstName', "Brohuda"); // observer will fire

 

上面的代碼對person實例的fullName屬性,添加了一個觀察change的觀察者。

 

因爲觀察者在ember.js中非常常見,ember提供了針對class的觀察者的書寫方式:

    Person.reopen({
        fullNameChanged: function() {
            // this is an inline version of .addObserver
        }.observes('fullName')
    });
或者,可以藉助Ember.observer方法,將上面的代碼改寫爲:

    Person.reopen({
        fullNameChanged: Ember.observer(function() {
            // this is an inline version of .addObserver
        }, 'fullName')
    });

 

Ember.addBeforeObserver和Ember.addObserver類似,不同之處是添加變更的前事件

 

 

四、綁定(Bindings)的支持

 

在ember.js中,將綁定看成兩個屬性之間的聯動,通過觀察者來保持數據的一致性。

在ember中,綁定分爲雙向綁定和單向綁定,我們分別介紹一下。

 

 

1、雙向綁定(Two-Way Bindings)的支持

 

最簡單的創建雙向數據綁定的方式,是創建一個以Binding結尾的屬性(在ember中的形式化的文法很常見,有趣),並指定需要綁定的屬性的fullName。

例如:

    App.wife= Ember.Object.create({
        householdIncome: 80000
    });

    App.husband= Ember.Object.create({
        householdIncomeBinding'App.wife.householdIncome'
    });

    App.husband.get('householdIncome'); // 80000

    // Someone gets raise.
    App.husband.set('householdIncome', 90000);
    App.wife.get('householdIncome'); // 90000

 

上面的代碼在App.husband中創建了一個名字爲householdIncome的屬性(什麼名字都可以,以Binding結尾就可以),並建立到App.wife.householdIncome的雙向的綁定。這樣,在App.husband中操作數據和App.wife中操作數據是一樣的。

 

2、單向綁定(One-Way Bindings)的支持

 

在ember中,還存在一種綁定模式,變更只是從綁定源頭建立綁定的屬性同步變更,而不會將綁定屬性的變更同步回綁定的源頭,這種方式稱爲單向綁定。

 

在ember中使用單向綁定大多是基於性能的原因,如果沒什麼問題還是應該使用雙向的綁定。

 

例子:

    App.user = Ember.Object.create({
         fullName: "Kara Gates"
    });

    App.userView = Ember.View.create({
         userNameBindingEmber.Binding.oneWay('App.user.fullName')
    });

    // Changing the name of the user object changes
    // the value on the view.
    App.user.set('fullName', "Krang Gates");
    // App.userView.userName will become "Krang Gates"

    // ...but changes to the view don't make it back to
    // the object.
    App.userView.set('userName', "Truckasaurus Gates");
    App.user.get('fullName'); // "Krang Gates"

 

五、一些技術實現的內幕

 

在前面的介紹中,大家基本上了解了ember.js對象體系的規劃,也許會對ember提供的衆多特性會感到很興奮與好奇,想只知道how。接下來在這個章節中,我們會深入解讀ember.js源代碼,解析ember.js一些關鍵特性的實現方式,欣賞ember.js在技術領域對javascript社區的貢獻。

 

下面,我們按層次遞進,來看看ember.js的內在美。

 

1、Mixin的概念

 

先說一下ember.js的Mixin的概念:

Mixin,表示混合,有點類似AOP設計模式,或者C++中的多重繼承(Multiple Inheritance)。mixin本身只提供實現,而在類的基礎中可以在基礎的類之後添加mixin,表示引用mixin中的實現,就像類本身定義的方法一樣。

 

在ember.js中,mixin通過Ember.Mixin.create創建,並支持mixin的繼承,最終,在類的定義的時刻將mixin中的方法按次序合併到類中。

我們看看mixin的創建、繼承與多重繼承:

    Ember.Enumerable = Ember.Mixin.create({
        //code ……
    }) ;

    Ember.Array = Ember.Mixin.create(

       Ember.Enumerable, 
       /** @scope Ember.Array.prototype */ 
       {
           //code …….
       }

    ) ;

 

    var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {

        //code ……
    });

 

ember.js的mixin的方式可以很方便的複用代碼,值得推薦。

 

2、Mixin的數據結構

 

我們看看ember.js的Ember.Mixin是怎麼定義的:

Ember.Mixin= function() { return initMixin(this, arguments); };

 

也就是說,如果執行new Ember.Mixin(),就會轉到initMixin方法。

 

爲了統一行爲模式,Ember.Mixin也提供了create類型方法:

Mixin.create = function() {
  Ember.anyUnprocessedMixins = true;
  var M = this;
  return initMixin(new M()arguments);
};

 

結合前面的代碼,可以看明白,Mixin的create方法創建了一個Mixin(無參數),並調用initMixin將參數合併到mixin之中。

 

看看initMixin的代碼可以發現,如果存在參數列表,會將參數放到當前mixin的mixins數據成員之中: 

function initMixin(mixinargs) {
  if (args && args.length > 0) {
    mixin.mixins = a_map.call(args, function(x) {
      if (x instanceofMixin) { return x; }

      // Note: Manually setup a primitive mixin here. This is the only
      // way to actually get a primitive mixin. This way normal creation
      // of mixins will give you combined mixins...
      var mixinnew Mixin();
      mixin.properties = x;
      return mixin;
    });
  }
  return mixin;
}

 

注意標記出來的initMixin對非mixin類型的字典的處理方式。

 

這樣,所有的包含的mixin的對象會形成一個樹形的數據結構,關於mixin的組織結構,我們先寫到這,後面的mixins的合併,我們在對象創建的過程中來講解。

 

3、class Type的構建過程(較長)

 

這部分我們來看ember.js的繼承體系的實現方式,瞭解ember.js如何維護父子關係,以及如何創建出來第一個基礎基類。

 

在ember.js中,所有類的基類是Ember.Object,在代碼中的定義如下:

Ember.ObjectEmber.CoreObject.extend(Ember.Observable);

 

可以注意兩點:

(1) 處於簡化Object的代碼、提高源代碼整體的可讀性的目的(純粹是筆者的猜測),ember.js提供了Ember.CoreObject來封裝Object的大多功能;

(2) 在Object的級別上,ember.js就將Ember.ObservableMixin合並進來,也就是說,對於一切ember.js對象,都是可觀察的;

 

Ember.Object的大多API都寄存在Ember.CoreObject之上,我們繼續追蹤Ember.CoreObject的實現。

 

>>>>>>>>>>>>>Trace CoreObject

 

Ember.CoreObject的定義:

var CoreObjectmakeCtor();

 

CoreObject.PrototypeMixin Mixin.create({
  reopen: function() {
    applyMixin(this, arguments, true);
    return this;
  },
  isInstance: true,
  init: function() {},
  concatenatedProperties: null,
  isDestroyed: false,
  isDestroying: false,
  destroy: function() {
    if (this.isDestroying) { return; }
    this.isDestroying = true;

    schedule('actions', this, this.willDestroy);
    schedule('destroy', this, this._scheduledDestroy);
    return this;
  },
  willDestroy: Ember.K,
  _scheduledDestroy: function() {
    if (this.isDestroyed) { return; }
    destroy(this);
    this.isDestroyed = true;
  },

  bind: function(to, from) {
    if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
    from.to(to).connect(this);
    return from;
  },
  toString: function toString() {
    var hasToStringExtension = typeof this.toStringExtension === 'function',
        extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
    var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
    this.toString = makeToString(ret);
    return ret;
  }
});

 

CoreObject.PrototypeMixin.ownerConstructor CoreObject;

 

CoreObject.__super__= null;

 

原來的代碼比較長,我修整了一下,只提供乾貨,去掉了註釋,便於閱讀代碼。

 

CoreObject.PrototypeMixin的作用是提供instance級別的prototype,我們後面會講,現在先跳過去,知道有這麼一個東東就好了。

 

接下來是makeCtor方法,這個方法中創建了CoreObject類型。爲了更好的閱讀代碼,我講makeCtor的代碼分成了兩個部分。

 

makeCtor方法代碼Part I:

function makeCtor() {

  var wasApplied = false, initMixins, initProperties;

  var Class= function() {
     //code ......, see part II
  };

  Class.toStringMixin.prototype.toString;
  Class.willReopen = function() {
    if (wasApplied) {
      Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
    }

    wasApplied = false;
  };
  Class._initMixins= function(args) { initMixins = args; };
  Class._initProperties= function(args) { initProperties = args; };

  Class.proto= function() {
    var superclassClass.superclass;
    if (superclass) { superclass.proto();}

    if (!wasApplied) {
      wasApplied = true;
      Class.PrototypeMixin.applyPartial(Class.prototype);
      rewatch(Class.prototype);
    }

    return this.prototype;
  };

  return Class;

}

 

makeCtor方法的目標是從無到有創建出一個class類型,並提供了class的默認構造實現(會在new Class()的時候被調用),爲了方便閱讀,這部分代碼放在makeCtor方法代碼Part II中給出。

 

這部分代碼很明確:

> 提供class類型

> 提供class級別的_initMixins和_iniProperties方法,用於設置需要合併的mixin和初始化的參數列表

> 用於重建class.prototype的proto方法

> 提供willReopen類方法,會將當前的ProtoTypeMixin打包到一個mixin之中,很不起眼的一段代碼,卻是reopen之後可以訪問this._supper()方法的關鍵所在。

 

makeCtor方法代碼Part II:

var Class= function() {
    if (!wasApplied) {
      Class.proto();// prepare prototype...
    }
    o_defineProperty(thisGUID_KEYundefinedDescriptor);
    o_defineProperty(this, '_super', undefinedDescriptor);
    var m = meta(this);
    m.proto = this;
    if (initMixins) {
      // capture locally so we can clear the closed over variable
      var mixins = initMixins;
      initMixins = null;
      this.reopen.apply(this, mixins);
    }
    if (initProperties) {
      // capture locally so we can clear the closed over variable
      var props = initProperties;
      initProperties = null;

      var concatenatedProperties = this.concatenatedProperties;

      for (var i = 0, l = props.length; i < l; i++) {
        var properties = props[i];

        Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));

        for (var keyName in properties) {
          if (!properties.hasOwnProperty(keyName)) { continue; }

          var value = properties[keyName],
              IS_BINDING = Ember.IS_BINDING;

          if (IS_BINDING.test(keyName)) {
            var bindings = m.bindings;
            if (!bindings) {
              bindings = m.bindings = {};
            } else if (!m.hasOwnProperty('bindings')) {
              bindings = m.bindings = o_create(m.bindings);
            }
            bindings[keyName] = value;
          }

          var desc = m.descs[keyName];

          Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
          Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));

          if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
            var baseValue = this[keyName];

            if (baseValue) {
              if ('function' === typeof baseValue.concat) {
                value = baseValue.concat(value);
              } else {
                value = Ember.makeArray(baseValue).concat(value);
              }
            } else {
              value = Ember.makeArray(value);
            }
          }

          if (desc) {
            desc.set(this, keyName, value);
          } else {
            if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
              this.setUnknownProperty(keyName, value);
            } else if (MANDATORY_SETTER) {
              Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
            } else {
              this[keyName] = value;
            }
          }
        }
      }
    }
    finishPartial(this, m);
    delete m.proto;
    finishChains(this);
    this.init.apply(this, arguments);
  };

 

大段的代碼,看起來有些凌亂,就簡單的說明一下其含義吧。

 

這部分的代碼屬於class的默認構造方法,在new class()的時候被執行(這個時候已經有class的instance了,所以this會是類的實例)。

 

在這部分代碼中,主要的工作如下:

> 建立類實例的prototype;

> 合併類的mixins;

> 合併類的properties,包括構造properties的元數據和識別properties中綁定的描述;

> finishPartial中,會啓動binding的初始化,同步binding的值;

> 關閉prototype的合併;

> 調用類的實例的init方法;

 

雖然代碼量比較大,但是調理比較清晰,贊一個。

 

看到這裏,大家會比較奇怪,沒找到CoreObject上有extend方法啊?這部分代碼是託管在ClassMixin之中的。爲了追根述源,我們繼續追蹤ClassMixin的代碼。

 

>>>>>>>>>>>>>Trace ClassMixin

 

到這裏,我們已經快到達分析ember.js的類的創建過程的終點了,還是從代碼開始:

var ClassMixinMixin.create({

  ClassMixin: Ember.required(),

  PrototypeMixin: Ember.required(),

  isClass: true,

  isMethod: false,

  extend: function() {
    var ClassmakeCtor(), proto;
    Class.ClassMixinMixin.create(this.ClassMixin);
    Class.PrototypeMixinMixin.create(this.PrototypeMixin);

    Class.ClassMixin.ownerConstructor= Class;
    Class.PrototypeMixin.ownerConstructor= Class;

    reopen.apply(Class.PrototypeMixinarguments);

    Class.superclassthis;
    Class.__super__  = this.prototype;

    protoClass.prototypeo_create(this.prototype);
    proto.constructorClass;
    generateGuid(proto, 'ember');
    meta(proto).proto = proto; // this will disable observers on prototype

    Class.ClassMixin.apply(Class);
    return Class;
  },

  createWithMixins: function() {
    var C = this;
    if (arguments.length>0) { this._initMixins(arguments); }
    return new C();
  },

  create: function() {
    var C = this;
    if (arguments.length>0) { this._initProperties(arguments); }
    return new C();
  },

  reopen: function() {
    this.willReopen();
    reopen.apply(this.PrototypeMixinarguments);
    return this;
  },

  reopenClass: function() {
    reopen.apply(this.ClassMixin, arguments);
    applyMixin(this, arguments, false);
    return this;
  },

  detect: function(obj) {
    if ('function' !== typeof obj) { return false; }
    while(obj) {
      if (obj===this) { return true; }
      obj = obj.superclass;
    }
    return false;
  },

  detectInstance: function(obj) {
    return obj instanceof this;
  },

  metaForProperty: function(key) {
    var desc = meta(this.proto(), false).descs[key];

    Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
    return desc._meta || {};
  },

  eachComputedProperty: function(callback, binding) {
    var proto = this.proto(),
        descs = meta(proto).descs,
        empty = {},
        property;

    for (var name in descs) {
      property = descs[name];

      if (property instanceof Ember.ComputedProperty) {
        callback.call(binding || this, name, property._meta || empty);
      }
    }
  }

});

 

ClassMixin.ownerConstructorCoreObject;

 

CoreObject.ClassMixin = ClassMixin;
ClassMixin.apply(CoreObject);

 

老樣子,代碼還是乾貨,去掉了所有的註釋。

 

在ClassMixin的代碼中,我們看到了ember.js的大部分核心API,包括我們很關心的extendcreate等類方法,這些方法通過Mixinapply方法,合併到CoreObject上。

 

爲了便於大家分析和研究,將相關的代碼貼上:

var MixinPrototype = Mixin.prototype;

 

MixinPrototype.apply= function(obj) {
  return applyMixin(obj, [this], false);
};

 

其實Mixin的合併一般是在class的創建的過程中來做的,不過對於ClassMixin來說,是需要將Mixin合併到CoreObject類型上,提供類型的方法和屬性,因此需要自己來觸發Mixin的合併過程。

 

再回到ClassMixin的extend方法,我們可以看到extend方法的核心使用makeCtor獲取一個class type,然後就是根據當前類型(就是super class)的ClassMixin和PrototypeMixin來構造classClassMixin和PrototypeMixin(會設置好其層級關係,包含super class的ClassMixin和PrototypeMixin),並設置其父子關係,填充元數據信息,設置類基本的方法和屬性等等。

 

需要注意的是class上的ClassMixinPrototypeMixin,分別表示類級別方法實例級別方法的列表,以及在代碼中體現出來的reopen的概念,每次reopen之後,都會導致class的PrototypeMixin增加一個層級,這是ember中this._supper()方法的底層的支持上的關鍵一步。

 

ClassMixin的create方法更簡單,只是設置類型的啓動參數(這裏是initProperties),並通過new來觸發其默認構造過程的執行。

 

到這裏,對於ember.js的繼承和對象的創建過程的分析就先告一段落了,代碼很多,但是如果從表達的語義來看是比較簡單的。考慮到ember.js基於原型法的繼承方式,以及其對觀察者模式的內在支持,可以理解ember.js爲什麼會選擇這麼複雜的實現。

 

4、this._super()的實現方式

 

對ember.js的this._super()方法很喜歡,這在override類的實例方法的時候很實用。

 

前面寫的東東有點多,快到文檔字數的極限了,這部分我們就簡單點寫。

 

在ClassMixin的extend方法中,通過reopen(MixinPrototype.reopen)將class的PrototypeMixin準備就緒,之後在ClassMixin的create方法中,會觸發書寫在makeCtor方法中的類的默認的構造方法的執行。在其中,會調用makeCtor爲class準備的proto方法,proto方法中將前面整理出來的class的PrototypeMixin的內容,使用Mixin的applyPartial方法(內部會調用applyMixin方法),將其合併到class的prototype之中。具體的代碼我就不給出來了,大家自己定位和閱讀吧。

 

在mixin的applyMixin方法中,會通過mergeMixin來合併所有Mixin提供的對象屬性(次序是自頂向下的次序,先基類(或稱爲底層)的Mixin,後派生類(或稱爲上層)的Mixin),合併的目標是提供的第一個參數,這裏是當前類的prototype。而父類的方法,在extend的過程中,會通過Mixin合併到當前class的PrototypeMixin之中。

 

很明顯,在合併mixin中數據的時候,會有一個次序,已經存在的方法被看成superMethod,而接下來合併的同名的方法需要得到他的引用。ember.js是通過Ember.wrap(method, superMethod)來獲取一個支持this._supre()的包裝函數的。 我們看看其代碼:

Ember.wrap= function(func, superFunc) {
  function K() {}

  function superWrapper() {
    var ret, sup = this._super;
    this._super= superFunc || K;
    ret = func.apply(thisarguments);
    this._supersup;
    return ret;
  }

  superWrapper.wrappedFunction = func;
  superWrapper.__ember_observes__ = func.__ember_observes__;
  superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;

  return superWrapper;
};

 

這個包裝函數,將一個閉包方法返回來,並引用調用時在調用之前設置this._super,並在調用完畢之後恢復,很聰明的解法。

 

5、對象屬性的get/set過程

 

ember.js在類實例的級別提供get/set方法,用於獲取和設置對象的數據。ember.js支持計算屬性,還支持觀察者的模式,因此其對屬性的管理要複雜一些。我們簡單的看看ember.js的屬性的讀寫過程,簡單的闡述一下其工作原理。

 

>>>>>>>>>get 過程:

get= function get(obj, keyName) {
  // Helpers that operate with 'this' within an #each
  if (keyName === '') {
    return obj;
  }

  if (!keyName && 'string'===typeof obj) {
    keyName = obj;
    obj = null;
  }

  Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);

  if (obj === null || keyName.indexOf('.') !== -1) {
    return getPath(obj, keyName);
  }

  var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
  if (desc) {
    return desc.get(obj, keyName);
  } else {
    if (MANDATORY_SETTER&& meta && meta.watching[keyName] > 0) {
      ret = meta.values[keyName];
    } else {
      retobj[keyName];
    }

    if (ret === undefined &&
        'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
      return obj.unknownProperty(keyName);
    }

    return ret;
  }
};

 

有幾個層次,分別描述一下:

> 如果keyName中存在'.',通過getPath做多路徑追蹤;

> 如果存在屬性的描述,使用屬性描述中的get方法獲取屬性數據;

> 否則,從對象上獲取數據。如果數據爲undefind,並且在object上有unknownProperty方法,調用unknownProperty方法獲取屬性值;

 

>>>>>>>>>set 過程:

var set= function set(obj, keyName, value, tolerant) {
  if (typeof obj === 'string') {
    Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
    value = keyName;
    keyName = obj;
    obj = null;
  }

  if (!obj || keyName.indexOf('.') !== -1) {
    return setPath(obj, keyName, value, tolerant);
  }

  Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
  Ember.assert('calling set on destroyed object', !obj.isDestroyed);

  var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
      isUnknown, currentValue;
  if (desc) {
    desc.set(obj, keyName, value);
  } else {
    isUnknown = 'object' === typeof obj && !(keyName in obj);

    // setUnknownProperty is called if `obj` is an object,
    // the property does not already exist, and the
    // `setUnknownProperty` method exists on the object
    if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
      obj.setUnknownProperty(keyName, value);
    } else if (meta&& meta.watching[keyName] > 0) {
      if (MANDATORY_SETTER) {
        currentValuemeta.values[keyName];
      } else {
        currentValueobj[keyName];
      }
      // only trigger a change if the value has changed
      if (value !== currentValue) {
        Ember.propertyWillChange(obj, keyName);
        if (MANDATORY_SETTER) {
          if (currentValue === undefined && !(keyName in obj)) {
            Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
          } else {
            meta.values[keyName] = value;
          }
        } else {
          obj[keyName] = value;
        }
        Ember.propertyDidChange(obj, keyName);
      }
    } else {
      obj[keyName] = value;
    }
  }
  return value;
};

 

set方法考慮的東西要多一些,如下:

> 如果keyName中存在'.',通過setPath做路徑追蹤;

> 如果存在屬性的描述,通過其set方法設置屬性的值;

> 如果存在屬性的觀察者,獲取屬性的舊值,如果與新值不同,將屬性的數據存放到對象之中,並分別觸發屬性變更的前、後事件;

> 如果不存在觀察者,直接將屬性的值存放到對象上;

 

6、計算屬性的實現

 

有了前面描述的屬性描述的支持,對計算屬性的支持也就順理成章了,我們看看ember.js是怎麼做的。

 

先看看計算屬性的定義:

Person = Ember.Object.extend({
    // these will be supplied by `create`
    firstName: null,
    lastName: null,

    fullName: function() {
        var firstName = this.get('firstName');
        var lastName = this.get('lastName');

        return firstName + ' ' + lastName;
    }.property('firstName', 'lastName')
});

 

有點類似jquery的語法,寫一個函數,並調用函數的property方法返回屬性的描述符。

 

我們看property的實現:

  Function.prototype.property= function() {
    var retEmber.computed(this);
    return ret.property.apply(ret, arguments);
  };

 

該方法中,通過Ember.computed方法產生一個ComputedProperty類的實例,並將參數設置到其property之中。

 

再看Ember.computed方法:

Ember.computed= function(func) {
  var args;

  if (arguments.length > 1) {
    args = a_slice.call(arguments, 0, -1);
    func = a_slice.call(arguments, -1)[0];
  }

  if ( typeof func !== "function" ) {
    throw new Error("Computed Property declared without a property function");
  }

  var cp = new ComputedProperty(func);

  if (args) {
    cp.property.apply(cp, args);
  }

  return cp;
};

 

註釋:

從Ember. computed的代碼來看,它支持向func中攜帶參數,如果有參數,最後一個參數是func,前面的是args。

 

Ember.computed很簡單,只是創建一個ComputedProperty類的實例,並返回它。

 

ComputedProperty類的代碼較多,我們只貼一下必要的代碼:

function ComputedProperty(funcopts) {
  this.func = func;

  this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
  this._dependentKeys = opts && opts.dependentKeys;
  this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
}

Ember.ComputedProperty= ComputedProperty;
ComputedProperty.prototypenewEmber.Descriptor();

var ComputedPropertyPrototypeComputedProperty.prototype;

 

ComputedPropertyPrototype.property= function() {
  var args = [];
  for (var i = 0, l = arguments.length; i < l; i++) {
    args.push(arguments[i]);
  }
  this._dependentKeys = args;
  return this;
};

ComputedProperty採用的是原型法的繼承方式,直接指定prototype爲new Ember.Descriptor(),因此毫無疑問是Descriptor。其定義了Descriptor的get、set等方法,用戶控制屬性值的獲取和設置(代碼就不給了),還有提供了volatile和readOnly方法,用來控制計算屬性的結果是否緩存,以及是否運行set這個計算屬性的值。

 

 

六、小結

 

這裏,對ember.js的對象體系結構的描述該結束了,下面對上面描述的ember的核心功能做一個小小的彙總:

> 集成到Ember.Object上的觀察者的模式,允許觀察每個屬性值的變更;

> 通過Mixin提供類型無關的屬性和實現,支持AOP的設計模式;

> 提供extend、create等類方法,支持類的派生和類實例的創建;

> 提供reopen的模式,允許單個類在多處定義;

> 提供this._super()方法,可以訪問到被override的方法;

> 提供屬性描述符(Ember.Descriptor),支持計算屬性、別名等複雜屬性;

> 對數據綁定的內在支持;

> 提供get/set方法(accessor),接管類的屬性值的設置&獲取請求;

 

 

我對ember.js展示的恢宏的體系結構非常欣賞,很喜歡ember.js的架構師設計ember的方式,也對ember源代碼中的優美實現感到很欣喜。讀好的代碼和讀一本好書的感覺類似,優秀的架構師給其注入靈魂和主線;而優美的代碼會讓每個細節充滿了美感。讀ember的代碼是很快樂的過程,希望ember將來會給我更多的驚喜。

 

ember,謝謝!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章