ES5中新增的Array方法詳細說明
一、前言-索引
ES5中新增的不少東西,瞭解之對我們寫JavaScript會有不少幫助,比如陣列這塊,我們可能就不需要去有板有眼地for
迴圈了。
ES5中新增了寫陣列方法,如下:
瀏覽器支援
- 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 () { /* 實現 */ }; }
二、一個一個來
- 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瀏覽器下:
- 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
:在實際使用的時候,我們可以利用
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瀏覽器:
- filter
filter
為“過濾”、“篩選”之意。指陣列filter
後,返回過濾後的新陣列。用法跟map
極為相似:array.filter(callback,[ thisObject]);
filter
的callback
函式需要返回布林值true
或false
. 如果為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]
實際上,存在一些語法糖可以實現
map+filter
的效果,被稱之為“陣列簡約式(Array comprehensions)”。目前,僅FireFox瀏覽器可以實現,展示下又不會懷孕:var zhangEmails = [user.email for each (user in users) if (/^zhang/.test(user.email)) ]; console.log(zhangEmails); // [[email protected]]
- 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
- every
跟some
的基友關係已經是公開的祕密了,同樣是返回Boolean值,不過,every
需要每一個妃子都要讓朕滿意,否則——“來人,給我拖出去砍了!”IE6-IE8擴充套件(與
some
相比就是true
和false
調換一下):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("來人,拖出去斬了!"); }
結果是:
- indexOf
indexOf
方法在字串中自古就有,string.indexOf(searchString, position)
。陣列這裡的indexOf
方法與之類似。array.indexOf(searchElement[, fromIndex])
返回整數索引值,如果沒有匹配(嚴格匹配),返回
-1
.fromIndex
可選,表示從這個位置開始搜尋,若預設或格式不合要求,使用預設值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下的截圖:
- 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 (未找到)
- 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
說明:
- 因為
initialValue
不存在,因此一開始的previous
值等於陣列的第一個元素。 - 從而
current
值在第一次呼叫的時候就是2
. - 最後兩個引數為索引值
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瀏覽器下結果如下圖:
- 因為
- reduceRight
reduceRight
跟reduce
相比,用法類似: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瀏覽器下的結果:
三、更進一步的應用
我們還可以將上面這些陣列方法應用在其他物件上。
例如,我們使用forEach遍歷DOM元素。
var eleDivs = document.getElementsByTagName("div"); Array.prototype.forEach.call(eleDivs, function(div) { console.log("該div類名是:" + (div.className || "空")); });
結果如下,IE6下,demo結果:
等很多其他類陣列應用。
四、最後一點點了
本文為低版本IE擴充套件的Array方法我都合併到一個JS中了,您可以輕輕的右鍵這裡或下載或檢視:es5-array.js
以上所有未IE擴充套件的方法都是自己根據理解寫的,雖然多番測試,難免還會有細節遺漏的,歡迎指出來。