在 JavaScript 中,對於數組的操作非常頻繁,對應的 API 也很豐富 。ECMAScript
規范在每一版發布時,都會提供新的 API 來增強數組的操作能力,下面將詳細介紹這些 API 的一些特性。
ES5 新增的 9 個API
1、forEach( callback[,thisArg] )
在 ES5 之前,我們可以通過 for
和 for in
兩種方式來遍歷數組,ES5 引入了一個新方法 forEach
,使數組遍歷更加簡潔,forEach
需要傳遞兩個參數,第一個參數是回調函數,是必選參數,第二個參數是一個對象,用來改變 callback
中的 this
指向,是可選參數。
var arr = ['a', 'b', 'c']; arr.forEach(function(v, i, r) { console.log(v, i, r); }) > a 0 ['a', 'b', 'c'] b 1 ['a', 'b', 'c'] c 2 ['a', 'b', 'c']
callback 中傳入了3個參數 v,i,r
分別表示當前元素、當前位置、數組對象。再看看使用 thisArg
的例子:
var obj = { print: function (a, b) { console.log(a, b); } }; var arr = ['a', 'b', 'c']; arr.forEach(function (v, i, a) { this.print(v, i); }, obj);
不傳 thisArgs
時,callback
中的 this
默認指向 window 對象,當傳遞 thisArg
時,callback
中的 this
就指向了 thisArg
,因此這個參數的目的就是為了改變回調函數中的this
指向。
對於不支持 ES5 的瀏覽器,我們可以對 forEach
進行簡單的擴展來兼容老的瀏覽器:
if (!Array.prototype.forEach) { Array.prototype.forEach = function (callback, thisArg) { for (var i=0; i < this.length; i++) { // 當thisArg為undefined時,JS引擎會將window作為其調用者 callback.call(thisArg, this[i], i, this.toString()); } } }
2、filter( callback [, thisArg] )
filter
是`過濾`的意思,所以這個方法的作用就是返回一個匹配過濾條件的新數組,其接收兩個參數 callback
和 thisArg
,callback
也是回調函數,主要用於對元素進行條件匹配,thisArg
和 forEach
中的 thisArg
作用一樣,在這里就不重復了,看下面示例:
var arr = ["a", "b", "a", "c"]; var newArr = arr.filter(function (item) { return item === "a"; }); newArr > ["a", "a"]
沒有filter
的時候,要實現這個功能,我們事先要創建一個空的數組,把匹配到的元素再 push
進去,現在就不需要那么麻煩了,我們再看看對filter
的擴展:
if (!Array.prototype.filter) { Array.prototype.filter = function (callback, thisArg) { var temp = []; for (var i = 0; i < this.length; i++) { if (callback.call(thisArg, this[i])){ // 如果callback返回true,則該元素符合過濾條件,將元素壓入temp中 temp.push(this[i]); } } return temp; } }
可以看出,filter 將過濾的結果作為一個新數組返回,即使符合條件的元素只有一個,返回的也是數組 。為了更方便的對單個元素進行查詢,ES6 在數組原型上提供了 find
方法,用於從數組中查詢單個符合條件的元素,和 filter 不同的是,它返回的是單個元素。
[2, 3, 5, 8, 9, 3].find(item => item == 3); // 3
需要注意的是,find
只返回第一個匹配到的元素,如果沒有匹配到,則會返回 undefined
。和 filter 一樣,find
也可以傳遞第 2 個參數,用於設置回調函數的 this 指針 。
3、map( callback[,thisArg] )
map 的作用是對原數組進行加工處理后並將其作為一個新數組返回,該方法同樣接收兩個參數,callback 是回調函數用於對數組進行加工處理,thisArg 和上面的一樣。先看一個簡單的例子:
var arr = [ {w: 10, h: 10}, //定義長和寬 {w: 15, h: 20}, {w: 12, h: 12} ]; var newArr = arr.map(function (item) { // 根據長寬計算出面積並賦值給新屬性area item.area = item.w * item.h; return item; }); newArr[0] > {w: 10, h: 10, area: 100}
可以看出,newArr 返回的是增加了 area 屬性的對象數組。這個方法非常實用,一般情況下,當一個ajax請求返回時,我們都要對其結果集進行過濾和校驗等操作,這時 map
就派上用場了。我們再看看如果對 map 進行兼容性擴展:
if (!Array.prototype.map) { Array.prototype.map = function (callback, thisArg) { var temp = []; for (var i = 0; i < this.length; i++) { var newItem = callback.call(thisArg, this[i]); temp.push(newItem); // 將callback返回的新元素壓入temp中 } return temp; } }
4、reduce ( callback[,initialValue] )
reduce 在這里有`減少`的意思,其作用是對數組進行歸並操作,換句話說就是對數組每一個元素進行累加,最終返回所有元素之和。 回調函數 callback 接收4個參數:
previousValue - 存放的是上一次callback返回的結果,其初始值默認為數組的第一個元素。
currentValue - 是當前元素 。默認從數組的第二個元素開始。
currentIndex - 是當前元素位置 。
array - 是當前數組。
var arr = [1, 2, 3, 4]; var newArr = arr.reduce(function (previousValue, currentValue, currentIndex, array) { console.log(previousValue, currentValue, currentIndex); return previousValue + currentValue; }); 1 2 1 3 3 2 6 4 3 newArr > 10
reduce 除過可以傳遞 callback 之外,還可以傳遞一個參數 initialValue ,作為數組累加的基數。當傳了這個參數以后,callback 中的 previousValue
初始值就被置為 initialValue
,reduce
也改為從數組的第一個元素開始遍歷。
var arr = [1, 2, 3, 4]; var newArr = arr.reduce(function (previousValue, currentValue, currentIndex, array){ console.log(previousValue, currentValue, currentIndex); return previousValue + currentValue; }, 100); 100 1 0 101 2 1 103 3 2 106 4 3 newArr > 110
從結果可以看出,reduce 最終返回的是: previousValue + 數組本身歸並計算的結果。對 reduce 的 polyfill 實現如下:
if (!Array.prototype.reduce) { Array.prototype.reduce = function (callback, initialValue) { var previousValue = initialValue || this[0];// 如果不指定intialValue,則默認為數組的第一個元素 // 如果不指定initialValue,i從1開始遍歷,否則就從0開始遍歷 for (var i = initialValue ? 0 : 1; i < this.length; i++) { // previousValue 累加每一次返回的結果 previousValue = callback(previousValue, this[i], i, this.toString()); } return previousValue; } }
5、reduceRight ( callback[,initialValue] )
和 reduce 的作用完全相同,唯一的不同是,reduceRight 是從右至左遍歷數組的元素。
6、some ( callback[,thisArg] )
some
是`某些、一些`的意思,其作用是對數組中的每一項執行回調函數,如果該函數對任一項返回 true,則停止遍歷,並返回 true 。
var arr = [ 1, 2, 3, 4]; var result = arr.some(function(item, index, array ){ console.log(item, index, array); return item > 2; }); > 1 0 [1, 2, 3, 4] 2 1 [1, 2, 3, 4] 3 2 [1, 2, 3, 4] restule > true
some 檢測整個數組,只要當arr中有一個元素符合條件 item>2 就停止檢測和遍歷,並返回 true,以表示檢測到目標。這和我們在 for 循環中使用 break 語言的作用有點類似。 對於 some
的兼容性擴展如下:
if(!Array.prototype.some) { Array.prototype.some = function (callback, thisArg) { for (var i = 0; i < this.length; i++) { if(callback.call(thisArg, this[i], i, this.toString())){ return true; // 檢測到callback返回true,跳出循環,並返回true } } return false; // 一個符合條件的都沒有檢測到,返回false } }
7、every (callback[,thisArg])
every 是`每一個`的意思,其作用是對數組中的每一項執行回調函數,如果該函數對每一項都返回 true,則返回 true 。
var arr = [ 1, 2, 3, 4]; var result = arr.every(function(item, index, array ){ console.log(item, index, array); return item < 3; }); 1 0 [1, 2, 3, 4] 2 1 [1, 2, 3, 4] 3 2 [1, 2, 3, 4] result > false
當檢測第3個元素時,item<3 為 false,停止檢測,並返回 false,這說明every在檢測元素時,要求每一個元素都要符合條件 item<3,如果有一個不符合就停止檢測,並返回false。(你可以測試 item<5 時的運行結果,返回值一定是 true ) 。
那 every 到底有什么作用呢? 當一個 for 循環使用了 break 語句后,我們想知道 for 循環是否正常的執行完時, 我們一般會通過檢測for中的索引 i==arr.length 來判斷,因此every 的作用就體現在這里。
下面是對於 every 的兼容性擴展:
if (!Array.prototype.every) { Array.prototype.every = function (callback, thisArg) { for (var i = 0; i < this.length; i++) { if(!callback.call(thisArg,this[i], i, this.toString())){ return false; // 檢測到不符合條件的元素,跳出循環,並返回false } } return true; // 所有元素都符合條件,返回true } }
8、indexOf[searchElement[, fromIndex]]
indexOf() 用於查詢數組元素對應的索引位置,可以傳遞兩個參數,第一個參數是要匹配的元素,必須是簡單數據類型。第二個參數是指定查詢的起始位置。
// 默認從索引0的位置開始 [1, 2, 3, 5, 2].indexOf(2); // 1 // 指定從索引3的位置開始 [1, 2, 3, 5, 2].indexOf(2, 3); // 4
indexOf() 返回的是元素在數組中的位置 。如果只想知道數組中是否存在某個元素,而不關心元素的位置,也可以使用 ES6 提供的 includes()
方法來判斷。
let a = [1, 2, 3]; a.includes(1); // true a.includes(1, 1);// false
includes()
也是數組原型上的方法, 和 indexOf()
的傳參是一樣的。
需要注意的是,indexOf() 適用於數組元素是簡單類型的情況,而無法檢索對象數組的元素位置。
let arr = [{c: 1}, {c: 2}]; // 對象數組 arr.indexOf({c: 1}); // -1
對於這個問題,可以使用 forEach() 來遍歷數組,當找到符合條件的元素時,就可以獲取到對應的數組下標,而在 ES6 中,可以使用 findIndex() 達到同樣的目的。
findIndex() 也是用於查詢數組元素的位置,和 indexOf() 不同的是,它可以檢索對象數組的元素位置,但需要通過回調函數來指定匹配的元素。
//簡單數組 [1, 2, 3, 5].findIndex(item => item == 3); // 2 //對象數組 [{id: 1}, {id: 3}, {id: 5}].findIndex(item => item.id == 3); // 1
9、lastIndexOf[searchElement[, fromIndex]]
和 indexOf() 的作用完全相同,唯一的不同是,lastIndexOf() 是從右至左檢索數組元素。
其他常用 API
1、sort( [compareFunction] )
對數組做原地排序,並返回這個數組,默認按照字符串 UNICODE
的碼位點排序,如下所示:
var fruit = ['cherries', 'apples', 'bananas']; fruit.sort(); // ['apples', 'bananas', 'cherries'] var scores = [1, 10, 2, 21]; scores.sort(); // [1, 10, 2, 21]
這顯然不是我們想要的結果。為了實現真正意義上的排序,可以給 sort
傳遞一個回調函數 compareFunction, 其接收兩個參數a
和 b
,分別代表數組中待比較的兩個元素,a 元素 排在 b 的前面 。
回調函數需要返回一個表達式,用以標明 升序 或 降序 操作:
return a - b
:如果表達式 a - b 為真,觸發交換操作。也就是說, 當 a > b 時,進行元素交換,讓較小的元素 b 排在較大的元素 a 前面,即 升序操作。return b - a
:如果表達式 b - a 為真,觸發交換操作。也就是說, 當 a < b 時,進行元素交換,讓較大的元素 b 排在較小的元素 a 前面,即 降序操作。
var numbers = [2, 4, 1, 10, 3]; // 回調參數 a,b 是數組要比較的兩個元素,a 排在 b 的前面。 numbers.sort(function(a, b){ // 當 a > b 時觸發交換操作,把較小的排在前面,即升序。 return a - b; }); > [1,2,3,4,10] numbers.sort(function(a,b){ // 當 a < b 時觸發交換操作,把較大的排在前面,即降序。 return b - a; }); > [10,4,3,2,1]
2、join( [separator] )
將數組中的所有元素連接成一個字符串。separtor 用於指定連接每個數組元素的分隔符。分隔符會被轉成字符串類型;如果省略的話,默認為一個逗號。如果separtor 是一個空字符串,那么數組中的所有元素將被直接連接。
var data = ['Wind', 'Rain', 'Fire']; data.join(); // Wind,Rain,Fire data.join(', '); // Wind, Rain, Fire data.join(' + '); // Wind + Rain + Fire data.join(''); // WindRainFire
3、concat( value1,...,valueN )
concat 方法將創建一個新的數組,然后將調用它的對象(this 指向的對象)中的元素以及所有參數中的數組類型的參數中的元素以及非數組類型的參數本身按照順序放入這個新數組,並返回該數組, valueN 允許是數組或非數組值。在沒有給 concat 傳遞參數的情況下,它只是復制當前數組並返回副本。
var alpha = ['a', 'b', 'c']; alpha.concat(1, [2, 3]); //["a", "b", "c", 1, 2, 3] alpha.concat(); // ['a', 'b', 'c']
4、push( element1,...,elementN ) 和 pop( )
push 添加一個或多個元素到數組的末尾,並返回數組新的長度;pop刪除一個數組中的最后的一個元素,並且返回這個元素。
var data = [1, 2, 3]; data.push(4, 5, 6); // 6 > 數組的長度 data > [1,2,3,4,5,6] data.pop(); //6 > 出棧的元素 data > [1,2,3,4,5]
注意:push 和 pop 並不會改變原來的元素位置。
5、unshift( element1, ..., elementN ) 和 shift( )
unshift 添加一個或多個元素到數組的開頭,並返回數組新的長度;shift 刪除一個數組中的第一個元素,並且返回這個元素。
var data = [1, 2, 3]; data.unshift(-1, -2, -3); // 6 > 新數組的長度 data > [-1,-2,-3,1,2,3] data.shift(); // -1 > 被移除的元素 data > [-2,-3,1,2,3]
注意:unshift 和 shift 都會改變原來的元素位置。
如果把數組看成一個棧,push 和 pop 、unshift 和 shift 對數組的操作行為就符合 后進先出 (LIFO) 規律 ;如果把數組看成一個隊列,push
和 shift
、unshift
和 pop
對數組的操作行為就符合 先進先出 (FIFO) 規律。
因此,可以使用數組來實現對應的數據結構:棧 和 隊列。
6、slice( begin [, end] )
slice 方法從begin 的索引位置開始提取數組元素,到 end 位置結束,但不包括 end 位置的元素,如果 end 被省略,則默認提取到數組的結尾,如果結束位置小於起始位置,則返回空數組。
var data = [1, 2, 3]; data.slice(0); // [1,2,3] 提取的元素 data.slice(1, 2); // [2] 提取的元素 data.slice(2, 1); // []
如果參數中有一個負數,則用數組長度加上該數來確定相應的位置。例如,在一個包含 5 項的數組上調用 slice(-2, -1) 與調用 slice(3, 4) 得到的結果相同。
var t = [1, 2, 3, 4, 5]; t.slice(-2, -1); // [4] t.slice(3, 4); // [4]
slice方法會返回一個新的數組,由於數組是引用類型, 通常會通過 arr.slice(0) 來實現數組的 淺拷貝,但這只對 數組元素是基本類型 的情況有效。
// 簡單數組拷貝 var arr = [1, 2, 3, 4]; var cyarr = arr.slice(0); // 淺拷貝 arr.splice(3, 1); // 對原數組操作 console.log(arr, cyarr); //[1,2,3] , [1,2,3,4] > 拷貝成功
如果是對象數組,數組元素作為指針指向對象內存,slice(0) 僅僅是拷貝了一份指針作為副本,而副本中的指針,指向的還是原來的對象,因此,對一個數組中的對象做操作,另一個數組中的對象也會同步變化。
//對象數組拷貝 var list = [{name: 'zhangsan'}]; var cylist = list.slice(0); // 淺拷貝 list[0].name = 'lisi'; // 對原數組操作 console.log(list, cylist); // [{name:'lisi'}] , [{name:'lisi'}] -> 拷貝失敗
要實現數組的深拷貝,需要通過 JSON
的序列化和反序列化來實現,即: JSON.parse( JSON.stringify(arr) )
//對象數組拷貝 var list = [{name: 'zhangsan'}]; var cylist = JSON.parse(JSON.stringify(list)); // 深拷貝 list[0].name = 'lisi'; // 對原數組操作 console.log(list, cylist); // [{name: 'lisi'}] , [{name: 'zhangsan'}] > 拷貝成功
7、splice( start, deleteCount[,value1,...,valueN] )
splice方法從一個數組中移除一個或多個元素,如果必要,在所移除元素的位置上插入新元素,返回所移除的元素。
data.splice(2, 1); // [3] > 被刪除的元素 data > [1,2] data.splice(2, 2, 4, 5); // [3] > 被刪除的元素 data > [1,2,4,5] data.splice(2, 2, 4, 5, 6); // [3] > 被刪除的元素 data > [1,2,4,5,6]
原創發布 @一像素 2016.01