ES5中新增的Array方法

一、前言-索引

ES5中新增的不少東西,瞭解之對我們寫JavaScript會有不少幫助,比如數組這塊,我們可能就不需要去有板有眼地for循環了。

ES5中新增了寫數組方法,如下:

  1. forEach (js v1.6)
  2. map (js v1.6)
  3. filter (js v1.6)
  4. some (js v1.6)
  5. every (js v1.6)
  6. indexOf (js v1.6)
  7. lastIndexOf (js v1.6)
  8. reduce (js v1.8)
  9. reduceRight (js v1.8)

瀏覽器支持

  • Opera 11+
  • Firefox 3.6+
  • Safari 5+
  • Chrome 8+
  • Internet Explorer 9+

對於讓人失望很多次的IE6-IE8瀏覽器,Array原型擴展可以實現以上全部功能,例如forEach方法:

// 對於古董瀏覽器,如IE6-IE8

if (typeof Array.prototype.forEach != "function") {
  Array.prototype.forEach = function () {
    /* 實現 */
  };
}

二、一個一個來

  1. forEach
    forEach是Array新方法中最基本的一個,就是遍歷,循環。例如下面這個例子:

    [1, 2 ,3, 4].forEach(alert);

    等同於下面這個傳統的for循環:

    var array = [1, 2, 3, 4];
    
    for (var k = 0, length = array.length; k < length; k++) {
      alert(array[k]);
    }

    Array在ES5新增的方法中,參數都是function類型,默認有傳參,這些參數分別是?見下面:

    [1, 2 ,3, 4].forEach(console.log);
    
    // 結果:
    
    // 1, 0, [1, 2, 3, 4]
    // 2, 1, [1, 2, 3, 4]
    // 3, 2, [1, 2, 3, 4]
    // 4, 3, [1, 2, 3, 4]

    用來判斷參數個數以及內容的測試輸出截圖 張鑫旭-鑫空間-鑫生活

    顯而易見,forEach方法中的function回調支持3個參數,第1個是遍歷的數組內容;第2個是對應的數組索引,第3個是數組本身。

    因此,我們有:

    [].forEach(function(value, index, array) {
        // ...
    });

    對比jQuery中的$.each方法:

    $.each([], function(index, value, array) {
        // ...
    });

    會發現,第1個和第2個參數正好是相反的,大家要注意了,不要記錯了。後面類似的方法,例如$.map也是如此。

    現在,我們就可以使用forEach賣弄一個稍顯完整的例子了,數組求和:

    var sum = 0;
    
    [1, 2, 3, 4].forEach(function (item, index, array) {
      console.log(array[index] == item); // true
      sum += item;
    });
    
    alert(sum); // 10

    再下面,更進一步,forEach除了接受一個必須的回調函數參數,還可以接受一個可選的上下文參數(改變回調函數裏面的this指向)(第2個參數)。

    array.forEach(callback,[ thisObject])

    例子更能說明一切:

    var database = {
      users: ["張含韻", "江一燕", "李小璐"],
      sendEmail: function (user) {
        if (this.isValidUser(user)) {
          console.log("你好," + user);
        } else {
          console.log("抱歉,"+ user +",你不是本家人");	
        }
      },
      isValidUser: function (user) {
        return /^張/.test(user);
      }
    };
    
    // 給每個人法郵件
    database.users.forEach(  // database.users中人遍歷
      database.sendEmail,    // 發送郵件
      database               // 使用database代替上面標紅的this
    );
    
    // 結果:
    // 你好,張含韻
    // 抱歉,江一燕,你不是本家人
    // 抱歉,李小璐,你不是本家

    如果這第2個可選參數不指定,則使用全局對象代替(在瀏覽器是爲window),嚴格模式下甚至是undefined.

    另外,forEach不會遍歷純粹“佔着官位吃空餉”的元素的,例如下面這個例子:

    var array = [1, 2, 3];
    
    delete array[1]; // 移除 2
    alert(array); // "1,,3"
    
    alert(array.length); // but the length is still 3
    
    array.forEach(alert); // 彈出的僅僅是1和3

    綜上全部規則,我們就可以對IE6-IE8進行仿真擴展了,如下代碼:

    // 對於古董瀏覽器,如IE6-IE8
    
    if (typeof Array.prototype.forEach != "function") {
      Array.prototype.forEach = function (fn, context) {
        for (var k = 0, length = this.length; k < length; k++) {
          if (typeof fn === "function" && Object.prototype.hasOwnProperty.call(this, k)) {
            fn.call(context, this[k], k, this);
          }
        }
      };
    }
    

    現在拿上面“張含韻”的例子測下我們擴展的forEach方法,您可能狠狠地點擊這裏:兼容處理的forEach方法demo

    例如IE7瀏覽器下:
    IE7下forEach方法測試結果截圖 張鑫旭-鑫空間-鑫生活

  2. map
    這裏的map不是“地圖”的意思,而是指“映射”。[].map(); 基本用法跟forEach方法類似:

    array.map(callback,[ thisObject]);

    callback的參數也類似:

    [].map(function(value, index, array) {
        // ...
    });

    map方法的作用不難理解,“映射”嘛,也就是原數組被“映射”成對應新數組。下面這個例子是數值項求平方:

    var data = [1, 2, 3, 4];
    
    var arrayOfSquares = data.map(function (item) {
      return item * item;
    });
    
    alert(arrayOfSquares); // 1, 4, 9, 16

    callback需要有return值,如果沒有,就像下面這樣:

    var data = [1, 2, 3, 4];
    var arrayOfSquares = data.map(function() {});
    
    arrayOfSquares.forEach(console.log);

    結果如下圖,可以看到,數組所有項都被映射成了undefined
    全部項都成了undefined

    在實際使用的時候,我們可以利用map方法方便獲得對象數組中的特定屬性值們。例如下面這個例子(之後的兼容demo也是該例子):

    var users = [
      {name: "張含韻", "email": "[email protected]"},
      {name: "江一燕",   "email": "[email protected]"},
      {name: "李小璐",  "email": "[email protected]"}
    ];
    
    var emails = users.map(function (user) { return user.email; });
    
    console.log(emails.join(", ")); // [email protected], [email protected], [email protected]

    Array.prototype擴展可以讓IE6-IE8瀏覽器也支持map方法:

    if (typeof Array.prototype.map != "function") {
      Array.prototype.map = function (fn, context) {
        var arr = [];
        if (typeof fn === "function") {
          for (var k = 0, length = this.length; k < length; k++) {      
             arr.push(fn.call(context, this[k], k, this));
          }
        }
        return arr;
      };
    }

    您可以狠狠地點擊這裏:兼容map方法測試demo

    結果顯示如下圖,IE6瀏覽器:
    map映射方法測試,IE6下截圖

  3. filter
    filter爲“過濾”、“篩選”之意。指數組filter後,返回過濾後的新數組。用法跟map極爲相似:

    array.filter(callback,[ thisObject]);

    filtercallback函數需要返回布爾值truefalse. 如果爲true則表示,恭喜你,通過啦!如果爲false, 只能高歌“我只能無情地將你拋棄……”

    可能會疑問,一定要是Boolean值嗎?我們可以簡單測試下嘛,如下:

    var data = [0, 1, 2, 3];
    var arrayFilter = data.filter(function(item) {
        return item;
    });
    console.log(arrayFilter); // [1, 2, 3]

    有此可見,返回值只要是弱等於== true/false就可以了,而非非得返回 === true/false.

    因此,我們在爲低版本瀏覽器擴展時候,無需關心是否返回值是否是純粹布爾值(見下黑色代碼部分):

    if (typeof Array.prototype.filter != "function") {
      Array.prototype.filter = function (fn, context) {
        var arr = [];
        if (typeof fn === "function") {
           for (var k = 0, length = this.length; k < length; k++) {
              fn.call(context, this[k], k, this) && arr.push(this[k]);
           }
        }
        return arr;
      };
    }

    接着上面map篩選郵件的例子,您可以狠狠地點擊這裏:兼容處理後filter方法測試demo

    主要測試代碼爲:

    var emailsZhang = users
      // 獲得郵件
      .map(function (user) { return user.email; })
      // 篩選出zhang開頭的郵件
      .filter(function(email) {  return /^zhang/.test(email); });
    
    console.log(emailsZhang.join(", ")); // [email protected]

    filter demo頁面的結果截圖 張鑫旭-鑫空間-鑫生活

    實際上,存在一些語法糖可以實現map+filter的效果,被稱之爲“數組簡約式(Array comprehensions)”。目前,僅FireFox瀏覽器可以實現,展示下又不會懷孕:

    var zhangEmails = [user.email for each (user in users) if (/^zhang/.test(user.email)) ];
    
    console.log(zhangEmails); // [[email protected]]

    數組簡約式(Array comprehensions)實現的map+filter效果

  4. some
    some意指“某些”,指是否“某些項”合乎條件。與下面的every算是好基友,every表示是否“每一項”都要靠譜。用法如下:

    array.some(callback,[ thisObject]);

    例如下面的簡單使用:

    var scores = [5, 8, 3, 10];
    var current = 7;
    
    function higherThanCurrent(score) {
      return score > current;
    }
    
    if (scores.some(higherThanCurrent)) {
      alert("朕準了!");
    }

    結果彈出了“朕準了”文字。 some要求至少有1個值讓callback返回true就可以了。顯然,8 > 7,因此scores.some(higherThanCurrent)值爲true.

    我們自然可以使用forEach進行判斷,不過,相比some, 不足在於,some只有有true即返回不再執行了。

    IE6-IE8擴展如下:

    if (typeof Array.prototype.some != "function") {
      Array.prototype.some = function (fn, context) {
    	var passed = false;
    	if (typeof fn === "function") {
       	  for (var k = 0, length = this.length; k < length; k++) {
    		  if (passed === true) break;
    		  passed = !!fn.call(context, this[k], k, this);
    	  }
        }
    	return passed;
      };
    }

    於是,我們就有了“朕準了”的demo,您可以狠狠地點擊這裏:兼容處理後的some方法demo

    IE7瀏覽器下some擴展demo結果

  5. every
    some的基友關係已經是公開的祕密了,同樣是返回Boolean值,不過,every需要每一個妃子都要讓朕滿意,否則——“來人,給我拖出去砍了!”

    IE6-IE8擴展(與some相比就是truefalse調換一下):

    if (typeof Array.prototype.every != "function") {
      Array.prototype.every = function (fn, context) {
        var passed = true;
        if (typeof fn === "function") {
           for (var k = 0, length = this.length; k < length; k++) {
              if (passed === false) break;
              passed = !!fn.call(context, this[k], k, this);
          }
        }
        return passed;
      };
    }

    還是那個朕的例子,您可以狠狠地點擊這裏:是否every妃子讓朕滿意demo

    if (scores.every(higherThanCurrent)) {
      console.log("朕準了!");
    } else {
      console.log("來人,拖出去斬了!");        
    }

    結果是:
    every方法結果

  6. indexOf
    indexOf方法在字符串中自古就有,string.indexOf(searchString, position)。數組這裏的indexOf方法與之類似。

    array.indexOf(searchElement[, fromIndex])

    返回整數索引值,如果沒有匹配(嚴格匹配),返回-1fromIndex可選,表示從這個位置開始搜索,若缺省或格式不合要求,使用默認值0,我在FireFox下測試,發現使用字符串數值也是可以的,例如"3"3都可以。

    var data = [2, 5, 7, 3, 5];
    
    console.log(data.indexOf(5, "x")); // 1 ("x"被忽略)
    console.log(data.indexOf(5, "3")); // 4 (從3號位開始搜索)
    
    console.log(data.indexOf(4)); // -1 (未找到)
    console.log(data.indexOf("5")); // -1 (未找到,因爲5 !== "5")

    兼容處理如下:

    if (typeof Array.prototype.indexOf != "function") {
      Array.prototype.indexOf = function (searchElement, fromIndex) {
        var index = -1;
        fromIndex = fromIndex * 1 || 0;
    
        for (var k = 0, length = this.length; k < length; k++) {
          if (k >= fromIndex && this[k] === searchElement) {
              index = k;
              break;
          }
        }
        return index;
      };
    }

    一個路子下來的,顯然,輪到demo了,您可以狠狠地點擊這裏:兼容處理後indexOf方法測試demo

    下圖爲ietester IE6下的截圖:
    IE6下indexOf擴展後測試結果截圖

  7. lastIndexOf
    lastIndexOf方法與indexOf方法類似:

    array.lastIndexOf(searchElement[, fromIndex])

    只是lastIndexOf是從字符串的末尾開始查找,而不是從開頭。還有一個不同就是fromIndex的默認值是array.length - 1而不是0.

    IE6等瀏覽器如下折騰:

    if (typeof Array.prototype.lastIndexOf != "function") {
      Array.prototype.lastIndexOf = function (searchElement, fromIndex) {
        var index = -1, length = this.length;
        fromIndex = fromIndex * 1 || length - 1;
    
        for (var k = length - 1; k > -1; k-=1) {
            if (k <= fromIndex && this[k] === searchElement) {
                index = k;
                break;
            }
        }
        return index;
      };
    }

    於是,則有:

    var data = [2, 5, 7, 3, 5];
    
    console.log(data.lastIndexOf(5)); // 4
    console.log(data.lastIndexOf(5, 3)); // 1 (從後往前,索引值小於3的開始搜索)
    
    console.log(data.lastIndexOf(4)); // -1 (未找到)

    懶得截圖了,結果查看可狠狠地點擊這裏:lastIndexOf測試demo

  8. reduce
    reduce是JavaScript 1.8中才引入的,中文意思爲“減少”、“約簡”。不過,從功能來看,我個人是無法與“減少”這種含義聯繫起來的,反而更接近於“迭代”、“遞歸(recursion)”,擦,因爲單詞這麼接近,不會是ECMA-262 5th制定者筆誤寫錯了吧~~

    此方法相比上面的方法都複雜,用法如下:

    array.reduce(callback[, initialValue])

    callback函數接受4個參數:之前值、當前值、索引值以及數組本身。initialValue參數可選,表示初始值。若指定,則當作最初使用的previous值;如果缺省,則使用數組的第一個元素作爲previous初始值,同時current往後排一位,相比有initialValue值少一次迭代。

    var sum = [1, 2, 3, 4].reduce(function (previous, current, index, array) {
      return previous + current;
    });
    
    console.log(sum); // 10

    說明:

    1. 因爲initialValue不存在,因此一開始的previous值等於數組的第一個元素。
    2. 從而current值在第一次調用的時候就是2.
    3. 最後兩個參數爲索引值index以及數組本身array.

    以下爲循環執行過程:

    // 初始設置
    previous = initialValue = 1, current = 2
    
    // 第一次迭代
    previous = (1 + 2) =  3, current = 3
    
    // 第二次迭代
    previous = (3 + 3) =  6, current = 4
    
    // 第三次迭代
    previous = (6 + 4) =  10, current = undefined (退出)

    有了reduce,我們可以輕鬆實現二維數組的扁平化:

    var matrix = [
      [1, 2],
      [3, 4],
      [5, 6]
    ];
    
    // 二維數組扁平化
    var flatten = matrix.reduce(function (previous, current) {
      return previous.concat(current);
    });
    
    console.log(flatten); // [1, 2, 3, 4, 5, 6]

    兼容處理IE6-IE8:

    if (typeof Array.prototype.reduce != "function") {
      Array.prototype.reduce = function (callback, initialValue ) {
         var previous = initialValue, k = 0, length = this.length;
         if (typeof initialValue === "undefined") {
            previous = this[0];
            k = 1;
         }
         
        if (typeof callback === "function") {
          for (k; k < length; k++) {
             this.hasOwnProperty(k) && (previous = callback(previous, this[k], k, this));
          }
        }
        return previous;
      };
    }

    然後,測試整合,demo演示,您可以狠狠地點擊這裏:兼容IE6的reduce方法測試demo

    IE6瀏覽器下結果如下圖:
    reduce方法測試頁面結構示意

  9. reduceRight
    reduceRightreduce相比,用法類似:

    array.reduceRight(callback[, initialValue])

    實現上差異在於reduceRight是從數組的末尾開始實現。看下面這個例子:

    var data = [1, 2, 3, 4];
    var specialDiff = data.reduceRight(function (previous, current, index) {
      if (index == 0) {
        return previous + current;
      }
      return previous - current;
    });
    
    console.log(specialDiff); // 0

    結果0是如何得到的呢?
    我們一步一步查看循環執行:

    // 初始設置
    index = 3, previous = initialValue = 4, current = 3
    
    // 第一次迭代
    index = 2, previous = (4- 3) = 1, current = 2
    
    // 第二次迭代
    index = 1, previous = (1 - 2) = -1, current = 1
    
    // 第三次迭代
    index = 0, previous = (-1 + 1) = 0, current = undefined (退出)

    爲使低版本瀏覽器支持此方法,您可以添加如下代碼:

    if (typeof Array.prototype.reduceRight != "function") {
      Array.prototype.reduceRight = function (callback, initialValue ) {
        var length = this.length, k = length - 1, previous = initialValue;
        if (typeof initialValue === "undefined") {
            previous = this[length - 1];
            k--;
        }
        if (typeof callback === "function") {
           for (k; k > -1; k-=1) {          
              this.hasOwnProperty(k) && (previous = callback(previous, this[k], k, this));
           }
        }
        return previous;
      };
    }

    您可以狠狠地點擊這裏:reduceRight簡單使用demo

    對比FireFox瀏覽器和IE7瀏覽器下的結果:
    FireFox瀏覽器下reduceRight測試結果 對比IE7下reduceRight測試結果截圖 張鑫旭-鑫空間-鑫生活

三、更進一步的應用

我們還可以將上面這些數組方法應用在其他對象上。

例如,我們使用forEach遍歷DOM元素。

var eleDivs = document.getElementsByTagName("div");
Array.prototype.forEach.call(eleDivs, function(div) {
    console.log("該div類名是:" + (div.className || "空"));
});

可以輸出頁面所有div的類名,您可以狠狠地點擊這裏:Array新方法forEach遍歷DOM demo

結果如下,IE6下,demo結果:
forEach方法遍歷DOM的使用演示 張鑫旭-鑫空間-鑫生活

等很多其他類數組應用。

四、最後一點點了

本文爲低版本IE擴展的Array方法我都合併到一個JS中了,您可以輕輕的右鍵這裏或下載或查看:es5-array.js

以上所有未IE擴展的方法都是自己根據理解寫的,雖然多番測試,難免還會有細節遺漏的,歡迎指出來。

注:原文出處

http://www.zhangxinxu.com/wordpress/2013/04/es5%E6%96%B0%E5%A2%9E%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95/

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