天天看点

深入解读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('[email protected]')

    });

聚集属性的魔法是通过@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({

         userNameBinding: Ember.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, 

       {

           //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(mixin, args) {

  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 mixin= new 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.Object= Ember.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 CoreObject= makeCtor();

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.toString= Mixin.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 superclass= Class.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(this, GUID_KEY, undefinedDescriptor);

    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 ClassMixin= Mixin.create({

  ClassMixin: Ember.required(),

  PrototypeMixin: Ember.required(),

  isClass: true,

  isMethod: false,

  extend: function() {

    var Class= makeCtor(), proto;

    Class.ClassMixin= Mixin.create(this.ClassMixin);

    Class.PrototypeMixin= Mixin.create(this.PrototypeMixin);

    Class.ClassMixin.ownerConstructor= Class;

    Class.PrototypeMixin.ownerConstructor= Class;

    reopen.apply(Class.PrototypeMixin, arguments);

    Class.superclass= this;

    Class.__super__  = this.prototype;

    proto= Class.prototype= o_create(this.prototype);

    proto.constructor= Class;

    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.PrototypeMixin, arguments);

    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.ownerConstructor= CoreObject;

CoreObject.ClassMixin = ClassMixin;

ClassMixin.apply(CoreObject);

老样子,代码还是干货,去掉了所有的注释。

在ClassMixin的代码中,我们看到了ember.js的大部分核心API,包括我们很关心的extend和create等类方法,这些方法通过Mixin的apply方法,合并到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来构造class的ClassMixin和PrototypeMixin(会设置好其层级关系,包含super class的ClassMixin和PrototypeMixin),并设置其父子关系,填充元数据信息,设置类基本的方法和属性等等。

需要注意的是class上的ClassMixin和PrototypeMixin,分别表示类级别方法和实例级别方法的列表,以及在代码中体现出来的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(this, arguments);

    this._super= sup;

    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 {

      ret= obj[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) {

        currentValue= meta.values[keyName];

      } else {

        currentValue= obj[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 ret= Ember.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(func, opts) {

  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.prototype= newEmber.Descriptor();

var ComputedPropertyPrototype= ComputedProperty.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,谢谢!

继续阅读