深入解读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,谢谢!

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值