數組(array)是按次序排列的一組值。每一個值的位置都有編號(從0開始),整個數組用方括號表示。javascript
var arr = ['a', 'b', 'c'];
上面代碼中的a
、b
、c
就構成一個數組,兩端的方括號是數組的標誌。a
是0號位置,b
是1號位置,c
是2號位置。java
除了在定義時賦值,數組也能夠先定義後賦值。node
var arr = []; arr[0] = 'a'; arr[1] = 'b'; arr[2] = 'c';
任何類型的數據,均可以放入數組。算法
var arr = [ {a: 1}, [1, 2, 3], function() {return true;} ]; arr[0] // Object {a: 1} arr[1] // [1, 2, 3] arr[2] // function (){return true;}
上面數組arr
的3個成員依次是對象、數組、函數。數組
若是數組的元素仍是數組,就造成了多維數組。瀏覽器
var a = [[1, 2], [3, 4]]; a[0][1] // 2 a[1][1] // 4
本質上,數組屬於一種特殊的對象。typeof
運算符會返回數組的類型是object
。數據結構
typeof [1, 2, 3] // "object"
上面代碼代表,typeof
運算符認爲數組的類型就是對象。app
數組的特殊性體如今,它的鍵名是按次序排列的一組整數(0,1,2…)。ide
var arr = ['a', 'b', 'c']; Object.keys(arr) // ["0", "1", "2"]
上面代碼中,Object.keys
方法返回數組的全部鍵名。能夠看到數組的鍵名就是整數0、一、2。函數
因爲數組成員的鍵名是固定的(默認老是0、一、2…),所以數組不用爲每一個元素指定鍵名,而對象的每一個成員都必須指定鍵名。JavaScript 語言規定,對象的鍵名一概爲字符串,因此,數組的鍵名其實也是字符串。之因此能夠用數值讀取,是由於非字符串的鍵名會被轉爲字符串。
var arr = ['a', 'b', 'c']; arr['0'] // 'a' arr[0] // 'a'
上面代碼分別用數值和字符串做爲鍵名,結果都能讀取數組。緣由是數值鍵名被自動轉爲了字符串。
注意,這點在賦值時也成立。若是一個值老是先轉成字符串,再進行賦值。
var a = []; a[1.00] = 6; a[1] // 6
上面代碼中,因爲1.00
轉成字符串是1
,因此經過數字鍵1
能夠讀取值。
上一章說過,對象有兩種讀取成員的方法:點結構(object.key
)和方括號結構(object[key]
)。可是,對於數值的鍵名,不能使用點結構。
var arr = [1, 2, 3]; arr.0 // SyntaxError
上面代碼中,arr.0
的寫法不合法,由於單獨的數值不能做爲標識符(identifier)。因此,數組成員只能用方括號arr[0]
表示(方括號是運算符,能夠接受數值)。
數組的length
屬性,返回數組的成員數量。
['a', 'b', 'c'].length // 3
JavaScript 使用一個32位整數,保存數組的元素個數。這意味着,數組成員最多隻有 4294967295 個(232 - 1)個,也就是說length
屬性的最大值就是 4294967295。
只要是數組,就必定有length
屬性。該屬性是一個動態的值,等於鍵名中的最大整數加上1
。
var arr = ['a', 'b']; arr.length // 2 arr[2] = 'c'; arr.length // 3 arr[9] = 'd'; arr.length // 10 arr[1000] = 'e'; arr.length // 1001
上面代碼表示,數組的數字鍵不須要連續,length
屬性的值老是比最大的那個整數鍵大1
。另外,這也代表數組是一種動態的數據結構,能夠隨時增減數組的成員。
length
屬性是可寫的。若是人爲設置一個小於當前成員個數的值,該數組的成員會自動減小到length
設置的值。
var arr = [ 'a', 'b', 'c' ]; arr.length // 3 arr.length = 2; arr // ["a", "b"]
上面代碼表示,當數組的length
屬性設爲2(即最大的整數鍵只能是1)那麼整數鍵2(值爲c
)就已經不在數組中了,被自動刪除了。
清空數組的一個有效方法,就是將length
屬性設爲0。
var arr = [ 'a', 'b', 'c' ]; arr.length = 0; arr // []
若是人爲設置length
大於當前元素個數,則數組的成員數量會增長到這個值,新增的位置都是空位。
var a = ['a']; a.length = 3; a[1] // undefined
上面代碼表示,當length
屬性設爲大於數組個數時,讀取新增的位置都會返回undefined
。
若是人爲設置length
爲不合法的值,JavaScript 會報錯。
// 設置負值 [].length = -1 // RangeError: Invalid array length // 數組元素個數大於等於2的32次方 [].length = Math.pow(2, 32) // RangeError: Invalid array length // 設置字符串 [].length = 'abc' // RangeError: Invalid array length
值得注意的是,因爲數組本質上是一種對象,因此能夠爲數組添加屬性,可是這不影響length
屬性的值。
var a = []; a['p'] = 'abc'; a.length // 0 a[2.1] = 'abc'; a.length // 0
上面代碼將數組的鍵分別設爲字符串和小數,結果都不影響length
屬性。由於,length
屬性的值就是等於最大的數字鍵加1,而這個數組沒有整數鍵,因此length
屬性保持爲0
。
若是數組的鍵名是添加超出範圍的數值,該鍵名會自動轉爲字符串。
var arr = []; arr[-1] = 'a'; arr[Math.pow(2, 32)] = 'b'; arr.length // 0 arr[-1] // "a" arr[4294967296] // "b"
上面代碼中,咱們爲數組arr
添加了兩個不合法的數字鍵,結果length
屬性沒有發生變化。這些數字鍵都變成了字符串鍵名。最後兩行之因此會取到值,是由於取鍵值時,數字鍵名會默認轉爲字符串。
檢查某個鍵名是否存在的運算符in
,適用於對象,也適用於數組。
var arr = [ 'a', 'b', 'c' ]; 2 in arr // true '2' in arr // true 4 in arr // false
上面代碼代表,數組存在鍵名爲2
的鍵。因爲鍵名都是字符串,因此數值2
會自動轉成字符串。
注意,若是數組的某個位置是空位,in
運算符返回false
。
var arr = []; arr[100] = 'a'; 100 in arr // true 1 in arr // false
上面代碼中,數組arr
只有一個成員arr[100]
,其餘位置的鍵名都會返回false
。
for...in
循環不只能夠遍歷對象,也能夠遍歷數組,畢竟數組只是一種特殊對象。
var a = [1, 2, 3]; for (var i in a) { console.log(a[i]); } // 1 // 2 // 3
可是,for...in
不只會遍歷數組全部的數字鍵,還會遍歷非數字鍵。
var a = [1, 2, 3]; a.foo = true; for (var key in a) { console.log(key); } // 0 // 1 // 2 // foo
上面代碼在遍歷數組時,也遍歷到了非整數鍵foo
。因此,不推薦使用for...in
遍歷數組。
數組的遍歷能夠考慮使用for
循環或while
循環。
var a = [1, 2, 3]; // for循環 for(var i = 0; i < a.length; i++) { console.log(a[i]); } // while循環 var i = 0; while (i < a.length) { console.log(a[i]); i++; } var l = a.length; while (l--) { console.log(a[l]); }
上面代碼是三種遍歷數組的寫法。最後一種寫法是逆向遍歷,即從最後一個元素向第一個元素遍歷。
數組的forEach
方法,也能夠用來遍歷數組,詳見《標準庫》的 Array 對象一章。
var colors = ['red', 'green', 'blue']; colors.forEach(function (color) { console.log(color); }); // red // green // blue
當數組的某個位置是空元素,即兩個逗號之間沒有任何值,咱們稱該數組存在空位(hole)。
var a = [1, , 1]; a.length // 3
上面代碼代表,數組的空位不影響length
屬性。
須要注意的是,若是最後一個元素後面有逗號,並不會產生空位。也就是說,有沒有這個逗號,結果都是同樣的。
var a = [1, 2, 3,]; a.length // 3 a // [1, 2, 3]
上面代碼中,數組最後一個成員後面有一個逗號,這不影響length
屬性的值,與沒有這個逗號時效果同樣。
數組的空位是能夠讀取的,返回undefined
。
var a = [, , ,]; a[1] // undefined
使用delete
命令刪除一個數組成員,會造成空位,而且不會影響length
屬性。
var a = [1, 2, 3]; delete a[1]; a[1] // undefined a.length // 3
上面代碼用delete
命令刪除了數組的第二個元素,這個位置就造成了空位,可是對length
屬性沒有影響。也就是說,length
屬性不過濾空位。因此,使用length
屬性進行數組遍歷,必定要很是當心。
數組的某個位置是空位,與某個位置是undefined
,是不同的。若是是空位,使用數組的forEach
方法、for...in
結構、以及Object.keys
方法進行遍歷,空位都會被跳過。
var a = [, , ,]; a.forEach(function (x, i) { console.log(i + '. ' + x); }) // 不產生任何輸出 for (var i in a) { console.log(i); } // 不產生任何輸出 Object.keys(a) // []
若是某個位置是undefined
,遍歷的時候就不會被跳過。
var a = [undefined, undefined, undefined]; a.forEach(function (x, i) { console.log(i + '. ' + x); }); // 0. undefined // 1. undefined // 2. undefined for (var i in a) { console.log(i); } // 0 // 1 // 2 Object.keys(a) // ['0', '1', '2']
這就是說,空位就是數組沒有這個元素,因此不會被遍歷到,而undefined
則表示數組有這個元素,值是undefined
,因此遍歷不會跳過。
若是一個對象的全部鍵名都是正整數或零,而且有length
屬性,那麼這個對象就很像數組,語法上稱爲「相似數組的對象」(array-like object)。
var obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; obj[0] // 'a' obj[1] // 'b' obj.length // 3 obj.push('d') // TypeError: obj.push is not a function
上面代碼中,對象obj
就是一個相似數組的對象。可是,「相似數組的對象」並非數組,由於它們不具有數組特有的方法。對象obj
沒有數組的push
方法,使用該方法就會報錯。
「相似數組的對象」的根本特徵,就是具備length
屬性。只要有length
屬性,就能夠認爲這個對象相似於數組。可是有一個問題,這種length
屬性不是動態值,不會隨着成員的變化而變化。
var obj = { length: 0 }; obj[3] = 'd'; obj.length // 0
上面代碼爲對象obj
添加了一個數字鍵,可是length
屬性沒變。這就說明了obj
不是數組。
典型的「相似數組的對象」是函數的arguments
對象,以及大多數 DOM 元素集,還有字符串。
// arguments對象 function args() { return arguments } var arrayLike = args('a', 'b'); arrayLike[0] // 'a' arrayLike.length // 2 arrayLike instanceof Array // false // DOM元素集 var elts = document.getElementsByTagName('h3'); elts.length // 3 elts instanceof Array // false // 字符串 'abc'[1] // 'b' 'abc'.length // 3 'abc' instanceof Array // false
上面代碼包含三個例子,它們都不是數組(instanceof
運算符返回false
),可是看上去都很是像數組。
數組的slice
方法能夠將「相似數組的對象」變成真正的數組。
var arr = Array.prototype.slice.call(arrayLike);
除了轉爲真正的數組,「相似數組的對象」還有一個辦法可使用數組的方法,就是經過call()
把數組的方法放到對象上面。
function print(value, index) { console.log(index + ' : ' + value); } Array.prototype.forEach.call(arrayLike, print);
上面代碼中,arrayLike
表明一個相似數組的對象,原本是不可使用數組的forEach()
方法的,可是經過call()
,能夠把forEach()
嫁接到arrayLike
上面調用。
下面的例子就是經過這種方法,在arguments
對象上面調用forEach
方法。
// forEach 方法 function logArgs() { Array.prototype.forEach.call(arguments, function (elem, i) { console.log(i + '. ' + elem); }); } // 等同於 for 循環 function logArgs() { for (var i = 0; i < arguments.length; i++) { console.log(i + '. ' + arguments[i]); } }
字符串也是相似數組的對象,因此也能夠用Array.prototype.forEach.call
遍歷。
Array.prototype.forEach.call('abc', function (chr) { console.log(chr); }); // a // b // c
注意,這種方法比直接使用數組原生的forEach
要慢,因此最好仍是先將「相似數組的對象」轉爲真正的數組,而後再直接調用數組的forEach
方法。
var arr = Array.prototype.slice.call('abc'); arr.forEach(function (chr) { console.log(chr); }); // a // b // c
Array
是 JavaScript 的原生對象,同時也是一個構造函數,能夠用它生成新的數組。
var arr = new Array(2); arr.length // 2 arr // [ empty x 2 ]
上面代碼中,Array
構造函數的參數2
,表示生成一個兩個成員的數組,每一個位置都是空值。
若是沒有使用new
,運行結果也是同樣的。
var arr = new Array(2); // 等同於 var arr = Array(2);
Array
構造函數有一個很大的缺陷,就是不一樣的參數,會致使它的行爲不一致。
// 無參數時,返回一個空數組 new Array() // [] // 單個正整數參數,表示返回的新數組的長度 new Array(1) // [ empty ] new Array(2) // [ empty x 2 ] // 非正整數的數值做爲參數,會報錯 new Array(3.2) // RangeError: Invalid array length new Array(-3) // RangeError: Invalid array length // 單個非數值(好比字符串、布爾值、對象等)做爲參數, // 則該參數是返回的新數組的成員 new Array('abc') // ['abc'] new Array([1]) // [Array[1]] // 多參數時,全部參數都是返回的新數組的成員 new Array(1, 2) // [1, 2] new Array('a', 'b', 'c') // ['a', 'b', 'c']
能夠看到,Array
做爲構造函數,行爲很不一致。所以,不建議使用它生成新數組,直接使用數組字面量是更好的作法。
// bad var arr = new Array(1, 2); // good var arr = [1, 2];
注意,若是參數是一個正整數,返回數組的成員都是空位。雖然讀取的時候返回undefined
,但實際上該位置沒有任何值。雖然能夠取到length
屬性,可是取不到鍵名。
var a = new Array(3); var b = [undefined, undefined, undefined]; a.length // 3 b.length // 3 a[0] // undefined b[0] // undefined 0 in a // false 0 in b // true
上面代碼中,a
是一個長度爲3的空數組,b
是一個三個成員都是undefined
的數組。讀取鍵值的時候,a
和b
都返回undefined
,可是a
的鍵位都是空的,b
的鍵位是有值的。
Array.isArray
方法返回一個布爾值,表示參數是否爲數組。它能夠彌補typeof
運算符的不足。
var arr = [1, 2, 3]; typeof arr // "object" Array.isArray(arr) // true
上面代碼中,typeof
運算符只能顯示數組的類型是Object
,而Array.isArray
方法能夠識別數組。
valueOf
方法是一個全部對象都擁有的方法,表示對該對象求值。不一樣對象的valueOf
方法不盡一致,數組的valueOf
方法返回數組自己。
var arr = [1, 2, 3]; arr.valueOf() // [1, 2, 3]
toString
方法也是對象的通用方法,數組的toString
方法返回數組的字符串形式。
var arr = [1, 2, 3]; arr.toString() // "1,2,3" var arr = [1, 2, 3, [4, 5, 6]]; arr.toString() // "1,2,3,4,5,6"
push
方法用於在數組的末端添加一個或多個元素,並返回添加新元素後的數組長度。注意,該方法會改變原數組。
var arr = []; arr.push(1) // 1 arr.push('a') // 2 arr.push(true, {}) // 4 arr // [1, 'a', true, {}]
上面代碼使用push
方法,往數組中添加了四個成員。
pop
方法用於刪除數組的最後一個元素,並返回該元素。注意,該方法會改變原數組。
var arr = ['a', 'b', 'c']; arr.pop() // 'c' arr // ['a', 'b']
對空數組使用pop
方法,不會報錯,而是返回undefined
。
[].pop() // undefined
push
和pop
結合使用,就構成了「後進先出」的棧結構(stack)。
var arr = []; arr.push(1, 2); arr.push(3); arr.pop(); arr // [1, 2]
上面代碼中,3
是最後進入數組的,可是最先離開數組。
shift
方法用於刪除數組的第一個元素,並返回該元素。注意,該方法會改變原數組。
var a = ['a', 'b', 'c']; a.shift() // 'a' a // ['b', 'c']
shift
方法能夠遍歷並清空一個數組。
var list = [1, 2, 3, 4, 5, 6]; var item; while (item = list.shift()) { console.log(item); } list // []
push
和shift
結合使用,就構成了「先進先出」的隊列結構(queue)。
unshift
方法用於在數組的第一個位置添加元素,並返回添加新元素後的數組長度。注意,該方法會改變原數組。
var a = ['a', 'b', 'c']; a.unshift('x'); // 4 a // ['x', 'a', 'b', 'c']
unshift
方法能夠接受多個參數,這些參數都會添加到目標數組頭部。
var arr = [ 'c', 'd' ]; arr.unshift('a', 'b') // 4 arr // [ 'a', 'b', 'c', 'd' ]
join
方法以指定參數做爲分隔符,將全部數組成員鏈接爲一個字符串返回。若是不提供參數,默認用逗號分隔。
var a = [1, 2, 3, 4]; a.join(' ') // '1 2 3 4' a.join(' | ') // "1 | 2 | 3 | 4" a.join() // "1,2,3,4"
若是數組成員是undefined
或null
或空位,會被轉成空字符串。
[undefined, null].join('#') // '#' ['a',, 'b'].join('-') // 'a--b'
經過call
方法,這個方法也能夠用於字符串或相似數組的對象。
Array.prototype.join.call('hello', '-') // "h-e-l-l-o" var obj = { 0: 'a', 1: 'b', length: 2 }; Array.prototype.join.call(obj, '-') // 'a-b'
concat
方法用於多個數組的合併。它將新數組的成員,添加到原數組成員的後部,而後返回一個新數組,原數組不變。
['hello'].concat(['world']) // ["hello", "world"] ['hello'].concat(['world'], ['!']) // ["hello", "world", "!"] [].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }] [2].concat({a: 1}) // [2, {a: 1}]
除了數組做爲參數,concat
也接受其餘類型的值做爲參數,添加到目標數組尾部。
[1, 2, 3].concat(4, 5, 6) // [1, 2, 3, 4, 5, 6]
若是數組成員包括對象,concat
方法返回當前數組的一個淺拷貝。所謂「淺拷貝」,指的是新數組拷貝的是對象的引用。
var obj = { a: 1 }; var oldArray = [obj]; var newArray = oldArray.concat(); obj.a = 2; newArray[0].a // 2
上面代碼中,原數組包含一個對象,concat
方法生成的新數組包含這個對象的引用。因此,改變原對象之後,新數組跟着改變。
reverse
方法用於顛倒排列數組元素,返回改變後的數組。注意,該方法將改變原數組。
var a = ['a', 'b', 'c']; a.reverse() // ["c", "b", "a"] a // ["c", "b", "a"]
slice
方法用於提取目標數組的一部分,返回一個新數組,原數組不變。
arr.slice(start, end);
它的第一個參數爲起始位置(從0開始),第二個參數爲終止位置(但該位置的元素自己不包括在內)。若是省略第二個參數,則一直返回到原數組的最後一個成員。
var a = ['a', 'b', 'c']; a.slice(0) // ["a", "b", "c"] a.slice(1) // ["b", "c"] a.slice(1, 2) // ["b"] a.slice(2, 6) // ["c"] a.slice() // ["a", "b", "c"]
上面代碼中,最後一個例子slice
沒有參數,實際上等於返回一個原數組的拷貝。
若是slice
方法的參數是負數,則表示倒數計算的位置。
var a = ['a', 'b', 'c']; a.slice(-2) // ["b", "c"] a.slice(-2, -1) // ["b"]
上面代碼中,-2
表示倒數計算的第二個位置,-1
表示倒數計算的第一個位置。
若是第一個參數大於等於數組長度,或者第二個參數小於第一個參數,則返回空數組。
var a = ['a', 'b', 'c']; a.slice(4) // [] a.slice(2, 1) // []
slice
方法的一個重要應用,是將相似數組的對象轉爲真正的數組。
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b'] Array.prototype.slice.call(document.querySelectorAll("div")); Array.prototype.slice.call(arguments);
上面代碼的參數都不是數組,可是經過call
方法,在它們上面調用slice
方法,就能夠把它們轉爲真正的數組。
splice
方法用於刪除原數組的一部分紅員,並能夠在刪除的位置添加新的數組成員,返回值是被刪除的元素。注意,該方法會改變原數組。
arr.splice(start, count, addElement1, addElement2, ...);
splice
的第一個參數是刪除的起始位置(從0開始),第二個參數是被刪除的元素個數。若是後面還有更多的參數,則表示這些就是要被插入數組的新元素。
var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2) // ["e", "f"] a // ["a", "b", "c", "d"]
上面代碼從原數組4號位置,刪除了兩個數組成員。
var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2, 1, 2) // ["e", "f"] a // ["a", "b", "c", "d", 1, 2]
上面代碼除了刪除成員,還插入了兩個新成員。
起始位置若是是負數,就表示從倒數位置開始刪除。
var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(-4, 2) // ["c", "d"]
上面代碼表示,從倒數第四個位置c
開始刪除兩個成員。
若是隻是單純地插入元素,splice
方法的第二個參數能夠設爲0
。
var a = [1, 1, 1]; a.splice(1, 0, 2) // [] a // [1, 2, 1, 1]
若是隻提供第一個參數,等同於將原數組在指定位置拆分紅兩個數組。
var a = [1, 2, 3, 4]; a.splice(2) // [3, 4] a // [1, 2]
sort
方法對數組成員進行排序,默認是按照字典順序排序。排序後,原數組將被改變。
['d', 'c', 'b', 'a'].sort() // ['a', 'b', 'c', 'd'] [4, 3, 2, 1].sort() // [1, 2, 3, 4] [11, 101].sort() // [101, 11] [10111, 1101, 111].sort() // [10111, 1101, 111]
上面代碼的最後兩個例子,須要特殊注意。sort
方法不是按照大小排序,而是按照字典順序。也就是說,數值會被先轉成字符串,再按照字典順序進行比較,因此101
排在11
的前面。
若是想讓sort
方法按照自定義方式排序,能夠傳入一個函數做爲參數。
[10111, 1101, 111].sort(function (a, b) { return a - b; }) // [111, 1101, 10111]
上面代碼中,sort
的參數函數自己接受兩個參數,表示進行比較的兩個數組成員。若是該函數的返回值大於0
,表示第一個成員排在第二個成員後面;其餘狀況下,都是第一個元素排在第二個元素前面。
[ { name: "張三", age: 30 }, { name: "李四", age: 24 }, { name: "王五", age: 28 } ].sort(function (o1, o2) { return o1.age - o2.age; }) // [ // { name: "李四", age: 24 }, // { name: "王五", age: 28 }, // { name: "張三", age: 30 } // ]
map
方法將數組的全部成員依次傳入參數函數,而後把每一次的執行結果組成一個新數組返回。
var numbers = [1, 2, 3]; numbers.map(function (n) { return n + 1; }); // [2, 3, 4] numbers // [1, 2, 3]
上面代碼中,numbers
數組的全部成員依次執行參數函數,運行結果組成一個新數組返回,原數組沒有變化。
map
方法接受一個函數做爲參數。該函數調用時,map
方法向它傳入三個參數:當前成員、當前位置和數組自己。
[1, 2, 3].map(function(elem, index, arr) { return elem * index; }); // [0, 2, 6]
上面代碼中,map
方法的回調函數有三個參數,elem
爲當前成員的值,index
爲當前成員的位置,arr
爲原數組([1, 2, 3]
)。
map
方法還能夠接受第二個參數,用來綁定回調函數內部的this
變量(詳見《this 變量》一章)。
var arr = ['a', 'b', 'c']; [1, 2].map(function (e) { return this[e]; }, arr) // ['b', 'c']
上面代碼經過map
方法的第二個參數,將回調函數內部的this
對象,指向arr
數組。
若是數組有空位,map
方法的回調函數在這個位置不會執行,會跳過數組的空位。
var f = function (n) { return 'a' }; [1, undefined, 2].map(f) // ["a", "a", "a"] [1, null, 2].map(f) // ["a", "a", "a"] [1, , 2].map(f) // ["a", , "a"]
上面代碼中,map
方法不會跳過undefined
和null
,可是會跳過空位。
forEach
方法與map
方法很類似,也是對數組的全部成員依次執行參數函數。可是,forEach
方法不返回值,只用來操做數據。這就是說,若是數組遍歷的目的是爲了獲得返回值,那麼使用map
方法,不然使用forEach
方法。
forEach
的用法與map
方法一致,參數是一個函數,該函數一樣接受三個參數:當前值、當前位置、整個數組。
function log(element, index, array) { console.log('[' + index + '] = ' + element); } [2, 5, 9].forEach(log); // [0] = 2 // [1] = 5 // [2] = 9
上面代碼中,forEach
遍歷數組不是爲了獲得返回值,而是爲了在屏幕輸出內容,因此沒必要使用map
方法。
forEach
方法也能夠接受第二個參數,綁定參數函數的this
變量。
var out = []; [1, 2, 3].forEach(function(elem) { this.push(elem * elem); }, out); out // [1, 4, 9]
上面代碼中,空數組out
是forEach
方法的第二個參數,結果,回調函數內部的this
關鍵字就指向out
。
注意,forEach
方法沒法中斷執行,老是會將全部成員遍歷完。若是但願符合某種條件時,就中斷遍歷,要使用for
循環。
var arr = [1, 2, 3]; for (var i = 0; i < arr.length; i++) { if (arr[i] === 2) break; console.log(arr[i]); } // 1
上面代碼中,執行到數組的第二個成員時,就會中斷執行。forEach
方法作不到這一點。
forEach
方法也會跳過數組的空位。
var log = function (n) { console.log(n + 1); }; [1, undefined, 2].forEach(log) // 2 // NaN // 3 [1, null, 2].forEach(log) // 2 // 1 // 3 [1, , 2].forEach(log) // 2 // 3
上面代碼中,forEach
方法不會跳過undefined
和null
,但會跳過空位。
filter
方法用於過濾數組成員,知足條件的成員組成一個新數組返回。
它的參數是一個函數,全部數組成員依次執行該函數,返回結果爲true
的成員組成一個新數組返回。該方法不會改變原數組。
[1, 2, 3, 4, 5].filter(function (elem) { return (elem > 3); }) // [4, 5]
上面代碼將大於3
的數組成員,做爲一個新數組返回。
var arr = [0, 1, 'a', false]; arr.filter(Boolean) // [1, "a"]
上面代碼中,filter
方法返回數組arr
裏面全部布爾值爲true
的成員。
filter
方法的參數函數能夠接受三個參數:當前成員,當前位置和整個數組。
[1, 2, 3, 4, 5].filter(function (elem, index, arr) { return index % 2 === 0; }); // [1, 3, 5]
上面代碼返回偶數位置的成員組成的新數組。
filter
方法還能夠接受第二個參數,用來綁定參數函數內部的this
變量。
var obj = { MAX: 3 }; var myFilter = function (item) { if (item > this.MAX) return true; }; var arr = [2, 8, 3, 4, 1, 3, 2, 9]; arr.filter(myFilter, obj) // [8, 4, 9]
上面代碼中,過濾器myFilter
內部有this
變量,它能夠被filter
方法的第二個參數obj
綁定,返回大於3
的成員。
這兩個方法相似「斷言」(assert),返回一個布爾值,表示判斷數組成員是否符合某種條件。
它們接受一個函數做爲參數,全部數組成員依次執行該函數。該函數接受三個參數:當前成員、當前位置和整個數組,而後返回一個布爾值。
some
方法是隻要一個成員的返回值是true
,則整個some
方法的返回值就是true
,不然返回false
。
var arr = [1, 2, 3, 4, 5]; arr.some(function (elem, index, arr) { return elem >= 3; }); // true
上面代碼中,若是數組arr
有一個成員大於等於3,some
方法就返回true
。
every
方法是全部成員的返回值都是true
,整個every
方法才返回true
,不然返回false
。
var arr = [1, 2, 3, 4, 5]; arr.every(function (elem, index, arr) { return elem >= 3; }); // false
上面代碼中,數組arr
並不是全部成員大於等於3
,因此返回false
。
注意,對於空數組,some
方法返回false
,every
方法返回true
,回調函數都不會執行。
function isEven(x) { return x % 2 === 0 } [].some(isEven) // false [].every(isEven) // true
some
和every
方法還能夠接受第二個參數,用來綁定參數函數內部的this
變量。
reduce
方法和reduceRight
方法依次處理數組的每一個成員,最終累計爲一個值。它們的差異是,reduce
是從左到右處理(從第一個成員到最後一個成員),reduceRight
則是從右到左(從最後一個成員到第一個成員),其餘徹底同樣。
[1, 2, 3, 4, 5].reduce(function (a, b) { console.log(a, b); return a + b; }) // 1 2 // 3 3 // 6 4 // 10 5 //最後結果:15
上面代碼中,reduce
方法求出數組全部成員的和。第一次執行,a
是數組的第一個成員1
,b
是數組的第二個成員2
。第二次執行,a
爲上一輪的返回值3
,b
爲第三個成員3
。第三次執行,a
爲上一輪的返回值6
,b
爲第四個成員4
。第四次執行,a
爲上一輪返回值10
,b
爲第五個成員5
。至此全部成員遍歷完成,整個方法的返回值就是最後一輪的返回值15
。
reduce
方法和reduceRight
方法的第一個參數都是一個函數。該函數接受如下四個參數。
這四個參數之中,只有前兩個是必須的,後兩個則是可選的。
若是要對累積變量指定初值,能夠把它放在reduce
方法和reduceRight
方法的第二個參數。
[1, 2, 3, 4, 5].reduce(function (a, b) { return a + b; }, 10); // 25
上面代碼指定參數a
的初值爲10,因此數組從10開始累加,最終結果爲25。注意,這時b
是從數組的第一個成員開始遍歷。
上面的第二個參數至關於設定了默認值,處理空數組時尤爲有用。
function add(prev, cur) { return prev + cur; } [].reduce(add) // TypeError: Reduce of empty array with no initial value [].reduce(add, 1) // 1
上面代碼中,因爲空數組取不到初始值,reduce
方法會報錯。這時,加上第二個參數,就能保證老是會返回一個值。
下面是一個reduceRight
方法的例子。
function substract(prev, cur) { return prev - cur; } [3, 2, 1].reduce(substract) // 0 [3, 2, 1].reduceRight(substract) // -4
上面代碼中,reduce
方法至關於3
減去2
再減去1
,reduceRight
方法至關於1
減去2
再減去3
。
因爲這兩個方法會遍歷數組,因此實際上還能夠用來作一些遍歷相關的操做。好比,找出字符長度最長的數組成員。
function findLongest(entries) { return entries.reduce(function (longest, entry) { return entry.length > longest.length ? entry : longest; }, ''); } findLongest(['aaa', 'bb', 'c']) // "aaa"
上面代碼中,reduce
的參數函數會將字符長度較長的那個數組成員,做爲累積值。這致使遍歷全部成員以後,累積值就是字符長度最長的那個成員。
indexOf
方法返回給定元素在數組中第一次出現的位置,若是沒有出現則返回-1
。
var a = ['a', 'b', 'c']; a.indexOf('b') // 1 a.indexOf('y') // -1
indexOf
方法還能夠接受第二個參數,表示搜索的開始位置。
['a', 'b', 'c'].indexOf('a', 1) // -1
上面代碼從1號位置開始搜索字符a
,結果爲-1
,表示沒有搜索到。
lastIndexOf
方法返回給定元素在數組中最後一次出現的位置,若是沒有出現則返回-1
。
var a = [2, 5, 9, 2]; a.lastIndexOf(2) // 3 a.lastIndexOf(7) // -1
注意,這兩個方法不能用來搜索NaN
的位置,即它們沒法肯定數組成員是否包含NaN
。
[NaN].indexOf(NaN) // -1 [NaN].lastIndexOf(NaN) // -1
這是由於這兩個方法內部,使用嚴格相等運算符(===
)進行比較,而NaN
是惟一一個不等於自身的值。
上面這些數組方法之中,有很多返回的仍是數組,因此能夠鏈式使用。
var users = [ {name: 'tom', email: 'tom@example.com'}, {name: 'peter', email: 'peter@example.com'} ]; users .map(function (user) { return user.email; }) .filter(function (email) { return /^t/.test(email); }) .forEach(console.log); // "tom@example.com"
上面代碼中,先產生一個全部 Email 地址組成的數組,而後再過濾出以t
開頭的 Email 地址。
擴展運算符(spread)是三個點(...
)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>]
該運算符主要用於函數調用。
function push(array, ...items) { array.push(...items); } function add(x, y) { return x + y; } const numbers = [4, 38]; add(...numbers) // 42
上面代碼中,array.push(...items)
和add(...numbers)
這兩行,都是函數的調用,它們的都使用了擴展運算符。該運算符將一個數組,變爲參數序列。
擴展運算符與正常的函數參數能夠結合使用,很是靈活。
function f(v, w, x, y, z) { } const args = [0, 1]; f(-1, ...args, 2, ...[3]);
擴展運算符後面還能夠放置表達式。
const arr = [ ...(x > 0 ? ['a'] : []), 'b', ];
若是擴展運算符後面是一個空數組,則不產生任何效果。
[...[], 1] // [1]
注意,只有函數調用時,擴展運算符才能夠放在圓括號中,不然會報錯。
(...[1, 2]) // Uncaught SyntaxError: Unexpected number console.log((...[1, 2])) // Uncaught SyntaxError: Unexpected number console.log(...[1, 2]) // 1 2
上面三種狀況,擴展運算符都放在圓括號裏面,可是前兩種狀況會報錯,由於擴展運算符所在的括號不是函數調用。
因爲擴展運算符能夠展開數組,因此再也不須要apply
方法,將數組轉爲函數的參數了。
// ES5 的寫法 function f(x, y, z) { // ... } var args = [0, 1, 2]; f.apply(null, args); // ES6的寫法 function f(x, y, z) { // ... } let args = [0, 1, 2]; f(...args);
下面是擴展運算符取代apply
方法的一個實際的例子,應用Math.max
方法,簡化求出一個數組最大元素的寫法。
// ES5 的寫法 Math.max.apply(null, [14, 3, 77]) // ES6 的寫法 Math.max(...[14, 3, 77]) // 等同於 Math.max(14, 3, 77);
上面代碼中,因爲 JavaScript 不提供求數組最大元素的函數,因此只能套用Math.max
函數,將數組轉爲一個參數序列,而後求最大值。有了擴展運算符之後,就能夠直接用Math.max
了。
另外一個例子是經過push
函數,將一個數組添加到另外一個數組的尾部。
// ES5的 寫法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; Array.prototype.push.apply(arr1, arr2); // ES6 的寫法 let arr1 = [0, 1, 2]; let arr2 = [3, 4, 5]; arr1.push(...arr2);
上面代碼的 ES5 寫法中,push
方法的參數不能是數組,因此只好經過apply
方法變通使用push
方法。有了擴展運算符,就能夠直接將數組傳入push
方法。
下面是另一個例子。
// ES5 new (Date.bind.apply(Date, [null, 2015, 1, 1])) // ES6 new Date(...[2015, 1, 1]);
(1)複製數組
數組是複合的數據類型,直接複製的話,只是複製了指向底層數據結構的指針,而不是克隆一個全新的數組。
const a1 = [1, 2]; const a2 = a1; a2[0] = 2; a1 // [2, 2]
上面代碼中,a2
並非a1
的克隆,而是指向同一份數據的另外一個指針。修改a2
,會直接致使a1
的變化。
ES5 只能用變通方法來複製數組。
const a1 = [1, 2]; const a2 = a1.concat(); a2[0] = 2; a1 // [1, 2]
上面代碼中,a1
會返回原數組的克隆,再修改a2
就不會對a1
產生影響。
擴展運算符提供了複製數組的簡便寫法。
const a1 = [1, 2]; // 寫法一 const a2 = [...a1]; // 寫法二 const [...a2] = a1;
上面的兩種寫法,a2
都是a1
的克隆。
(2)合併數組
擴展運算符提供了數組合並的新寫法。
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合併數組 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合併數組 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
不過,這兩種方法都是淺拷貝,使用的時候須要注意。
const a1 = [{ foo: 1 }]; const a2 = [{ bar: 2 }]; const a3 = a1.concat(a2); const a4 = [...a1, ...a2]; a3[0] === a1[0] // true a4[0] === a1[0] // true
上面代碼中,a3
和a4
是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。
(3)與解構賦值結合
擴展運算符能夠與解構賦值結合起來,用於生成數組。
// ES5 a = list[0], rest = list.slice(1) // ES6 [a, ...rest] = list
下面是另一些例子。
const [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5] const [first, ...rest] = []; first // undefined rest // [] const [first, ...rest] = ["foo"]; first // "foo" rest // []
若是將擴展運算符用於數組賦值,只能放在參數的最後一位,不然會報錯。
const [...butLast, last] = [1, 2, 3, 4, 5]; // 報錯 const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 報錯
(4)字符串
擴展運算符還能夠將字符串轉爲真正的數組。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
上面的寫法,有一個重要的好處,那就是可以正確識別四個字節的 Unicode 字符。
'x\uD83D\uDE80y'.length // 4 [...'x\uD83D\uDE80y'].length // 3
上面代碼的第一種寫法,JavaScript 會將四個字節的 Unicode 字符,識別爲 2 個字符,採用擴展運算符就沒有這個問題。所以,正確返回字符串長度的函數,能夠像下面這樣寫。
function length(str) { return [...str].length; } length('x\uD83D\uDE80y') // 3
凡是涉及到操做四個字節的 Unicode 字符的函數,都有這個問題。所以,最好都用擴展運算符改寫。
let str = 'x\uD83D\uDE80y'; str.split('').reverse().join('') // 'y\uDE80\uD83Dx' [...str].reverse().join('') // 'y\uD83D\uDE80x'
上面代碼中,若是不用擴展運算符,字符串的reverse
操做就不正確。
(5)實現了 Iterator 接口的對象
任何定義了遍歷器(Iterator)接口的對象(參閱 Iterator 一章),均可以用擴展運算符轉爲真正的數組。必需要有遍歷器(Iterator)接口,才能轉爲真正的數組。
let nodeList = document.querySelectorAll('div'); let array = [...nodeList];
上面代碼中,querySelectorAll
方法返回的是一個NodeList
對象。它不是數組,而是一個相似數組的對象。這時,擴展運算符能夠將其轉爲真正的數組,緣由就在於NodeList
對象實現了 Iterator 。
Number.prototype[Symbol.iterator] = function*() { let i = 0; let num = this.valueOf(); while (i < num) { yield i++; } } console.log([...5]) // [0, 1, 2, 3, 4]
上面代碼中,先定義了Number
對象的遍歷器接口,擴展運算符將5
自動轉成Number
實例之後,就會調用這個接口,就會返回自定義的結果。
對於那些沒有部署 Iterator 接口的相似數組的對象,擴展運算符就沒法將其轉爲真正的數組。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // TypeError: Cannot spread non-iterable object. let arr = [...arrayLike];
上面代碼中,arrayLike
是一個相似數組的對象,可是沒有部署 Iterator 接口,擴展運算符就會報錯。這時,能夠改成使用Array.from
方法將arrayLike
轉爲真正的數組。
(6)Map 和 Set 結構,Generator 函數
擴展運算符內部調用的是數據結構的 Iterator 接口,所以只要具備 Iterator 接口的對象,均可以使用擴展運算符,好比 Map 結構。
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3]
Generator 函數運行後,返回一個遍歷器對象,所以也可使用擴展運算符。
const go = function*(){ yield 1; yield 2; yield 3; }; [...go()] // [1, 2, 3]
上面代碼中,變量go
是一個 Generator 函數,執行後返回的是一個遍歷器對象,對這個遍歷器對象執行擴展運算符,就會將內部遍歷獲得的值,轉爲一個數組。
若是對沒有 Iterator 接口的對象,使用擴展運算符,將會報錯。
const obj = {a: 1, b: 2}; let arr = [...obj]; // TypeError: Cannot spread non-iterable object
Array.from
方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括 ES6 新增的數據結構 Set 和 Map)。即便沒有遍歷器(Iterator)接口,也能轉爲真正的數組。
下面是一個相似數組的對象,Array.from
將它轉爲真正的數組。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5的寫法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的寫法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
實際應用中,常見的相似數組的對象是 DOM 操做返回的 NodeList 集合,以及函數內部的arguments
對象。Array.from
均可以將它們轉爲真正的數組。
// NodeList對象 let ps = document.querySelectorAll('p'); Array.from(ps).filter(p => { return p.textContent.length > 100; }); // arguments對象 function foo() { var args = Array.from(arguments); // ... }
上面代碼中,querySelectorAll
方法返回的是一個相似數組的對象,能夠將這個對象轉爲真正的數組,再使用filter
方法。
只要是部署了 Iterator 接口的數據結構,Array.from
都能將其轉爲數組。
Array.from('hello') // ['h', 'e', 'l', 'l', 'o'] let namesSet = new Set(['a', 'b']) Array.from(namesSet) // ['a', 'b']
上面代碼中,字符串和 Set 結構都具備 Iterator 接口,所以能夠被Array.from
轉爲真正的數組。
若是參數是一個真正的數組,Array.from
會返回一個如出一轍的新數組。
Array.from([1, 2, 3]) // [1, 2, 3]
值得提醒的是,擴展運算符(...
)也能夠將某些數據結構轉爲數組。
// arguments對象 function foo() { const args = [...arguments]; } // NodeList對象 [...document.querySelectorAll('div')]
擴展運算符背後調用的是遍歷器接口(Symbol.iterator
),若是一個對象沒有部署這個接口,就沒法轉換。Array.from
方法還支持相似數組的對象。所謂相似數組的對象,本質特徵只有一點,即必須有length
屬性。所以,任何有length
屬性的對象,均可以經過Array.from
方法轉爲數組,而此時擴展運算符就沒法轉換。
Array.from({ length: 3 }); // [ undefined, undefined, undefined ]
上面代碼中,Array.from
返回了一個具備三個成員的數組,每一個位置的值都是undefined
。擴展運算符轉換不了這個對象。
對於尚未部署該方法的瀏覽器,能夠用Array.prototype.slice
方法替代。
const toArray = (() => Array.from ? Array.from : obj => [].slice.call(obj) )();
Array.from
還能夠接受第二個參數,做用相似於數組的map
方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。
Array.from(arrayLike, x => x * x); // 等同於 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
下面的例子是取出一組 DOM 節點的文本內容。
let spans = document.querySelectorAll('span.name'); // map() let names1 = Array.prototype.map.call(spans, s => s.textContent); // Array.from() let names2 = Array.from(spans, s => s.textContent)
下面的例子將數組中布爾值爲false
的成員轉爲0
。
Array.from([1, , 2, , 3], (n) => n || 0) // [1, 0, 2, 0, 3]
另外一個例子是返回各類數據的類型。
function typesOf () { return Array.from(arguments, value => typeof value) } typesOf(null, [], NaN) // ['object', 'object', 'number']
若是map
函數裏面用到了this
關鍵字,還能夠傳入Array.from
的第三個參數,用來綁定this
。
Array.from()
能夠將各類值轉爲真正的數組,而且還提供map
功能。這實際上意味着,只要有一個原始的數據結構,你就能夠先對它的值進行處理,而後轉成規範的數組結構,進而就可使用數量衆多的數組方法。
Array.from({ length: 2 }, () => 'jack') // ['jack', 'jack']
上面代碼中,Array.from
的第一個參數指定了第二個參數運行的次數。這種特性可讓該方法的用法變得很是靈活。
Array.from()
的另外一個應用是,將字符串轉爲數組,而後返回字符串的長度。由於它能正確處理各類 Unicode 字符,能夠避免 JavaScript 將大於\uFFFF
的 Unicode 字符,算做兩個字符的 bug。
function countSymbols(string) { return Array.from(string).length; }
Array.of
方法用於將一組值,轉換爲數組。
Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1
這個方法的主要目的,是彌補數組構造函數Array()
的不足。由於參數個數的不一樣,會致使Array()
的行爲有差別。
Array() // [] Array(3) // [, , ,] Array(3, 11, 8) // [3, 11, 8]
上面代碼中,Array
方法沒有參數、一個參數、三個參數時,返回結果都不同。只有當參數個數很多於 2 個時,Array()
纔會返回由參數組成的新數組。參數個數只有一個時,其實是指定數組的長度。
Array.of
基本上能夠用來替代Array()
或new Array()
,而且不存在因爲參數不一樣而致使的重載。它的行爲很是統一。
Array.of() // [] Array.of(undefined) // [undefined] Array.of(1) // [1] Array.of(1, 2) // [1, 2]
Array.of
老是返回參數值組成的數組。若是沒有參數,就返回一個空數組。
Array.of
方法能夠用下面的代碼模擬實現。
function ArrayOf(){ return [].slice.call(arguments); }
數組實例的copyWithin
方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。也就是說,使用這個方法,會修改當前數組。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三個參數。
這三個參數都應該是數值,若是不是,會自動轉爲數值。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
上面代碼表示將從 3 號位直到數組結束的成員(4 和 5),複製到從 0 號位開始的位置,結果覆蓋了原來的 1 和 2。
下面是更多例子。
// 將3號位複製到0號位 [1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5] // -2至關於3號位,-1至關於4號位 [1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5] // 將3號位複製到0號位 [].copyWithin.call({length: 5, 3: 1}, 0, 3) // {0: 1, 3: 1, length: 5} // 將2號位到數組結束,複製到0號位 let i32a = new Int32Array([1, 2, 3, 4, 5]); i32a.copyWithin(0, 2); // Int32Array [3, 4, 5, 4, 5] // 對於沒有部署 TypedArray 的 copyWithin 方法的平臺 // 須要採用下面的寫法 [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); // Int32Array [4, 2, 3, 4, 5]
數組實例的find
方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,全部數組成員依次執行該回調函數,直到找出第一個返回值爲true
的成員,而後返回該成員。若是沒有符合條件的成員,則返回undefined
。
[1, 4, -5, 10].find((n) => n < 0) // -5
上面代碼找出數組中第一個小於 0 的成員。
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
上面代碼中,find
方法的回調函數能夠接受三個參數,依次爲當前的值、當前的位置和原數組。
數組實例的findIndex
方法的用法與find
方法很是相似,返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1
。
[1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
這兩個方法均可以接受第二個參數,用來綁定回調函數的this
對象。
function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); // 26
上面的代碼中,find
函數接收了第二個參數person
對象,回調函數中的this
對象指向person
對象。
另外,這兩個方法均可以發現NaN
,彌補了數組的indexOf
方法的不足。
[NaN].indexOf(NaN) // -1 [NaN].findIndex(y => Object.is(NaN, y)) // 0
上面代碼中,indexOf
方法沒法識別數組的NaN
成員,可是findIndex
方法能夠藉助Object.is
方法作到。
fill
方法使用給定值,填充一個數組。
['a', 'b', 'c'].fill(7) // [7, 7, 7] new Array(3).fill(7) // [7, 7, 7]
上面代碼代表,fill
方法用於空數組的初始化很是方便。數組中已有的元素,會被所有抹去。
fill
方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
上面代碼表示,fill
方法從 1 號位開始,向原數組填充 7,到 2 號位以前結束。
注意,若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。
let arr = new Array(3).fill({name: "Mike"}); arr[0].name = "Ben"; arr // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}] let arr = new Array(3).fill([]); arr[0].push(5); arr // [[5], [5], [5]]
ES6 提供三個新的方法——entries()
,keys()
和values()
——用於遍歷數組。它們都返回一個遍歷器對象(詳見《Iterator》一章),能夠用for...of
循環進行遍歷,惟一的區別是keys()
是對鍵名的遍歷、values()
是對鍵值的遍歷,entries()
是對鍵值對的遍歷。
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
若是不使用for...of
循環,能夠手動調用遍歷器對象的next
方法,進行遍歷。
let letter = ['a', 'b', 'c']; let entries = letter.entries(); console.log(entries.next().value); // [0, 'a'] console.log(entries.next().value); // [1, 'b'] console.log(entries.next().value); // [2, 'c']
Array.prototype.includes
方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes
方法相似。ES2016 引入了該方法。
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
該方法的第二個參數表示搜索的起始位置,默認爲0
。若是第二個參數爲負數,則表示倒數的位置,若是這時它大於數組長度(好比第二個參數爲-4
,但數組長度爲3
),則會重置爲從0
開始。
[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true
沒有該方法以前,咱們一般使用數組的indexOf
方法,檢查是否包含某個值。
if (arr.indexOf(el) !== -1) { // ... }
indexOf
方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,因此要去比較是否不等於-1
,表達起來不夠直觀。二是,它內部使用嚴格相等運算符(===
)進行判斷,這會致使對NaN
的誤判。
[NaN].indexOf(NaN) // -1
includes
使用的是不同的判斷算法,就沒有這個問題。
[NaN].includes(NaN) // true
下面代碼用來檢查當前環境是否支持該方法,若是不支持,部署一個簡易的替代版本。
const contains = (() => Array.prototype.includes ? (arr, value) => arr.includes(value) : (arr, value) => arr.some(el => el === value) )(); contains(['foo', 'bar'], 'baz'); // => false
另外,Map 和 Set 數據結構有一個has
方法,須要注意與includes
區分。
has
方法,是用來查找鍵名的,好比Map.prototype.has(key)
、WeakMap.prototype.has(key)
、Reflect.has(target, propertyKey)
。has
方法,是用來查找值的,好比Set.prototype.has(value)
、WeakSet.prototype.has(value)
。數組的成員有時仍是數組,Array.prototype.flat()
用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。
[1, 2, [3, 4]].flat() // [1, 2, 3, 4]
上面代碼中,原數組的成員裏面有一個數組,flat()
方法將子數組的成員取出來,添加在原來的位置。
flat()
默認只會「拉平」一層,若是想要「拉平」多層的嵌套數組,能夠將flat()
方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] [1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
上面代碼中,flat()
的參數爲2,表示要「拉平」兩層的嵌套數組。
若是無論有多少層嵌套,都要轉成一維數組,能夠用Infinity
關鍵字做爲參數。
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]
若是原數組有空位,flat()
方法會跳過空位。
[1, 2, , 4, 5].flat() // [1, 2, 4, 5]
flatMap()
方法對原數組的每一個成員執行一個函數(至關於執行Array.prototype.map()
),而後對返回值組成的數組執行flat()
方法。該方法返回一個新數組,不改變原數組。
// 至關於 [[2, 4], [3, 6], [4, 8]].flat() [2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]
flatMap()
只能展開一層數組。
// 至關於 [[[2]], [[4]], [[6]], [[8]]].flat() [1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
上面代碼中,遍歷函數返回的是一個雙層的數組,可是默認只能展開一層,所以flatMap()
返回的仍是一個嵌套數組。
flatMap()
方法的參數是一個遍歷函數,該函數能夠接受三個參數,分別是當前數組成員、當前數組成員的位置(從零開始)、原數組。
arr.flatMap(function callback(currentValue[, index[, array]]) { // ... }[, thisArg])
flatMap()
方法還能夠有第二個參數,用來綁定遍歷函數裏面的this
。
數組的空位指,數組的某一個位置沒有任何值。好比,Array
構造函數返回的數組都是空位。
Array(3) // [, , ,]
上面代碼中,Array(3)
返回一個具備 3 個空位的數組。
注意,空位不是undefined
,一個位置的值等於undefined
,依然是有值的。空位是沒有任何值,in
運算符能夠說明這一點。
0 in [undefined, undefined, undefined] // true 0 in [, , ,] // false
上面代碼說明,第一個數組的 0 號位置是有值的,第二個數組的 0 號位置沒有值。
ES5 對空位的處理,已經很不一致了,大多數狀況下會忽略空位。
forEach()
, filter()
, reduce()
, every()
和some()
都會跳過空位。map()
會跳過空位,但會保留這個值join()
和toString()
會將空位視爲undefined
,而undefined
和null
會被處理成空字符串。// forEach方法 [,'a'].forEach((x,i) => console.log(i)); // 1 // filter方法 ['a',,'b'].filter(x => true) // ['a','b'] // every方法 [,'a'].every(x => x==='a') // true // reduce方法 [1,,2].reduce((x,y) => x+y) // 3 // some方法 [,'a'].some(x => x !== 'a') // false // map方法 [,'a'].map(x => 1) // [,1] // join方法 [,'a',undefined,null].join('#') // "#a##" // toString方法 [,'a',undefined,null].toString() // ",a,,"
ES6 則是明確將空位轉爲undefined
。
Array.from
方法會將數組的空位,轉爲undefined
,也就是說,這個方法不會忽略空位。
Array.from(['a',,'b']) // [ "a", undefined, "b" ]
擴展運算符(...
)也會將空位轉爲undefined
。
[...['a',,'b']] // [ "a", undefined, "b" ]
copyWithin()
會連空位一塊兒拷貝。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
fill()
會將空位視爲正常的數組位置。
new Array(3).fill('a') // ["a","a","a"]
for...of
循環也會遍歷空位。
let arr = [, ,]; for (let i of arr) { console.log(1); } // 1 // 1
上面代碼中,數組arr
有兩個空位,for...of
並無忽略它們。若是改爲map
方法遍歷,空位是會跳過的。
entries()
、keys()
、values()
、find()
和findIndex()
會將空位處理成undefined
。
// entries() [...[,'a'].entries()] // [[0,undefined], [1,"a"]] // keys() [...[,'a'].keys()] // [0,1] // values() [...[,'a'].values()] // [undefined,"a"] // find() [,'a'].find(x => true) // undefined // findIndex() [,'a'].findIndex(x => true) // 0
因爲空位的處理規則很是不統一,因此建議避免出現空位。