ember.js提供的基础服务介绍

write by yinmingjun,引用请注明。

 

ember.js作为一个全功能的javascript的MVC框架,需要维护大量的代码,基于write less use more的原则,ember.js不可避免的抽象了大量的基础服务,以便最大程度的复用代码。而对于希望读懂ember.js代码的开发人员,这部分代码可能会成为理解ember.js的设计意图的障碍。因此,笔者在开始描述ember.js的特性的细节之前,选择对ember.js中的基础代码先做一个梳理,并相信理解这部分代码,对用好ember.js会有很大的帮助。

 

在这部分代码中,也包含ember.js对javascript语言潜力的挖掘,会看到很多javascript擅长的表述方式,对那些喜欢极致编程的开发人员,将会是一个很好的体验过程。

 

下面我们按种类来介绍ember.js的基础服务。

 

一、对调试的支持

 

在开发的过程中,一般有限会考虑调试支持相关的API,这是解决生产效率的关键要素。

 

在ember.js中,有下面的调试API:

 

> Ember.assert 方法

 

定义

Ember.assert = function(desc, test) {
     if (!test) throw new Error("assertion failed: "+desc);
};

描述

在代码中设置断言。如果断言不是true,就抛出异常,中止程序的运行。

Ember.assert在开发版的ember.js中,是断言方法,在release版中,会被替换成空方法(与编译系统中的assert类似),也就是说,Ember.assert是for开发的诊断工具。

 

参数

    desc      string类型,是传递给Ember.assert的诊断描述

    test       bool类型,如果为false,会引发断言的异常

 

> Ember.warn方法

 

定义

Ember.warn = function(message, test) {
  if (!test) {
    Ember.Logger.warn("WARNING: "+message);
    if ('trace' in Ember.Logger) Ember.Logger.trace();
  }
};

 

描述

与Ember.assert类似,不过不像Ember.assert会抛出异常并中止程序的运行,Ember.warn仅产生LOG。Ember.warn也是for开发的支持工具,在release版的ember.js中会被替换成空方法。

 

参数

message     string类型,是需要输出的警告信息

test             bool类型,如果为false,会引发输出警告的信息

 

> Ember.debug方法

 

定义

Ember.debug = function(message) {
  Ember.Logger.debug("DEBUG: "+message);
};

 

描述

在代码中输出调试信息。在release版中,会被替换成空方法。

 

参数

message    string类型,是需要输出的调试信息

 

> Ember.deprecate 方法

 

定义

Ember.deprecate = function(message, test) {

    //code .......

};

 

描述

与Ember.warn类似,将message会输出到logger的warn级别,Ember.deprecate与Ember.warn差别是Ember.deprecate还会提供调用栈的信息。在release版中,会被替换成空方法。

 

参数

message    string类型,是传递给Ember.deprecate的诊断信息描述

test             bool类型,如果为false,会输出警告信息到logger

 

> Ember.deprecateFunc方法

 

定义

Ember.deprecateFunc = function(message, func) {
  return function() {
    Ember.deprecate(message);
    return func.apply(this, arguments);
  };
};

 

描述

返回一个包装函数,用来包装对参数中func的调用。

Ember.deprecateFunc用来产生API的废弃的调用说明,并将对废弃的API重定向到新的API。

例如,下面的Ember.none方法被Ember.isNone替代了,如果继续调用Ember.none会产生警告信息:

     Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
在release版中,Ember.deprecateFunc的警告信息被移除,只保留API的重定向功能。

 

参数

message    string类型,是需要输出的警告信息

func           function类型,是重定向的目标函数

 

 

二、LOG支持

 

ember.js通过Ember.Logger接口来提供LOG的支持,Ember.Logger的定义如下:

 

Ember.Logger的定义

 

Ember.Logger= {
  log:   consoleMethod('log')   || Ember.K,
  warn:  consoleMethod('warn')  || Ember.K,
  error: consoleMethod('error') || Ember.K,
  info:  consoleMethod('info')  || Ember.K,
  debug: consoleMethod('debug') || consoleMethod('info') || Ember.K
};

 

Ember.Logger的描述

 

Ember.Logger的规则很简单,只是将信息输出到imports.console,在浏览器是输出到全局console函数之中。

替代Ember.Logger,只需要将其几个方法重定向就OK了,就默认实现来说,对大多数人来说以及足够用了,不过在大型系统中可能需要对Logger做集成。

 

三、对象复制&合并

 

在ember.js依赖的jquery中存在类似的服务,不过ember.js还是提供了,可能是因为对象复制的服务太基础了。在javascript的简单数据对象(POJO)的操作中,常见的操作是合并与复制,其中复制还可能需要做深度克隆。

 

ember.js通过Ember.merge方法支持对象的合并,通过Ember.copy支持对象的复制。

 

> Ember.merge方法

定义

Ember.merge = function(original, updates) {
    for (var prop in updates) {
        if (!updates.hasOwnProperty(prop)) { continue; }
        original[prop] = updates[prop];
    }
    return original;
};

 

描述

Ember.merge将updates中的内容合并到original对象之中。如果original中已经存在同名的属性,会被updates中的内容覆盖。

 

参数

original     object类型,是需要合并的目标

updates    object类型,是合并的数据来源

 

 

> Ember.copy方法

定义

Ember.copy = function(obj, deep) {
    // fast paths
    if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
    if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
    return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
};

 

描述

Ember.copy将复制obj中的内容,并返回克隆的对象。如果存在deep参数,并且其值为true,那么会做深度的复制。

注意,如果obj不是对象,或者是null,

合并到original对象之中。如果original中已经存在同名的属性,会被updates中的内容覆盖。

 

参数

obj     object类型,是需要复制的对象

deep  bool类型,可忽略,如果为true表示是深度复制

 

 

四、代码的调度&执行

 

> Ember.onLoad方法

定义

Ember.onLoad= function(name, callback) {
    var object;

    loadHooks[name] = loadHooks[name] || Ember.A();
    loadHooks[name].pushObject(callback);

    if (object = loaded[name]) {
        callback(object);
    }
};

 

描述

ember.js支持对onload队列的挂接,用于支持各个生命周期中的就绪事件的底层处理。和与Ember.onLoad方法配合的是Ember.runLoadHooks方法,用于执行一个名字对应的队列中所有的callback方法。如果队列以及执行过了,传入的callback会立即被执行。

在ember.js中,有'Ember.Handlebars'、'application'、'Ember.Application'等几个内部的队列。

 

参考Ember.runLoadHooks方法:

Ember.runLoadHooks= function(name, object) {
    loaded[name] = object;

    if (loadHooks[name]) {
        forEach.call(loadHooks[name], function(callback) {
            callback(object);
        });
    }
};

 

 

参数

name      string类型,是load队列的名字

callback funtion类型,是希望放到load队列中的callback方法 

 

 

> Ember.run方法

定义

Ember.run= function(target, method) {
  var ret;

  if (Ember.onerror) {
    try {
      ret = backburner.run.apply(backburner, arguments);
    } catch (e) {
      Ember.onerror(e);
    }
  } else {
    ret = backburner.run.apply(backburner, arguments);
  }

  return ret;
};

 

描述

Ember.run方法执行指定对象的方法。ember.js提供Ember.run方法的主要目的是可以获取到方法执行前后的事件,可以对绑定、变更等事件立即做出响应,还可以拦截代码运行过程中的异常,并将异常传递给Ember.onerror的全局的错误的handler中。

 

ember.js通过backburner来封装对运行的支持。

 

参数

target   object类型,可能为null,是method所在的对象。

methodfunction类型,target上的方法;或string类型,是target成员的名称。

args       后续的所有参数,可省略,将在调用时传递给method。

 

> Ember.run.join方法

定义

Ember.run.join= function(target, method) {
  if (!Ember.run.currentRunLoop) {
    return Ember.run.apply(Ember.run, arguments);
  }

  var args = slice.call(arguments);
  args.unshift('actions');
  Ember.run.schedule.apply(Ember.run, args);
};

 

描述

如果不存在Ember.run.currentRunLoop的时候与Ember.run方法相同,如果有Ember.run.currentRunLoop就会将要执行的方法放到RunLoop的'actions'队列中执行。 

 

说明

解释一下,Ember.run方法每次执行的时候都会建立一个DeferredActionQueues的实例,作为执行的上下文,将当前的Ember.run.currentRunLoop的实例推入栈中保存起来,并在执行完毕之后恢复Ember.run.currentRunLoop的值,也就是说,Ember.run.currentRunLoop总是代表当前的Ember.run方法的执行上下文。

 

参数

(同Ember.run方法)

 

> Ember.run.begin和Ember.run.end方法

定义

Ember.run.begin= function() {
  backburner.begin();
};

Ember.run.end= function() {
  backburner.end();
};

 

描述

是手工开启/关闭ember.js的RunLoop的底层调用的API,一般使用的模式如下:

  Ember.run.begin();
  // code to be execute within a RunLoop
  Ember.run.end();

 

> Ember.run.schedule方法

定义

Ember.run.schedule= function(queue, target, method) {
  checkAutoRun();
  backburner.schedule.apply(backburner, arguments);
};

描述

Ember.run.schedule方法将指定对象的方法传递到指定的调度队列中执行。

ember.js提供了'sync', 'actions', 'render', 'afterRender','destroy'五个队列,三个队列的执行次序与定义次序相同,sync最先,actions居中,destroy最后。ember.js的调度队列在每次Ember.run方法的end期间执行(完callback之后)。

 

Ember.run.schedule方法一般是在ember.js内部使用。

 

参数

 

queue     string类型,是'sync', 'actions', 'render', 'afterRender','destroy'中的一个,队列的执行次序与定义次序相同,sync最先,actions居中,destroy最后

target     object类型,可以为null,是method所在的对象。

method  function类型,target上的方法;或string类型,是target成员的名称。

args        后续的所有参数,可省略,将在调用时传递给method。

 

 

 

> Ember.run.scheduleOnce方法

定义

Ember.run.scheduleOnce= function(queue, target, method) {
     checkAutoRun();
     return backburner.scheduleOnce.apply(backburner, arguments);
};

 

描述

Ember.run.schedule方法左右类似,差别是在语义上保证只调度执行一次(无论使用Ember.run.scheduleOnce添加调度项目几次)

 

注意

使用匿名函数的时候要小心,因为每次执行的时候都返回返回不同的函数,在比较上会认为调度项目是不同的方法。如:

     Ember.run.scheduleOnce('actions', myContext,function(){ console.log("Closure"); });

参数

(参考Ember.run.schedule方法)

 

 

> Ember.run.once方法

定义

Ember.run.once= function(target, method) {
    checkAutoRun();
    var args = slice.call(arguments);
    args.unshift('actions');
    return backburner.scheduleOnce.apply(backburner, args);
};

 

描述

Ember.run.scheduleOnce的便捷方法,将提供的对象、方法调度到'actions'队列执行。

 

参数

target     object类型,可以为null,是method所在的对象。

method  function类型,target上的方法;或string类型,是target成员的名称。

args         后续的所有参数,可省略,将在调用时传递给method。

> Ember.run.later方法

定义

Ember.run.later= function(target, method) {
    return backburner.later.apply(backburner, arguments);
};

描述

延迟执行指定的方法,与setTimeout作用类似。调用的DEMO如下:

Ember.run.later(myContext, function(){
    // code here will execute within a RunLoop in about 500ms with this == myContext
  },500);

规定,最后一个参数是延迟的时间,单位是ms(毫秒)

 

参数

target        object类型,可以为null,是method所在的对象。

method    function类型,target上的方法;或string类型,是target成员的名称。

args          后续的所有参数,可省略,将在调用时传递给method。

delay     最后一个参数是延迟的毫秒数。

> Ember.run.next方法

定义

Ember.run.next= function() {
    var args = slice.call(arguments);
    args.push(1);
    return backburner.later.apply(backburner, args);
};

描述

 是Ember.run.later的便捷方法,表示延迟执行方法。包装的later中提供的延迟时间是1ms。

 

参数

target     object类型,可以为null,是method所在的对象。

method   function类型,target上的方法;或string类型,是target成员的名称。

args         后续的所有参数,可省略,将在调用时传递给method。

 

> Ember.run.cancel方法

定义

Ember.run.cancel= function(timer) {
  return backburner.cancel(timer);
};

 

描述

取消Ember.run.later()或Ember.run.next()指定的延迟方法的执行,提供的timer参数应该是Ember.run.later()或Ember.run.next()的返回值。

 

参数

timer    object类型,应该是Ember.run.later()或Ember.run.next()的返回值。

 

> Ember.run.cancelTimers方法

定义

Ember.run.cancelTimers= function () {
  backburner.cancelTimers();
};

 

描述

取消所有的延迟方法的执行,一般用于ember.js运行环境的清理。

 

参数

 

五、对事件体系的支持

 

ember.js通过Ember.Evented这个Mixin对事件体系提供支持,而Ember.Evented又被混合到Ember.CoreView之中,也就是说所有的view都能使用Ember.Evented提供的方法。

 

> Ember.Evented Mixin

定义

Ember.Evented = Ember.Mixin.create({

    on: function(name, target, method) {
    Ember.addListener(this, name, target, method);
    return this;
  },

  one: function(name, target, method) {
    if (!method) {
      method = target;
      target = null;
    }

    Ember.addListener(this, name, target, method, true);
    return this;
  },

  trigger: function(name) {
    var args = [], i, l;
    for (i = 1, l = arguments.length; i < l; i++) {
      args.push(arguments[i]);
    }
    Ember.sendEvent(this, name, args);
  },

  fire: function(name) {
    Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead.");
    this.trigger.apply(this, arguments);
  },

  off: function(name, target, method) {
    Ember.removeListener(this, name, target, method);
    return this;
  },

  has: function(name) {
    return Ember.hasListeners(this, name);
  }
});

 

描述

简单的说一下。

 

on方法:

用于设置event的handler,name对应事件的名称,target和method合起来描述handler。target参数是可以省略的。

 

one方法:

与on方法基本一致,差别是注册的handler执行过之后就自动remove,也就是说只响应一次事件。

trigger方法

触发事件,需要给出需要触发事件的名称,后面是传递给handler的参数。

 

fire方法

已经废弃,被trigger方法取代了。

 

off方法

从事件的监听者中去掉target+method对应的处理者。target参数是可以省略的。

 

has方法

获取指定的事件是否存在监听者。

 

 

五、对标准对象的扩展

 

ember.js对javascript的运行环境做了很多补充,我们简单看一下。

 

> 对string的扩充

定义

Ember.String = {

  fmt: function(str, formats) {
    // first, replace any ORDERED replacements.
    var idx  = 0; // the current index for non-numerical replacements
    return str.replace(
/%@([0-9]+)?/g, function(s, argIndex) {
      argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
      s = formats[argIndex];
      return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
    }) ;
  },

  loc: function(str, formats) {
    str = Ember.STRINGS[str] || str;
    return Ember.String.fmt(str, formats) ;
  },

w: function(str) { return str.split(/\s+/); },

  decamelize: function(str) {
    return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
  },

  dasherize: function(str) {
    var cache = STRING_DASHERIZE_CACHE,
        hit   = cache.hasOwnProperty(str),
        ret;

    if (hit) {
      return cache[str];
    } else {
      ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
      cache[str] = ret;
    }

    return ret;
  },

  camelize: function(str) {
    return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
      return chr ? chr.toUpperCase() : '';
    }).replace(/^([A-Z])/, function(match, separator, chr) {
      return match.toLowerCase();
    });
  },

classify: function(str) {
    var parts = str.split("."),
        out = [];

    for (var i=0, l=parts.length; i<l; i++) {
      var camelized = Ember.String.camelize(parts[i]);
      out.push(camelized.charAt(0).toUpperCase() + camelized.substr(1));
    }

    return out.join(".");
  },

  underscore: function(str) {
    return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
      replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
  },

  capitalize: function(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  }

};

 

  String.prototype.fmt = function() {
    return fmt(this, arguments);
  };

  String.prototype.w = function() {
    return w(this);
  };

  String.prototype.loc = function() {
    return loc(this, arguments);
  };

  String.prototype.camelize = function() {
    return camelize(this);
  };

  String.prototype.decamelize = function() {
    return decamelize(this);
  };

  String.prototype.dasherize = function() {
    return dasherize(this);
  };

  String.prototype.underscore = function() {
    return underscore(this);
  };

  String.prototype.classify = function() {
    return classify(this);
  };

  String.prototype.capitalize = function() {
    return capitalize(this);
  };

 

描述

简单的说一下。

fmt方法:

提供字符串格式化的支持。fmt通过提' %@'形式参数来提供格式化的支持,每个形式参数' %@'按照其出现的位置对应实际参数数组中对应的参数,如果后面有数字,则表示使用指定位置的实际参数。例如:

    "Hello%@%@".fmt('John', 'Doe');     // "Hello John Doe"
    "Hello%@2,%@1".fmt('John', 'Doe');  // "Hello Doe, John"

loc方法:

提供本地化的支持。通过在Ember.STRINGS中检索资源字符串来格式化字符串。第一个参数是Ember.STRINGS中资源的key值,第二个参数与fmt的第二个参数相同。例如:

    Ember.STRINGS = {
      '_Hello World': 'Bonjour le monde',
      '_Hello %@ %@': 'Bonjour %@ %@'
    };

    Ember.String.loc("_Hello World");  // 'Bonjour le monde';
    Ember.String.loc("_Hello %@ %@", ["John", "Smith"]);  // "Bonjour John Smith";

 

w方法:

提供字符串根据空格分词的功能。

 

decamelize方法:

字符串去骆驼化,在大小写的分界线上添加'_',并将大写转换成小写,并去掉首字母的大写。例如:

'innerHTML'.decamelize(); // 'inner_html'
'action_name'.decamelize(); // 'action_name'
'css-class-name'.decamelize(); // 'css-class-name'
'my favorite items'.decamelize(); // 'my favorite items'

 

dasherize方法:

字符串短线化。字符串先去骆驼化,然后将空格和'_'转换成'-'。例如:

'innerHTML'.dasherize(); // 'inner-html'
'action_name'.dasherize(); // 'action-name'
'css-class-name'.dasherize(); // 'css-class-name'
'my favorite items'.dasherize(); // 'my-favorite-items'

 

camelize方法:

字符串小骆驼化。将'_'、空格、'-'等分隔符去掉,并将连接处后面的字符转换成大写,最后将首字母小写,形成小骆驼形字符串。例如:

'innerHTML'.camelize(); // 'innerHTML'
'action_name'.camelize(); // 'actionName'
'css-class-name'.camelize(); // 'cssClassName'
'my favorite items'.camelize(); // 'myFavoriteItems'
'My Favorite Items'.camelize(); // 'myFavoriteItems'

 

classify方法:

 字符串大骆驼化,除了小骆驼化之外,还将首字母大写。例如:

    'innerHTML'.classify();          // 'InnerHTML'
    'action_name'.classify();        // 'ActionName'
    'css-class-name'.classify();     // 'CssClassName'
    'my favorite items'.classify();  // 'MyFavoriteItems'

underscore方法:

字符串下划线化。就是将空格、大小写分界处、'-'等替换成下划线分割,并转换成小写。例如:

    'innerHTML'.underscore();          // 'inner_html'
    'action_name'.underscore();        // 'action_name'
    'css-class-name'.underscore();     // 'css_class_name'
    'my favorite items'.underscore();  // 'my_favorite_items'

capitalize方法:

将字符串的首字母转换成大写。

 

> 对Function的扩充

定义

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

  Function.prototype.observes = function() {
    this.__ember_observes__ = a_slice.call(arguments);
    return this;
  };

  Function.prototype.observesBefore = function() {
    this.__ember_observesBefore__ = a_slice.call(arguments);
    return this;
  };

 

描述

property方法:

用来支持ember.js中的计算属性的书写。

 

observes方法:

用来支持ember.js中的属性变更的观察者的书写。

 

observesBefore方法:

用来支持ember.js中的属性变更的前事件的书写。

 

> 对Array的扩充

定义

// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
vararrayMap= isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun/*,thisp*/) {
  //"use strict";

  if (this === void 0 || this === null) {
    throw new TypeError();
  }

  vart= Object(this);
  varlen= t.length>>> 0;
  if (typeof fun !== "function") {
    throw new TypeError();
  }

  varres= new Array(len);
  varthisp= arguments[1];
  for (var i = 0; i < len; i++) {
    if (i in t) {
      res[i] =fun.call(thisp,t[i],i,t);
    }
  }

  returnres;
};

// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
vararrayForEach= isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun/*,thisp*/) {
  //"use strict";

  if (this === void 0 || this === null) {
    throw new TypeError();
  }

  vart= Object(this);
  varlen= t.length >>> 0;
  if (typeof fun !== "function") {
    throw new TypeError();
  }

  varthisp= arguments[1];
  for (var i = 0; i < len; i++) {
    if (i in t) {
      fun.call(thisp, t[i], i, t);
    }
  }
};

vararrayIndexOf= isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj,fromIndex) {
  if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
  else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
  for (var i = fromIndex, j = this.length; i < j; i++) {
    if (this[i]===obj) { return i; }
  }
  return -1;
};

Ember.ArrayPolyfills= {
   map: arrayMap,
  forEach: arrayForEach,
   indexOf: arrayIndexOf
};

 

描述

由于可能和javascript语言的标准冲突,对Array的map、forEach和indexOf的扩充没有直接填充到Ember.ArrayPolyfills对象之中。对这几个方法简短的说明一下。

 

map方法:

对每个数组的元素调用回调函数来获取结果,并返回结果的数组。map的第一个参数是callback方法,第二个参数如果存在,会传递到callback作为this的值(call方法会将第一个参数作为this参数)。callback的参数依次是:当前的数组元素、数组元素的索引、数组本身,返回值作为map的结果,保存在map函数维护的返回值的数组中,在map结束后作为map的返回值返回。

 

forEach方法:

map方法类似,只是不会收集callback的返回值。

 

indexOf方法:

获取指定对象在数组中的索引,第二个参数可选,用于指定检索的起始位置。

 

> Ember.Enumerable Mixin

定义

Ember.Enumerable=Ember.Mixin.create({

  // compatibility
isEnumerable: true,

  nextObject: Ember.required(Function),

firstObject:Ember.computed(function() {
    if (get(this, 'length')===0) return undefined ;

    // handle generic enumerables
    varcontext=popCtx(), ret;
    ret = this.nextObject(0, null, context);
   pushCtx(context);
    return ret ;
  }).property('[]'),

lastObject:Ember.computed(function() {
    var len = get(this, 'length');
    if (len===0) return undefined ;
    varcontext=popCtx(), idx=0, cur, last = null;
    do {
      last = cur;
      cur = this.nextObject(idx++, last, context);
    } while (cur !== undefined);
   pushCtx(context);
    return last;
  }).property('[]'),

contains: function(obj) {
    return this.find(function(item) { return item===obj; }) !== undefined;
  },

forEach: function(callback,target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = get(this, 'length'), last = null,context= popCtx();

    if (target === undefined) target = null;

    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last,context) ;
     callback.call(target, next, idx, this);
      last = next ;
    }
    last = null ;
    context = pushCtx(context);
    return this ;
  },

getEach: function(key) {
    return this.mapProperty(key);
  },

setEach: function(key, value) {
    return this.forEach(function(item) {
      set(item, key, value);
    });
  },

  map: function(callback,target) {
    varret=Ember.A([]);
    this.forEach(function(x, idx, i) {
      ret[idx] =callback.call(target,x,idx,i);
    });
    return ret ;
  },

mapProperty: function(key) {
    return this.map(function(next) {
      return get(next, key);
    });
  },

  filter: function(callback, target) {
    var ret = Ember.A([]);
    this.forEach(function(x, idx, i) {
      if (callback.call(target, x, idx, i)) ret.push(x);
    });
    return ret ;
  },

reject: function(callback, target) {
    return this.filter(function() {
      return !(callback.apply(target, arguments));
    });
  },

filterProperty: function(key, value) {
    return this.filter(iter.apply(this, arguments));
  },

rejectProperty: function(key, value) {
    var exactValue = function(item) { return get(item, key) === value; },
        hasValue = function(item) { return !!get(item, key); },
        use = (arguments.length === 2 ? exactValue : hasValue);

    return this.reject(use);
  },

find: function(callback, target) {
    var len = get(this, 'length') ;
    if (target === undefined) target = null;

    var last = null, next, found = false, ret ;
    var context = popCtx();
    for(var idx=0;idx<len && !found;idx++) {
      next = this.nextObject(idx, last, context) ;
      if (found = callback.call(target, next, idx, this)) ret = next ;
      last = next ;
    }
    next = last = null ;
    context = pushCtx(context);
    return ret ;
  },

findProperty: function(key, value) {
    return this.find(iter.apply(this, arguments));
  },

every: function(callback, target) {
    return !this.find(function(x, idx, i) {
      return !callback.call(target, x, idx, i);
    });
  },

everyProperty: function(key, value) {
    return this.every(iter.apply(this, arguments));
  },

some: function(callback, target) {
    return !!this.find(function(x, idx, i) {
      return !!callback.call(target, x, idx, i);
    });
  },

someProperty: function(key, value) {
    return this.some(iter.apply(this, arguments));
  },

reduce: function(callback, initialValue, reducerProperty) {
    if (typeof callback !== "function") { throw new TypeError(); }

    var ret = initialValue;

    this.forEach(function(item, i) {
      ret = callback.call(null, ret, item, i, this, reducerProperty);
    }, this);

    return ret;
  },

invoke: function(methodName) {
    var args, ret = Ember.A([]);
    if (arguments.length>1) args = a_slice.call(arguments, 1);

    this.forEach(function(x, idx) {
      var method = x && x[methodName];
      if ('function' === typeof method) {
        ret[idx] = args ? method.apply(x, args) : method.call(x);
      }
    }, this);

    return ret;
  },

toArray: function() {
    var ret = Ember.A([]);
    this.forEach(function(o, idx) { ret[idx] = o; });
    return ret ;
  },

compact: function() {
    return this.filter(function(value) { return value != null; });
  },

without: function(value) {
    if (!this.contains(value)) return this; // nothing to do
    var ret = Ember.A([]);
    this.forEach(function(k) {
      if (k !== value) ret[ret.length] = k;
    }) ;
    return ret ;
  },

uniq: function() {
    var ret = Ember.A([]);
    this.forEach(function(k){
      if (a_indexOf(ret, k)<0) ret.push(k);
    });
    return ret;
  },

'[]':Ember.computed(function(key, value) {
    return this;
  }),

  // ..........................................................
  // ENUMERABLE OBSERVERS
  //

addEnumerableObserver: function(target, opts) {
    var willChange = (opts && opts.willChange) || 'enumerableWillChange',
        didChange  = (opts && opts.didChange) || 'enumerableDidChange';

    var hasObservers = get(this, 'hasEnumerableObservers');
    if (!hasObservers) Ember.propertyWillChange(this,'hasEnumerableObservers');
    Ember.addListener(this,
'@enumerable:before', target, willChange);
    Ember.addListener(this,'@enumerable:change', target, didChange);
    if (!hasObservers) Ember.propertyDidChange(this,'hasEnumerableObservers');
    return this;
  },

removeEnumerableObserver: function(target, opts) {
    var willChange = (opts && opts.willChange) || 'enumerableWillChange',
        didChange  = (opts && opts.didChange) || 'enumerableDidChange';

    var hasObservers = get(this,'hasEnumerableObservers');
    if (hasObservers) Ember.propertyWillChange(this,'hasEnumerableObservers');
    Ember.removeListener(this,
'@enumerable:before', target, willChange);
    Ember.removeListener(this,'@enumerable:change', target, didChange);
    if (hasObservers) Ember.propertyDidChange(this,'hasEnumerableObservers');
    return this;
  },

hasEnumerableObservers:Ember.computed(function() {
    return Ember.hasListeners(this,'@enumerable:change') || Ember.hasListeners(this,'@enumerable:before');
  }),


enumerableContentWillChange: function(removing, adding) {

    var removeCnt, addCnt, hasDelta;

    if ('number' === typeof removing) removeCnt = removing;
    else if (removing) removeCnt = get(removing, 'length');
    else removeCnt = removing = -1;

    if ('number' === typeof adding) addCnt = adding;
    else if (adding) addCnt = get(adding,'length');
    else addCnt = adding = -1;

    hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;

    if (removing === -1) removing = null;
    if (adding   === -1) adding   = null;

    Ember.propertyWillChange(this, '[]');
    if (hasDelta) Ember.propertyWillChange(this, 'length');
    Ember.sendEvent(this,, [this, removing, adding]);

    return this;
  },

enumerableContentDidChange: function(removing, adding) {
    var removeCnt, addCnt, hasDelta;

    if ('number' === typeof removing) removeCnt = removing;
    else if (removing) removeCnt = get(removing, 'length');
    else removeCnt = removing = -1;

    if ('number' === typeof adding) addCnt = adding;
    else if (adding) addCnt = get(adding, 'length');
    else addCnt = adding = -1;

    hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;

    if (removing === -1) removing = null;
    if (adding   === -1) adding   = null;

    Ember.sendEvent(this,'@enumerable:change', [this, removing, adding]);
    if (hasDelta) Ember.propertyDidChange(this, 'length');
    Ember.propertyDidChange(this, '[]');

    return this ;
  }

}) ;

 

描述

Ember.EnumerableMixin提供基础的可枚举能力,是ember.js对于可枚举特性的规划,并在其中提供了对枚举集合的观察者模式,是ember.js中很基础的一个组件。

 

我们简单的描述一下它的各个成员。

 

isEnumerable属性:

是Ember.Enumerable的存在性的标记。

 

nextObject方法:

是实现可枚举特性的基础方法,用于获取下一个对象,这个由实现类提供实现。对nextObject方法的调用很简单:

this.nextObject(idx, last,context) ;

 idx是迭代的索引,从0开始依次累加;last是上次调用nextObject返回值;context被枚举对象用于维护枚举状态,返回值是当前的枚举值。

 

firstObject计算属性:

用于返回枚举中的第一个对象,访问属性会导致枚举状态的变化,影响nextObject方法的返回值。

 

lastObject计算属性:

用于返回枚举中的最后一个对象,访问属性会导致枚举状态的变化,影响nextObject方法的返回值。

 

contains方法:

判断集合中是否包含指定的对象。

 

forEach方法:

用于对遍历集合中的所有元素,并对每个元素调用callback方法。第二个target参数是callback的this上下文参数的值,回调函数的描述如下:

    function(item, index, enumerable);
item 是当前的枚举值;index是枚举的索引;enumerable是当前的可枚举对象;

 

getEach方法:

mapProperty的别名。

 

setEach方法:

有key, value两个参数,用于遍历所有的枚举项,并在每个枚举项上设置指定的key属性的值为value。

 

map方法:

forEach方法类似,不过会收集每个callback的返回值,并放置到数组之中,作为map的结果返回。

callback和target参数的描述参考forEach方法。

 

mapProperty方法:

有key一个参数,遍历集合的所有元素,并依次获取当前枚举项的key的属性值,并将结果传递到数组中返回。

 

filter方法:

有callback, target两个参数,target参数是callback的this上下文,callback的参数描述与forEach方法中的callback的参数描述一致,callback的返回值如果是true,当前枚举项的值会被添加到返回的数组之中。

 

reject方法:

filter方法的反集,只返回callback为false的枚举项。

 

filterProperty方法:

有key, value两个参数,对所有的集合元素做过滤,只返回其属性key的值与value相等的元素。

 

rejectProperty方法:

有key, value两个参数,其中value是可以省略的,表达的是不同的含义。如果提供了value,那么对所有的集合元素做过滤,只返回其属性key的值与value不相等的元素;如果没提供value,那么返回不含key属性(或key属性值的bool转换为false的)的元素。

 

find方法:

有callback, target两个参数,target参数是callback的this上下文,callback的参数描述与forEach方法中的callback的参数描述一致,在callback中对每个元素做比较,如果是需要的元素,返回true,当前元素会被作为find的结果返回。没找到返回undefined。

 

findProperty方法:

有key, value两个参数,用于查找集合元素中属性key的值与value相等的元素。

 

every方法:

有callback, target两个参数,target参数是callback的this上下文,callback的参数描述与forEach方法中的callback的参数描述一致。如果集合中的每个元素的callback的结果都是true,那么every方法的返回值就是true,否则返回false。

 

everyProperty方法:

有key, value两个参数,如果集合中的每个元素的key属性的值都是value,那么返回值就是true,否则返回false。

 

some方法: 

有callback, target两个参数,target参数是callback的this上下文,callback的参数描述与forEach方法中的callback的参数描述一致。如果对集合中的任何一个元素的callback的结果是true,返回值就是true,否则返回false。

 

someProperty方法:

有key, value两个参数,如果集合中的有任何一个元素的key属性的值是value,那么返回值就是true,否则返回false。

 

reduce方法:

有callback, initialValue, reducerProperty三个参数,其中initialValue是reduce最初的工作集,reduce方法对集合中的所有元素,依次调用callback方法,callback的描述如下:

function(previousValue, item, index, enumerable, reducerProperty);
最初传递给callback的previousValue的值是initialValue参数的值,而callback的返回值会作为下一次callback调用的previousValue参数的值,最后一次callback调用的返回值会作为reduce方法的返回值返回。

 

invoke方法:

携带任意多的参数,第一个参数是methodName,其余的参数会传递给调用的方法。对集合中的每个元素,调用指定的方法(如果集合元素是一个方法,如果不存在methodName成员,自身会被调用),传递的参数就是methodName以外的其他参数,返回值被放到数组中作为invoke方法的返回值返回。当前元素会作为method的this参数。

 

toArray方法:

将枚举集合转换成数组返回。

 

compact方法:

去掉集合中的null值,并将过滤结果作为数组返回。

 

without方法:

有一个value参数,返回不等于value的所有集合元素的数组。

 

uniq方法:

返回一个每个值都唯一的集合元素的数组。

 

'[]'计算属性:

没有实现,仅返回自身。

 

addEnumerableObserver方法:

支持枚举@enumerable观察者模式的内部方法,为枚举集合添加观察者。有target, opts两个参数,如果提供了opts参数并有willChangedidChange成员,这两个方法会作为观察者,否则会从当前类中查找enumerableWillChange方法和enumerableDidChange方法作为观察者。会触发hasEnumerableObservers计算属性的观察者。

 

removeEnumerableObserver方法:

支持枚举@enumerable观察者模式的内部方法,去掉枚举集合的观察者。有target, opts两个参数,如果提供了opts参数并有willChangedidChange成员,这两个方法会作为观察者,否则会从当前类中查找enumerableWillChange方法和enumerableDidChange方法作为观察者。会触发hasEnumerableObservers计算属性的观察者。

 

hasEnumerableObservers计算属性:

支持枚举@enumerable观察者模式的计算属性,用来判断是否存在@enumerable观察者。

 

enumerableContentWillChange方法:

是枚举@enumerable观察者前事件的触发方法,有removing, adding两个参数,可能会触发'[]'计算属性'length'属性上的观察者的前事件。

 

enumerableContentDidChange方法:

是枚举@enumerable观察者后事件的触发方法,有removing, adding两个参数,可能会触发'[]'计算属性'length'属性上的观察者的前事件。

 

> Ember.Array Mixin

定义

Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {

  length: Ember.required(),

  objectAt: function(idx) {
    if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
    return get(this, idx);
  },

  objectsAt: function(indexes) {
    var self = this;
    return map(indexes, function(idx){ return self.objectAt(idx); });
  },

  // overrides Ember.Enumerable version
  nextObject: function(idx) {
    return this.objectAt(idx);
  },

  '[]': Ember.computed(function(keyvalue) {
    if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
    return this ;
  }),

  firstObjectEmber.computed(function() {
    return this.objectAt(0);
  }),

  lastObjectEmber.computed(function() {
    return this.objectAt(get(this, 'length')-1);
  }),

  // optimized version from Enumerable
  contains: function(obj){
    return this.indexOf(obj) >= 0;
  },

  // Add any extra methods to Ember.Array that are native to the built-in Array.
  slice: function(beginIndex, endIndex) {
    var ret = Ember.A([]);
    var length = get(this, 'length') ;
    if (isNone(beginIndex)) beginIndex = 0 ;
    if (isNone(endIndex) || (endIndex > length)) endIndex = length ;

    if (beginIndex < 0) beginIndex = length + beginIndex;
    if (endIndex < 0) endIndex = length + endIndex;

    while(beginIndex < endIndex) {
      ret[ret.length] = this.objectAt(beginIndex++) ;
    }
    return ret ;
  },

  indexOf: function(object, startAt) {
    var idx, len = get(this, 'length');

    if (startAt === undefined) startAt = 0;
    if (startAt < 0) startAt += len;

    for(idx=startAt;idx<len;idx++) {
      if (this.objectAt(idx, true) === object) return idx ;
    }
    return -1;
  },

  lastIndexOf: function(object, startAt) {
    var idx, len = get(this, 'length');

    if (startAt === undefined || startAt >= len) startAt = len-1;
    if (startAt < 0) startAt += len;

    for(idx=startAt;idx>=0;idx--) {
      if (this.objectAt(idx) === object) return idx ;
    }
    return -1;
  },

  addArrayObserver: function(target, opts) {

    var willChange = (opts && opts.willChange) || 'arrayWillChange',
        didChange  = (opts && opts.didChange) || 'arrayDidChange';

    var hasObservers = get(this, 'hasArrayObservers');
    if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
    Ember.addListener(this,'@array:before', target, willChange);
    Ember.addListener(this,'@array:change', target, didChange);
    if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
    return this;

  },

  removeArrayObserver: function(target, opts) {

    var willChange = (opts && opts.willChange) || 'arrayWillChange',
        didChange  = (opts && opts.didChange) || 'arrayDidChange';

    var hasObservers = get(this, 'hasArrayObservers');
    if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
    Ember.removeListener(this, '@array:before', target, willChange);
    Ember.removeListener(this, '@array:change', target, didChange);
    if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
    return this;

  },

  hasArrayObserversEmber.computed(function() {
    return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
  }),

  arrayContentWillChange: function(startIdx, removeAmt, addAmt) {

    // if no args are passed assume everything changes
    if (startIdx===undefined) {
      startIdx = 0;
      removeAmt = addAmt = -1;
    } else {
      if (removeAmt === undefined) removeAmt=-1;
      if (addAmt    === undefined) addAmt=-1;
    }

    // Make sure the @each proxy is set up if anyone is observing @each
    if (Ember.isWatching(this, '@each')) { get(this, '@each'); }

    Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);

    var removing, lim;
    if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
      removing = [];
      lim = startIdx+removeAmt;
      for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
    } else {
      removing = removeAmt;
    }

    this.enumerableContentWillChange(removing, addAmt);

    return this;

  },

  arrayContentDidChange: function(startIdx, removeAmt, addAmt) {

    // if no args are passed assume everything changes
    if (startIdx===undefined) {
      startIdx = 0;
      removeAmt = addAmt = -1;
    } else {
      if (removeAmt === undefined) removeAmt=-1;
      if (addAmt    === undefined) addAmt=-1;
    }

    var adding, lim;
    if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
      adding = [];
      lim = startIdx+addAmt;
      for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
    } else {
      adding = addAmt;
    }

    this.enumerableContentDidChange(removeAmt, adding);
    Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);

    var length      = get(this, 'length'),
        cachedFirst = cacheFor(this, 'firstObject'),
        cachedLast  = cacheFor(this, 'lastObject');
    if (this.objectAt(0) !== cachedFirst) {
      Ember.propertyWillChange(this, 'firstObject');
      Ember.propertyDidChange(this, 'firstObject');
    }
    if (this.objectAt(length-1) !== cachedLast) {
      Ember.propertyWillChange(this, 'lastObject');
      Ember.propertyDidChange(this, 'lastObject');
    }

    return this;

  },

  // ..........................................................
  // ENUMERATED PROPERTIES
  //

  '@each': Ember.computed(function() {
    if (!this.__each) this.__each = new Ember.EachProxy(this);
    return this.__each;
  })

}) ;

 

描述

Ember.Array是ember.js对Array的类型的扩展定义,封装在一个Mixin之中。

 

也就是说需要混合Ember.Array到其他的类型中才能发挥作用。Ember.ArrayEmber.Enumerable扩展而来,继续向Ember.MutableArray扩展,最终合并到Ember.ArrayProxy之中。而Ember.ArrayProxy又是Ember.ArrayController的基类。ember.js在Ember.Array上提供了许多关键的特性,用来支持ember.js对array的观察者模式的支持。

 

接下来,对其成员做一个简短的说明。

 

length属性:

用于提供array的length。

 

objectAt方法:

有一个idx参数,提供指定位置上的数组元素。

 

objectsAt方法:

有一个indexes参数,是一个数组,用来获取多个位置上的数组元素。

 

nextObject方法:

提供可枚举的nextObject方法实现。

 

'[]'计算属性:

返回自身,如果有set,将value赋值到length属性上。

 

firstObject计算属性:

提供firstObject的优化实现。

 

lastObject计算属性:

提供lastObject的优化实现。

 

contains方法:

提供contains方法的优化实现。

 

slice方法:

提供数组切片的方法,有两个参数,beginIndex, endIndex,对应切片的开始和结束的位置。如:

    var arr = ['red', 'green', 'blue'];
    arr.slice(0);       // ['red', 'green', 'blue']
    arr.slice(0, 2);    // ['red', 'green']
    arr.slice(1, 100);  // ['green', 'blue']

 

indexOf方法:

有两个参数,object, startAt,startAt参数可以忽略。返回object所在的索引。没找到返回-1。startAt可以为负值,表示从结尾开始计算的索引。indexOf方法检索方向从前向后。

 

lastIndexOf方法:

有两个参数,object, startAt,startAt参数可以忽略。没找到返回-1。startAt可以为负值,表示从结尾开始计算的索引。lastIndexOf方法检索方向从后向前。

 

addArrayObserver方法:

支持数组@array观察者模式的内部方法,添加数组@array的观察者。有target, opts两个参数,如果提供了opts参数并有willChangedidChange成员,这两个方法会作为观察者,否则会从当前类中查找arrayWillChange方法和arrayDidChange方法作为观察者。会触发hasArrayObservers计算属性的观察者。

 

removeArrayObserver方法:

支持数组@array观察者模式的内部方法,去掉数组@array的观察者。有target, opts两个参数,如果提供了opts参数并有willChangedidChange成员,这两个方法会作为观察者,否则会从当前类中查找arrayWillChange方法和arrayDidChange方法作为观察者。会触发hasEnumerableObservers计算属性的观察者。

 

hasArrayObservers计算属性:

支持数组@array观察者模式的计算属性,用来判断是否存在@array观察者。

 

arrayContentWillChange方法:

是数组@array观察者前事件的触发方法,有startIdx, removeAmt, addAmt三个参数,会调用枚举的enumerableContentWillChange方法。

arrayContentDidChange方法:

是数组@array观察者后事件的触发方法,有startIdx, removeAmt, addAmt三个参数,会调用枚举的enumerableContentDidChange方法。可能会触发firstObject、lastObject等计算属性上的观察者。

 

@each计算属性:

 返回的是Ember.EachProxy实例的包装,细节先不展开了。

 

六、一些帮助函数

 

> Ember.isNone 方法

定义

Ember.isNone = function(obj) {
  return obj === null || obj === undefined;
};

 

描述

用于判断是否是null或undefined。

 

> Ember.isEmpty 方法

定义

Ember.isEmpty = function(obj) {
  return Ember.isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
};

 

描述

协助判断是否是空的方法。例:

  Ember.isEmpty();                // true
  Ember.isEmpty(null);            // true
  Ember.isEmpty(undefined);       // true
  Ember.isEmpty('');              // true
  Ember.isEmpty([]);              // true
  Ember.isEmpty('Adam Hawkins');  // false
  Ember.isEmpty([0,1,2]);         // false 

 

> Ember.isEqual 方法

定义

Ember.isEqual = function(a, b) {
  if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
  return a === b;
};

 

描述

协助判断是否相等的方法。例:

  Ember.isEqual('hello', 'hello');  // true
  Ember.isEqual(1, 2);              // false
  Ember.isEqual([4,2], [4,2]);      // false 

 

> Ember.inspect 方法

定义

Ember.inspect = function(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj + '';
  }

  var v, ret = [];
  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      v = obj[key];
      if (v === 'toString') { continue; } // ignore useless items
      if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
      ret.push(key + ": " + v);
    }
  }
  return "{" + ret.join(", ") + "}";
};

 

描述

提供获取对象摘要信息的方法。

 

> Ember.compare 方法

定义

Ember.compare = function compare(v, w) {
  if (v === w) { return 0; }

  var type1 = Ember.typeOf(v);
  var type2 = Ember.typeOf(w);

  var Comparable = Ember.Comparable;
  if (Comparable) {
    if (type1==='instance' && Comparable.detect(v.constructor)) {
      return v.constructor.compare(v, w);
    }

    if (type2 === 'instance' && Comparable.detect(w.constructor)) {
      return 1-w.constructor.compare(w, v);
    }
  }

  // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
  // do so now.
  var mapping = Ember.ORDER_DEFINITION_MAPPING;
  if (!mapping) {
    var order = Ember.ORDER_DEFINITION;
    mapping = Ember.ORDER_DEFINITION_MAPPING = {};
    var idx, len;
    for (idx = 0, len = order.length; idx < len;  ++idx) {
      mapping[order[idx]] = idx;
    }

    // We no longer need Ember.ORDER_DEFINITION.
    delete Ember.ORDER_DEFINITION;
  }

  var type1Index = mapping[type1];
  var type2Index = mapping[type2];

  if (type1Index < type2Index) { return -1; }
  if (type1Index > type2Index) { return 1; }

  // types are equal - so we have to check values now
  switch (type1) {
    case 'boolean':
    case 'number':
      if (v < w) { return -1; }
      if (v > w) { return 1; }
      return 0;

    case 'string':
      var comp = v.localeCompare(w);
      if (comp < 0) { return -1; }
      if (comp > 0) { return 1; }
      return 0;

    case 'array':
      var vLen = v.length;
      var wLen = w.length;
      var l = Math.min(vLen, wLen);
      var r = 0;
      var i = 0;
      while (r === 0 && i < l) {
        r = compare(v[i],w[i]);
        i++;
      }
      if (r !== 0) { return r; }

      // all elements are equal now
      // shorter array should be ordered first
      if (vLen < wLen) { return -1; }
      if (vLen > wLen) { return 1; }
      // arrays are equal now
      return 0;

    case 'instance':
      if (Ember.Comparable && Ember.Comparable.detect(v)) {
        return v.compare(v, w);
      }
      return 0;

    case 'date':
      var vNum = v.getTime();
      var wNum = w.getTime();
      if (vNum < wNum) { return -1; }
      if (vNum > wNum) { return 1; }
      return 0;

    default:
      return 0;
  }
};

 

描述

提供对象比较的方法,如果v>w,返回1,如果相等,返回0,如果v<w,返回-1。如:

  Ember.compare('hello', 'hello');  // 0
  Ember.compare('abc', 'dfg');      // -1
  Ember.compare(2, 1);              // 1

 

 

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