1. 程式人生 > >ES5中新增的Array方法詳細說明

ES5中新增的Array方法詳細說明

一、前言-索引

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

ES5中新增了寫陣列方法,如下:

  1. map (js v1.6)
  2. some (js v1.6)
  3. every (js v1.6)

瀏覽器支援

  • 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;
      };
    }

    結果顯示如下圖,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;
      };
    }

    主要測試程式碼為:

    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;
      };
    }
    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 (未找到)
  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;
      };
    }

    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;
      };
    }

    對比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 || "空"));
});

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

等很多其他類陣列應用。

四、最後一點點了

本文為低版本IE擴充套件的Array方法我都合併到一個JS中了,您可以輕輕的右鍵這裡或下載或檢視:es5-array.js

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