在JavaScript中,可以通過兩種方式創(chuàng)建數(shù)組,構(gòu)造函數(shù)和數(shù)組直接量, 其中后者為首選方法。數(shù)組對象繼承自Object.prototype
,對數(shù)組執(zhí)行typeof
操作符返回‘object’
而不是‘a(chǎn)rray’
。然而執(zhí)行[] instanceof Array
返回true
。此外,還有類數(shù)組對象是問題更復(fù)雜,如字符串對象,arguments
對象。arguments
對象不是Array
的實(shí)例,但卻有個length
屬性,并且值能通過索引獲取,所以能像數(shù)組一樣通過循環(huán)操作。
在本文中,我將復(fù)習(xí)一些數(shù)組原型的方法,并探索這些方法的用法。
.forEach
.some
和.every
.join
和.concat
的區(qū)別.pop
,.push
,.shift
和.unshift
.map
.filter
.sort
.reduce
和.reduceRight
.slice
.splice
.indexOf
in
操作符.reverse
如果你想測試上面的例子,您可以復(fù)制并粘貼到您的瀏覽器的控制臺中。
.forEach
這是JavaScript原生數(shù)組方法中最簡單的方法。不用懷疑,IE7和IE8不支持此方法。
forEach
方法需要一個回調(diào)函數(shù),數(shù)組內(nèi)的每個元素都會調(diào)用一次此方法,此方法需要三個參數(shù)如下:
value
當(dāng)前操作的數(shù)組元素array
當(dāng)前數(shù)組的引用此外,可以傳遞可選的第二個參數(shù),作為每個調(diào)用函數(shù)的上下文(this
)。
['_', 't', 'a', 'n', 'i', 'f', ']'].forEach(function (value, index, array) {
this.push(String.fromCharCode(value.charCodeAt() + index + 2))
}, out = [])
out.join('')
// <- 'awesome'
.join
函數(shù)我將在下文提及,上面例子中,它將數(shù)組中的不同元素拼接在一起,類似于如下的效果:out[0] + '' + out[1] + '' + out[2] + '' + out[n]
。
我們不能用break
中斷forEach
循環(huán),拋出異常是不明智的方法。幸運(yùn)的是,我們有其他的方法中斷操作。
.some
和.every
如果你曾經(jīng)用過.NET的枚舉,這些方法的名字和.Any(x => x.IsAwesome)
和 .All(x => x.IsAwesome)
非常相似。
這些方法和.forEach
類似,需要一個包含value
,index
,和array
三個參數(shù)的回調(diào)函數(shù),并且也有一個可選的第二個上下文參數(shù)。MDN對.some
的描述如下:
some
將會給數(shù)組里的每一個元素執(zhí)行一遍回調(diào)函數(shù),直到有一個回調(diào)函數(shù)返回true位置。如果找到目標(biāo)元素,some
立即返回true
,否則some
返回false
?;卣{(diào)函數(shù)只對已經(jīng)指定值的數(shù)組索引執(zhí)行;它不會對已刪除的或未指定值的元素執(zhí)行。
max = -Infinity
satisfied = [10, 12, 10, 8, 5, 23].some(function (value, index, array) {
if (value > max) max = value
return value < 10
})
console.log(max)
// <- 12
satisfied
// <- true
注意,當(dāng)回調(diào)函數(shù)的value < 10
條件滿足時,中斷函數(shù)循環(huán)。.every
的工作行為類似,但回調(diào)函數(shù)要返回false
而不是true
。
.join
和.concat
的區(qū)別.join
方法經(jīng)常和.concat
混淆。.join
(分隔符)方法創(chuàng)建一個字符串,會將數(shù)組里面每個元素用分隔符連接。如果沒有提供分隔符,默認(rèn)的分隔符為“,”。.concat
方法創(chuàng)建一個新數(shù)組,其是對原數(shù)組的淺拷貝(注意是淺拷貝哦)。
.concat
的標(biāo)志用法:array.concat(val, val2, val3, valn)
.concat
返回一個新書組array.concat()
沒有參數(shù)的情況下,會返回原數(shù)組的淺拷貝淺拷貝意味著新數(shù)組和原數(shù)組保持相同的對象引用,這通常是好事。例如:
var a = { foo: 'bar' }
var b = [1, 2, 3, a]
var c = b.concat()
console.log(b === c)
// <- false
b[3] === a && c[3] === a
// <- true
.pop
,.push
,.shift
和.unshift
每個人都知道向數(shù)組添加元素用.push
。但你知道一次可以添加多個元素嗎?如下[].push('a', 'b', 'c', 'd', 'z')
。
.pop
方法和.push
成對使用,它返回?cái)?shù)組的末尾元素并將元素從數(shù)組移除。如果數(shù)組為空,返回void 0(undefined)
。使用.push
和.pop
我們能輕易模擬出LIFO(后進(jìn)先出或先進(jìn)后出)棧。
function Stack () {
this._stack = []
}
Stack.prototype.next = function () {
return this._stack.pop()
}
Stack.prototype.add = function () {
return this._stack.push.apply(this._stack, arguments)
}
stack = new Stack()
stack.add(1,2,3)
stack.next()
// <- 3
相反,我們可以用.unshift
和 .shift
模擬FIFO(先進(jìn)先出)隊(duì)列。
function Queue () {
this._queue = []
}
Queue.prototype.next = function () {
return this._queue.shift()
}
Queue.prototype.add = function () {
return this._queue.unshift.apply(this._queue, arguments)
}
queue = new Queue()
queue.add(1,2,3)
queue.next()
// <- 1
用.shift
或.pop
能很容易遍歷數(shù)組元素,并做一些操作。
list = [1,2,3,4,5,6,7,8,9,10]
while (item = list.shift()) {
console.log(item)
}
list
// <- []
.map
map
方法會給原數(shù)組中的每個元素(必須有值)都調(diào)用一次callback
函數(shù).callback
每次執(zhí)行后的返回值組合起來形成一個新數(shù)組。callback
函數(shù)只會在有值的索引上被調(diào)用; 那些從來沒被賦過值或者使用delete
刪除的索引則不會被調(diào)用?!狹DN
Array.prototype.map
方法和上面我們提到的.forEach
,.some
和.every
有相同的參數(shù):.map(fn(value, index, array), thisArgument)
。
values = [void 0, null, false, '']
values[7] = void 0
result = values.map(function(value, index, array){
console.log(value)
return value
})
// <- [undefined, null, false, '', undefined × 3, undefined]
undefined × 3
值解釋.map
不會在沒被賦過值或者使用delete
刪除的索引上調(diào)用,但他們?nèi)匀槐话诮Y(jié)果數(shù)組中。map
在遍歷或改變數(shù)組方面非常有用,如下所示:
// 遍歷
[1, '2', '30', '9'].map(function (value) {
return parseInt(value, 10)
})
// 1, 2, 30, 9
[97, 119, 101, 115, 111, 109, 101].map(String.fromCharCode).join('')
// <- 'awesome'
// 一個映射新對象的通用模式
items.map(function (item) {
return {
id: item.id,
name: computeName(item)
}
})
.filter
filter
對每個數(shù)組元素執(zhí)行一次回調(diào)函數(shù),并返回一個由回調(diào)函數(shù)返回true
的元素 組成的新數(shù)組?;卣{(diào)函數(shù)只會對已經(jīng)指定值的數(shù)組項(xiàng)調(diào)用。
用法例子:.filter(fn(value, index, array), thisArgument)
。把它想象成.Where(x => x.IsAwesome)
LINQ expression(如果你熟悉C#),或者SQL語句里面的WHERE
。考慮到.filter
僅返回callback
函數(shù)返回真值的值,下面是一些有趣的例子。沒有傳遞給回調(diào)函數(shù)測試的元素被簡單的跳過,不會包含進(jìn)返回的新書組里。
[void 0, null, false, '', 1].filter(function (value) {
return value
})
// <- [1]
[void 0, null, false, '', 1].filter(function (value) {
return !value
})
// <- [void 0, null, false, '']
.sort
(比較函數(shù))如果未提供比較函數(shù),元素會轉(zhuǎn)換為字符串,并按字典許排列。例如,在字典序里,“80”排在“9”之前,但實(shí)際上我們希望的是80在9之后(數(shù)字排序)。
像大部分排序函數(shù)一樣,Array.prototype.sort(fn(a,b))
需要一個包含兩個測試參數(shù)的回調(diào)函數(shù),并且要產(chǎn)生一下三種返回值之一:
a
在b
前,則返回值小于零a
和b
是等價的,則返回值等于零a
在b
后,則返回值大于零代碼
[9,80,3,10,5,6].sort()
// <- [10, 3, 5, 6, 80, 9]
[9,80,3,10,5,6].sort(function (a, b) {
return a - b
})
// <- [3, 5, 6, 9, 10, 80]
.reduce
和.reduceRight
首先reduce
函數(shù)不是很好理解,.reduce從左到右而.reduceRight
從右到左循環(huán)遍歷數(shù)組,每次調(diào)用接收目前為止的部分結(jié)果和當(dāng)前遍歷的值。
兩種方法都有如下典型用法:.reduce(callback(previousValue, currentValue, index, array), initialValue)
。
previousValue
是最后被調(diào)用的回調(diào)函數(shù)的返回值,initialValue
是開始時previousValue
被初始化的值。currentValue
是當(dāng)前被遍歷的元素值,index
是當(dāng)前元素在數(shù)組中的索引值。array
是對調(diào)用.reduce
數(shù)組的簡單引用。
一個典型的用例,使用.reduce
的求和函數(shù)。
Array.prototype.sum = function () {
return this.reduce(function (partial, value) {
return partial + value
}, 0)
};
[3,4,5,6,10].sum()
// <- 28
上面提到如果想把數(shù)組連成一個字符串,可以使用.join
。當(dāng)數(shù)組的值是對象的情況下,除非對象有能返回其合理值的valueof
或toString
方法,否則.join
的表現(xiàn)和你期望的不一樣。然而,我們可以使用.reduce
作為對象的字符串生成器。
function concat (input) {
return input.reduce(function (partial, value) {
if (partial) {
partial += ', '
}
return partial + value
}, '')
}
concat([
{ name: 'George' },
{ name: 'Sam' },
{ name: 'Pear' }
])
// <- 'George, Sam, Pear'
.slice
和.concat
類似,調(diào)用.slice
缺省參數(shù)時,返回原數(shù)組的淺拷貝。slice
函數(shù)需要兩個參數(shù),一個是開始位置和一個結(jié)束位置。
Array.prototype.slice
能被用來將類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組。
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// <- ['a', 'b']
這對.concat
不適用,因?yàn)樗鼤脭?shù)組包裹類數(shù)組對象。
Array.prototype.concat.call({ 0: 'a', 1: 'b', length: 2 })
// <- [{ 0: 'a', 1: 'b', length: 2 }]
除此之外,另一個常見用途是從參數(shù)列表中移除最初的幾個元素,并將類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組。
function format (text, bold) {
if (bold) {
text = '<b>' + text + '</b>'
}
var values = Array.prototype.slice.call(arguments, 2)
values.forEach(function (value) {
text = text.replace('%s', value)
})
return text
}
format('some%sthing%s %s', true, 'some', 'other', 'things')
// <- <b>somesomethingother things</b>
.splice
.splice
是我最喜歡的原生數(shù)組函數(shù)之一。它允許你刪除元素,插入新元素,或在同一位置同時進(jìn)行上述操作,而只使用一個函數(shù)調(diào)用。注意和.concat
和.slice
不同的是.splice
函數(shù)修改原數(shù)組。
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13]
var spliced = source.splice(3, 4, 4, 5, 6, 7)
console.log(source)
// <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13]
spliced
// <- [8, 8, 8, 8]
你可能已經(jīng)注意到,它也返回被刪除的元素。如果你想遍歷已經(jīng)刪除的數(shù)組時這可能會派上用場。
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13]
var spliced = source.splice(9)
spliced.forEach(function (value) {
console.log('removed', value)
})
// <- removed 10
// <- removed 11
// <- removed 12
// <- removed 13
console.log(source)
// <- [1, 2, 3, 8, 8, 8, 8, 8, 9]
.indexOf
通過.indexOf
,我們可以查找數(shù)組元素的位置。如果沒有匹配元素則返回-1
。我發(fā)現(xiàn)我用的很多的一個模式是連續(xù)比較,例如a === 'a' || a === 'b' || a === 'c'
,或者即使只有兩個結(jié)果的比較。在這種情況下,你也可以使用.indexOf
,像這樣:['a', 'b', 'c'].indexOf(a) !== -1
。
注意這對指向同一個引用的對象同樣適用。第二個參數(shù)是開始查詢的起始位置。
var a = { foo: 'bar' }
var b = [a, 2]
console.log(b.indexOf(1))
// <- -1
console.log(b.indexOf({ foo: 'bar' }))
// <- -1
console.log(b.indexOf(a))
// <- 0
console.log(b.indexOf(a, 1))
// <- -1
b.indexOf(2, 1)
// <- 1
如果你想從后向前搜索,.lastIndexOf
能派上用場。
in
操作符在面試中新手容易犯的錯誤是混淆.indexOf
和in
操作符,如下:
var a = [1, 2, 5]
1 in a
// <- true, 但因?yàn)?2!
5 in a
// <- false
問題的關(guān)鍵是in
操作符檢索對象的鍵而非值。當(dāng)然,這在性能上比.indexOf
快得多。
var a = [3, 7, 6]
1 in a === !!a[1]
// <- true
in
操作符類似于將鍵值轉(zhuǎn)換為布爾值。!!
表達(dá)式通常被開發(fā)者用來雙重取非一個值(轉(zhuǎn)化為布爾值)。實(shí)際上相當(dāng)于強(qiáng)制轉(zhuǎn)換為布爾值,任何為真的值被轉(zhuǎn)為true
,任何為假的值被轉(zhuǎn)換為false
。
.reverse
這方法將數(shù)組中的元素翻轉(zhuǎn)并替換原來的元素。
var a = [1, 1, 7, 8]
a.reverse()
// [8, 7, 1, 1]
和復(fù)制不同的是,數(shù)組本身被更改。在以后的文章中我將展開對這些概念的理解,去看看如何創(chuàng)建一個庫,如Underscore或Lo-Dash。
本文為翻譯文章,原文為“Fun with JavaScript Native Array Functions”。
更多建議: