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

 

 

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