
目錄
- 數(shù)組的本質(zhì)
- Array構(gòu)造方法
- Array實(shí)例方法
- join()
- concat()
- reverse()
- slice()
- splice()
- sort()
- map()
- forEach()
- filter()
- some() 和 every()
- reduce() 和 reduceRight()
- 總結(jié)
數(shù)組的本質(zhì)
在講Js原生函數(shù)Array之前,我們先探討一下數(shù)組的本質(zhì)。先給出結(jié)論:數(shù)組是一個原型鏈上包含Array.prototype的對象。
這就是區(qū)分?jǐn)?shù)組和偽數(shù)組的唯一標(biāo)志。常見的偽數(shù)組,比如:
- arguments 對象
- document.querySelectAll('div') 返回的對象
- 字符串
它們都有與數(shù)組幾乎一樣的屬性,可以造出來 0,1,2,3,4,5...n,length 這些 key,能循環(huán)遍歷,能自己改寫valueOf和toString方法來實(shí)現(xiàn)數(shù)組差不多的功能,等等。但是它們無法直接調(diào)用Array.prototype中的方法,比如push,pop,shift,unshift……因?yàn)樗鼈兪荗bject構(gòu)造出來的,原型鏈上沒有這些方法。
要想調(diào)用Array.prototype中的方法,兩條路:
- 使用數(shù)組的
slice方法將“類似數(shù)組的對象”變成真正的數(shù)組 -
Array.prototype.join.call(arrayLike, ',')(join只是其中一種方法,調(diào)用比如forEach可在第二個參數(shù)傳入自定義的print方法來實(shí)現(xiàn)遍歷輸出)
以上的兩種方法中,第二種的效率比直接使用原生數(shù)組的forEach要慢,建議第一種。
其次,數(shù)組的length是一個動態(tài)屬性,等于鍵名中的最大整數(shù)加上1;而偽數(shù)組的length并不會隨著值的添加而改變,需要自己設(shè)置。
另外數(shù)組也可以添加非數(shù)字值的key,比如各種奇怪的字符串。和狹義的對象一樣,數(shù)組的鍵名其實(shí)也是字符串。之所以可以用數(shù)值讀取,是因?yàn)榉亲址逆I名會被轉(zhuǎn)為字符串。也就是說數(shù)組本質(zhì)是對象的一種,由typeof的返回值也可見。
Array構(gòu)造方法
Array是 JavaScript 的原生對象,同時也是一個構(gòu)造函數(shù),可以用它生成新的數(shù)組。
(1)一個參數(shù)
由上圖可見,當(dāng)只傳入一個3的時候,表示數(shù)組的length為3,而數(shù)組內(nèi)是empty × 3,雖然a[0]返回undefined,但是數(shù)組中并沒有'0'這個key。
另外,對于復(fù)雜類型,有new沒有new一樣,只是語義上的區(qū)別,沒有new表示封裝為對象,有new表示生成一個對象。基本類型有new表示生成對象,沒有new還是原來的類型,僅取值。
var arr = Array(3)
typeof arr // "object"
var a = String('123')
typeof a // "string"
var b = new String('123')
typeor b // "object"
傳入非正整數(shù)數(shù)字參數(shù),會有其他結(jié)果:
// 非正整數(shù)的數(shù)值作為參數(shù),會報錯
new Array(3.2) // RangeError: Invalid array length
new Array(-3) // RangeError: Invalid array length
// 單個非數(shù)值(比如字符串、布爾值、對象等)作為參數(shù),
// 則該參數(shù)是返回的新數(shù)組的成員
new Array('abc') // ['abc']
new Array([1]) // [Array[1]]
(2)多個參數(shù)
var arr = new Array(3, 3)
arr // [3, 3]
傳入多個參數(shù)時,所有參數(shù)都是返回的新數(shù)組的成員。
可以看到,Array作為構(gòu)造函數(shù),行為很不一致。因此,不建議使用它生成新數(shù)組,直接使用數(shù)組字面量是更好的做法。
Array實(shí)例方法
實(shí)例方法是定義在Array.prototype上的方法,每個實(shí)例對象都會繼承到。
常用的:
- valueOf:返回?cái)?shù)組本身
- toString:返回?cái)?shù)組的字符串形式(二維及以上會展開)
- indexOf:返回給定元素在數(shù)組中第一次出現(xiàn)的位置,如果沒有出現(xiàn)則返回-1
- push:用于在數(shù)組的末端添加一個或多個元素,并返回添加新元素后的
length - pop:用于刪除數(shù)組的最后一個元素,并返回該元素
- shift:用于刪除數(shù)組的第一個元素,并返回該元素
- unshift:用于在數(shù)組的第一個位置添加一個或多個元素,并返回添加新元素后的數(shù)組長度
注意:以上四種增刪數(shù)組的方法均會改變原數(shù)組。
join()
join()方法以指定參數(shù)作為分隔符,將所有數(shù)組成員連接為一個字符串返回。如果不提供參數(shù),默認(rèn)用逗號分隔。
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
['a',, 'b'].join('-')
// 'a--b'
// 空位轉(zhuǎn)成空字符串
concat()
concat方法用于多個數(shù)組的合并。它將新數(shù)組的成員,添加到原數(shù)組成員的后部,然后返回一個新數(shù)組,原數(shù)組不變。
var a = ['hello']
a.concat(['world'], ['!']) // ["hello", "world", "!"]
a // ['hello']
數(shù)組的淺拷貝:
var a = [{'n': 1}, {'s': 'abc'}]
var newArr = a.concat()
a[0].n = 2
newArr[0].n // 2
reverse()
reverse方法用于顛倒排列數(shù)組元素,返回改變后的數(shù)組。注意,該方法將改變原數(shù)組。
slice()
slice,即“切片”,該方法用于提取目標(biāo)數(shù)組的一部分,返回一個新數(shù)組,原數(shù)組不變。
可傳入兩個參數(shù),第一個參數(shù)為起始位置,第二個參數(shù)為終止位置(但該位置的元素本身不包括在內(nèi))
var a = ['a', 'b', 'c']
// 起始位置是1,末尾位置省略,表示直到末尾
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
// 第二個參數(shù)超過length也可以
a.slice(2, 6) // ["c"]
// 等于返回一個原數(shù)組的拷貝
a.slice() // ["a", "b", "c"]
// 負(fù)數(shù)表示倒數(shù)計(jì)算的位置
a.slice(-2) // ["b", "c"]
// 注意這里不包括-1位置的元素
a.slice(-2, -1) // ["b"]
// 如果第一個參數(shù)大于等于數(shù)組長度,或者第二個參數(shù)小于第一個參數(shù),則返回空數(shù)組
a.slice(4) // []
a.slice(2, 1) // []
splice()
splice方法用于刪除原數(shù)組的一部分成員,并可以在刪除的位置添加新的數(shù)組成員,返回值是被刪除的元素。注意,該方法會改變原數(shù)組。
語法:
arr.splice(start, count, addElement1, addElement2, ...)
start起始位置,count刪掉幾個元素,之后的全是添加的新元素。
var a = ['a', 'b', 'c', 'd', 'e', 'f']
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]
a.splice(-4, 2) // ["c", "d"]
a // ["a", "b", 1, 2]
// count設(shè)置為0,就表示只添加元素
a.splice(2, 0, "c") // []
a // ["a", "b", "c", 1, 2]
// 只設(shè)置start,就表示從start開始切分為兩個數(shù)組
a.splice(3) // [1, 2]
a // ["a", "b", "c"]
sort()
sort方法對數(shù)組成員進(jìn)行排序,默認(rèn)是按照字典順序排序,底層原理是快排。排序后,原數(shù)組將被改變。
[10111, 1101, 111].sort() // [10111, 1101, 111]
如果想讓sort方法按照自定義方式排序,可以傳入一個函數(shù)作為參數(shù)。
[
{ name: "張三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function (o1, o2) {
return o1.age - o2.age; // 表示按年紀(jì)從小到大,反之是從大到小
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "張三", age: 30 }
// ]
map()
map方法將數(shù)組的所有成員依次傳入?yún)?shù)函數(shù),然后把每一次的執(zhí)行結(jié)果組成一個新數(shù)組返回。該函數(shù)調(diào)用時,map方法向它傳入三個參數(shù):當(dāng)前成員、當(dāng)前位置和數(shù)組本身??梢赃x擇性省略參數(shù)。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
})
// [0, 2, 6]
用map()可以非常簡潔的實(shí)現(xiàn)一些小功能,比如:
// 所有數(shù)字取平方
[1, 2, 3].map(v => v ** 2) // [1, 4, 9]
// 所有數(shù)字轉(zhuǎn)字符串
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr.map(String) // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
map方法不會跳過undefined和null,但是會跳過空位。
forEach()
forEach方法與map方法很相似,也是對數(shù)組的所有成員依次執(zhí)行參數(shù)函數(shù)。但是,forEach方法不返回值,只用來操作數(shù)據(jù)。
forEach的用法與map方法一致,參數(shù)是一個函數(shù),該函數(shù)同樣接受三個參數(shù):當(dāng)前值、當(dāng)前位置、整個數(shù)組。
[2, 5, 9].forEach(function(elem, index, arr){
console.log('[' + index + '] = ' + element)
})
// [0] = 2
// [1] = 5
// [2] = 9
注意,forEach方法無法中斷執(zhí)行,總是會將所有成員遍歷完。如果希望符合某種條件時,就中斷遍歷,要使用for循環(huán)。
forEach方法不會跳過undefined和null,但會跳過空位。
filter()
filter方法用于過濾數(shù)組成員,滿足條件的成員組成一個新數(shù)組返回。
它的參數(shù)是一個函數(shù),所有數(shù)組成員依次執(zhí)行該函數(shù),返回結(jié)果為true的成員組成一個新數(shù)組返回。該方法不會改變原數(shù)組。也就是說,這個函數(shù)就是過濾規(guī)則。
[1, 2, 3, 4, 5].filter(v => v%2 === 0) // 篩選偶數(shù)
filter方法的參數(shù)函數(shù)可以接受三個參數(shù):當(dāng)前成員,當(dāng)前位置和整個數(shù)組。
some() 和 every()
這兩個方法類似“斷言”(assert),返回一個布爾值,表示判斷數(shù)組成員是否符合某種條件。
它們接受一個函數(shù)作為參數(shù),所有數(shù)組成員依次執(zhí)行該函數(shù)。該函數(shù)接受三個參數(shù):當(dāng)前成員、當(dāng)前位置和整個數(shù)組,然后返回一個布爾值。
some方法是只要一個成員的返回值是true,則整個some方法的返回值就是true,否則返回false。every方法是所有成員的返回值都是true,整個every方法才返回true,否則返回false。有點(diǎn) || 和 && 的意思。
var arr = [1, 2, 3, 4, 5]
arr.some(v => v >= 3) // true
arr.every(v => v >= 3) // false
注意,對于空數(shù)組,some方法返回false,every方法返回true,回調(diào)函數(shù)都不會執(zhí)行。
reduce() 和 reduceRight()
reduce方法和reduceRight方法依次處理數(shù)組的每個成員,最終累計(jì)為一個值。它們的差別是,reduce是從左到右處理(從第一個成員到最后一個成員),reduceRight則是從右到左(從最后一個成員到第一個成員),其他完全一樣。
reduce方法和reduceRight方法的第一個參數(shù)都是一個函數(shù)。該函數(shù)接受以下四個參數(shù):
- 累積變量,默認(rèn)為數(shù)組的第一個成員
- 當(dāng)前變量,默認(rèn)為數(shù)組的第二個成員
- 當(dāng)前位置(從0開始)
- 原數(shù)組
這四個參數(shù)之中,只有前兩個是必須的,后兩個則是可選的。
如果要對累積變量指定初值,可以把它放在reduce方法和reduceRight方法的第二個參數(shù)。
例子:找出字符長度最長的數(shù)組成員
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest
}, '');
}
findLongest(['aaa', 'bb', 'c']) // "aaa"
例子:計(jì)算所有奇數(shù)的和
var a = [1,2,3,4,5,6,7,8,9]
a.reduce((sum, n) => n % 2 === 1 ? sum + n : sum) // 25
總結(jié)
原生函數(shù)Array作為Js的標(biāo)準(zhǔn)庫之一,其API非常重要,記住常用的API能讓我們事半功倍地實(shí)現(xiàn)很多功能。并且,上面這些數(shù)組方法之中,有不少返回的還是數(shù)組,所以可以鏈?zhǔn)绞褂?,比如篩選數(shù)據(jù)庫中的email并且遍歷輸出,等等。