2013年12月28日 星期六

ExtJS 4 測試 : each 與 forEach 迴圈測試

我在 "ExtJS 架構原理與應用實例大全" (博碩) 這本書上看到一張 ExtJS 的架構圖, 覺得很能表達此框架的結構, 因此重繪了一張如下 :


ExtJS 把 Javascript 原生的功能加以擴充, 其中迴圈功能就屬於上圖中的核心元件部份. 迴圈是寫程式常用的技巧, 通常用在拜訪陣列元素或物件屬性. ExtJS 在許多類別或物件上都提供了 each 或 forEach 方法, 於 API 中搜尋約有 24 個 :

http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.tab.Panel

此處我們測試其中四個最常用的 :
  1. Ext.Array.each() 與 Ext.Array.forEach()
  2. Ext.Object.each()
  3. Ext.iterate()
首先測試 Ext.Array.each(), 此方法有一個別名 Ext.each(), 可以少打一個 Array. 其參數格式如下 , 前兩個參數為必要, 後兩個為備選. 第一個參數 iterable 傳入要拜訪的陣列, fn 是回呼函式, scope 為作用域 (預設 this), reverse 為布林值 (預設 false), true 表示逆向拜訪.

each(iterable, fn, [scope], [reverse])

而回呼函式呼叫結構如下 :

fn(item, index, arr)  

它會傳入三個參數, 其中 item 是目前所拜訪之陣列元素, index 是其索引, 而 arr 則為陣列本身, 這三個參數都是備選的, 通常應用時是傳入前兩個參數 (不傳入參數我們是要幹啥?).

我們設立一個陣列 color, 然後用 Ext.each() 來遍歷, 如下列範例 1 所示 :

測試範例 1 : http://mybidrobot.allalla.com/extjstest/each_1.htm [看原始碼]
 
      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.Array.each(color,
                       function(item, index) {
                          output += "color[" + index + "]=" + item + "<br>";
                          }
                       );
      Ext.Msg.alert("訊息",output);


在上例中, 回呼函式沒有接收第三參數, 即拜訪之陣列本身, 其實我們也可以用第三參數來取得陣列元素, 如下列範例 2 所示 :

測試範例 2http://mybidrobot.allalla.com/extjstest/each_2.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.each(color,
                      function(item, index, arr) {
                         output += "color[" + index + "]=" + arr[index] + "<br>";
                         }
                      );
      Ext.Msg.alert("訊息",output);


範例 2 中我們只是將範例 1 的 item 改成 arr[index], 效果一樣.

下面來測試 each() 的第四參數 reverse, 如下列範例 3 所示 :

測試範例 3http://mybidrobot.allalla.com/extjstest/each_3.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.Array.each(color,
                               function(item, index) {
                                  output += "color[" + index + "]=" + item +
"<br>";
                                  },
                               this, true
                               );
      Ext.Msg.alert("訊息",output);



可見當 reverse 設為 true 時, 將會逆序從陣列尾端開始拜訪. 注意, scope 不能略去不填, 否則 true 會被當成 scope, 導致被認為沒有傳入 reverse 而仍然以預設之順序拜訪陣列.

事實上, 使用 Javascript 原生的 for 或 for in 語法也不會很麻煩呀, 如下範例 4-1 與 4-2 所示 :

測試範例 4-1http://mybidrobot.allalla.com/extjstest/each_4_1.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      for (var i=0; i<color.length; i++) {
            output += "color[" + i + "]=" + color[i] +
"<br>";
            };
      Ext.Msg.alert("訊息",output);


測試範例 4-2http://mybidrobot.allalla.com/extjstest/each_4_2.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      for (i in color) {
           output += "color[" + i + "]=" + color[i] +
"<br>";
           };
      Ext.Msg.alert("訊息",output);


在 ECMAScript  5 還定義了一個 Javascript 陣列的原生 forEach() 方法, 用法跟 Ext.Array.forEach 幾乎一樣 (誰抄誰?), 如下列範例 4-3 所示 :

測試範例 4-3http://mybidrobot.allalla.com/extjstest/each_4_3.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      color.forEach(function(item,index){
         output += "color[" + index + "]=" + color[index] + "<br>";
         });
      Ext.Msg.alert("訊息",output);

不過 Ext.each() 還是有比較特殊的地方啦, 例如第二個參數回呼函式 fn 是有傳回值的 (布林值, 預設傳回 true), 如果傳回 false 會讓迴圈停止, 例如下列範例 5 所示 :

測試範例 5http://mybidrobot.allalla.com/extjstest/each_5.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.each(color,
                       function(item, index) {
                          if (item=="green") {return false;}
                          output += "color[" + index + "]=" + item + "<br>";
                          }
                       );
      Ext.Msg.alert("訊息",output);


可見當拜訪至索引 4 green 時, 便傳回 false 而中斷迴圈執行了.

接著要測試 Ext.Array 提供的另一個拜訪陣列的方法 forEach(). 其呼叫格式如下 (回呼函式 fn 與 each 相同, 只是傳回值不會影響迴圈執行) :

forEach(array, fn, [scope])

此方法用途與上面的 each() 相同, 但功能與效能上有些差異; 據 API 描述, forEach() 是委派給 Javascript 原生的 Array.prorotype.forEach() 來執行, 因此效能比 each() 要好. 其次, 功能上的差異是, forEach() 不能設定逆序拜訪, 也不能透過回呼函式傳回 false 來中斷迴圈.

下面範例 6 是改自範例 1, 只是將 each 改為 forEach 而已 :

測試範例 6http://mybidrobot.allalla.com/extjstest/each_6.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.Array.forEach(color,
                                        function(item, index) {
                                           output += "color[" + index + "]=" + item + "<br>";
                                           }
                                        );
      Ext.Msg.alert("訊息",output);

結果是一樣的, 但是要注意, 這裡必須寫 Ext.Array.forEach 全名 (因為它沒有 Ext.forEach 這樣的別名), 否則會出現找不到物件之錯誤.

下面要測試物件的拜訪方法 : Ext.Object.each(), 其呼叫結構如下 :

each(object, fn, [scope])

其第一參數為要拜訪之物件, 第二參數 fn 為回呼函式, 其呼叫結構如下 :

fn(key, value, object)

這三個參數都非必要, 但通常都要傳入前兩個 (不傳進來那能幹啥?). 如下列範例 7 所示 :

測試範例 7http://mybidrobot.allalla.com/extjstest/each_7.htm [看原始碼]

      var country={name:"伊朗",capital:"德黑蘭",code:"+98"};
      var output="";
      Ext.Object.each(country,
                                   function(key, value) {
                                      output += "country." + key + "=" + value + 
"<br>";
                                      }
                                  );
      Ext.Msg.alert("訊息",output); 



跟 Ext.Array.each() 一樣, 物件的 each 也可以在回呼函式中傳回 false 來中斷物件之拜訪, 如下列範例 8 所示 :

測試範例 8http://mybidrobot.allalla.com/extjstest/each_8.htm [看原始碼]

      var country={name:"伊朗",capital:"德黑蘭",code:"+98"};
      var output="";
      Ext.Object.each(country,
                                   function(key, value, object) {
                                      if (key=="code") {return false;}
                                          output += "country." + key + "=" + value +
"<br>";
                                          }
                                   );
      Ext.Msg.alert("訊息",output);

 

可見拜訪至國碼屬性 code 時就傳回 false 而停止迴圈了.

其實, 陣列是物件的一種, 只是其 key 為數值罷了. 因此我們也可以將陣列傳入 Ext.Object.each() 中遍歷, 如下列範例 9 所示 :

測試範例 9 : http://mybidrobot.allalla.com/extjstest/each_9.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output=""; 
      Ext.Object.each(color, 
                                 function(key, value, object) {
                                    if (key=="code") {return false;}
                                    output += "color." + key + "=" + value + "<br>";
                                    }
                               );
      Ext.Msg.alert("訊息",output);



可見陣列當物件看時, 其 key 就是其索引. 

如果物件有方法呢? 用 each() 遍歷時, 其值就是代表方法定義的函式內容, 如下列範例 10 所示 :

測試範例 10 : http://mybidrobot.allalla.com/extjstest/each_10.htm [看原始碼]

      function user(account,password) {
        this.account=account;
        this.password=password;
        this.changePassword=changePassword;
        function changePassword(newPassword) {
          this.password=newPassword;
          } 
        } 
      var user1=new user("tony","123");
      var output=""; 
      Ext.Object.each(user1, 
                                 function(key, value, object) {
                                    output += "user1." + key + "=" + value + "<br>";
                                    }
                                 );
      Ext.Msg.alert("訊息",output);



在上例中, 我們定義了具有一個方法的物件 user, 當遍歷物件時, 方法的內容會被當作 value 顯示出來.

上面範例 9 我們用 Ext.Object.each() 來拜訪陣列, 如果反過來呢? 也就是用 Ext.Array.each() 來拜訪物件呢? 這是行不通的, 整個物件會被當作只有一個元素的陣列, 如下範例 11 所示 :

測試範例 11 : http://mybidrobot.allalla.com/extjstest/each_11.htm [看原始碼]

      var country={name:"伊朗",capital:"德黑蘭",code:"+98"};
      var output=""; 
      Ext.Array.each(country, 
                                function(item, index) {
                                   output += "country[" + index + "]=" + item + "<br>";
                                  }
                              );
      Ext.Msg.alert("訊息",output);



所以, 陣列可以當物件來拜訪, 但是物件卻不能當物件來拜訪.

其實 ExtJS4 的根名稱空間 Ext 有一個 Ext.iterate() 方法是可以傳入陣列或物件的. 其參數格式與 Ext.Object.each() 相同, 從源碼可知, 其實它會先判斷傳入的是陣列還是物件, 若是陣列就呼叫 Ext.Array.each(); 否則就呼叫 Ext.Object.each(). 下列範例 11-1 是用 iterate 來拜訪陣列 :

測試範例 11-1 : http://mybidrobot.allalla.com/extjstest/each_11_1.htm [看原始碼]

      var color=["red","white","blue","yellow","green","pink","black"];
      var output="";
      Ext.iterate(color,
                       function(key, value) {
                          output += "color.'" + key + "=" + value + "<br>";
                          }
                       );
      Ext.Msg.alert("訊息",output);

範例 11-2 是用 iterate 來拜訪物件 :

測試範例 11-2 : http://mybidrobot.allalla.com/extjstest/each_11_2.htm [看原始碼]

      var country={name:"伊朗",capital:"德黑蘭",code:"+98"};
      var output=""; 
      Ext.iterate(country, 
                        function(key, value) {
                           output += "country." + key + "=" + value + "<br>";
                           }
                       );
      Ext.Msg.alert("訊息",output);

最後我們來比較一下各種迴圈的效能, 如下列範例 12 所示 (請用 Chrome 瀏覽, 按 Ctrl+Shift+J 即可打開下方控制台看到結果, Firefox 則按 F12) :

測試範例 12 : http://mybidrobot.allalla.com/extjstest/each_12.htm [看原始碼]

      //製作 100000 個元素的陣列
      var arr=[];
      for (var i=0; i<100000; i++) {arr[i]=i + 1;}
      //使用 Ext.Array.each
      var sum=0;
      console.time("Ext.Array.each");
      Ext.Array.each(arr, function(item, index) {sum += item;});
      console.timeEnd("Ext.Array.each");
      //使用 Ext.Array.forEach
      sum=0;
      console.time("Ext.Array.forEach");
      Ext.Array.forEach(arr, function(item, index) {sum += item;});
      console.timeEnd("Ext.Array.forEach");
      //使用 Ext.Object.each 迴圈
      sum=0;
      console.time("Ext.Object.each");
      Ext.Object.each(arr,function(key, value) {sum += value;});
      console.timeEnd("Ext.Object.each");
      //使用 Ext.iterate 迴圈
      sum=0;
      console.time("Ext.iterate");
      Ext.iterate(arr,function(key, value) {sum += value;});
      console.timeEnd("Ext.iterate");
      //使用 for 迴圈
      sum=0;
      console.time("for");
      for (var i=0; i<100000; i++) {sum += arr[i];}
      console.timeEnd("for");
      //使用 for in 迴圈
      sum=0;
      console.time("forin");
      for (item in arr) {sum += item;}
      console.timeEnd("forin");
      //使用 forEach 迴圈
      sum=0;
      console.time("forEach");
      arr.forEach(function(item, index) {sum += item;});
      console.timeEnd("forEach");




此處我們利用瀏覽器的 console.time() 與 console.timeEnd() 方法來計算迴圈所耗費的時間 (注意所傳入之參數起訖必須一致才能正確計算時間), 每次執行所得數據都會稍有變化, 但基本上差異不大. 由上可知這四種迴圈的效能依序是 :

第一名=for 
第二名=forEach, Ext.iterate, Ext.Array.forEach
第三名=for in
第四名=Ext.Object.each

可見 Javascript 原生的 for 效能還是最棒的, 但同樣原生的 for in 卻很遜, 所以最好少用. 其次, Ext.Array.forEach 委由 原生的 Array.prorotype.forEach 來執行, 效能確實比 each 要稍好, 雖然差距不大, 但迴圈數多時就很可觀了.

沒有留言 :