JavaScript數(shù)組的應(yīng)用應(yīng)該都比較熟悉了。先上一張神圖,轉(zhuǎn)自右下角大神。
- forEach,map,filter
- some,every
- reduce,reduceRight
- slice,splice
- indexOf,lastIndexOf
- sort
- 類(lèi)數(shù)組對(duì)象
forEach,map,filter
forEach遍歷數(shù)組,函數(shù)聲明:[].forEach( function(value, index, array) { … }, [thisArg] );
。參照MDN
第一個(gè)參數(shù)是回調(diào)函數(shù),它支持3個(gè)參數(shù),第1個(gè)是遍歷的數(shù)組內(nèi)容,第2個(gè)是對(duì)應(yīng)索引,第3個(gè)是數(shù)組自身。
第二個(gè)參數(shù)thisArg可選,可用于以改變回調(diào)函數(shù)里面的this指針
因?yàn)閒orEach是第一個(gè)被介紹的數(shù)組方法,所以稍微詳細(xì)點(diǎn)用console.log看一下回調(diào)函數(shù)的3個(gè)參數(shù)。(之后的數(shù)組方法有興趣的可以自己用console.log看一下回調(diào)函數(shù),不贅述)
[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]
上面已經(jīng)清晰地展現(xiàn)了遍歷的結(jié)果,第一列是value,第二列是對(duì)應(yīng)的index值,第三列是數(shù)組本身。
現(xiàn)在用forEach實(shí)現(xiàn)數(shù)組求和:
var price = 0;
[1.2, 2.4, 0.6, 7].forEach(function (value) {
price += value;
});
console.log(price); //11.2
相比f(wàn)or循環(huán),上述代碼除了更簡(jiǎn)單外,還避免了常見(jiàn)的for循環(huán)的起始,終止條件越界等錯(cuò)誤。對(duì)于數(shù)組遍歷來(lái)說(shuō),forEach和map是優(yōu)于for循環(huán)的。
現(xiàn)在看看第二個(gè)參數(shù)thisArgs的作用,如果不指定該參數(shù),回調(diào)函數(shù)內(nèi)的this指向的是window(關(guān)于this可以參照這里),例如上例中的回調(diào)函數(shù)里,你可以寫(xiě)成this.price += value;
,效果是一樣的(當(dāng)然前提是變量確實(shí)是window的全局屬性)。但有時(shí)this指向window就不對(duì)了,如下:
var group = {
members: ["Jack", "Andy", "Natasha"],
joinParty: "Yes",
getInfo: function (m) {
this.isJoinParty(m);
console.log(m + " " + this.joinParty);
},
isJoinParty: function (m) {
switch(m) {
case "Andy" :
this.joinParty = "No";
break;
default:
this.joinParty = "Yes";
break;
}
}
};
group.members.forEach(group.getInfo);
代碼很簡(jiǎn)單,小組內(nèi)3人,Andy不參加聚會(huì),另兩人參加聚會(huì)。期望把統(tǒng)計(jì)結(jié)果打印出來(lái)。但很遺憾上面代碼會(huì)報(bào)Error。按理說(shuō)getInfo函數(shù)里的this應(yīng)該指向group對(duì)象,但遺憾地是getInfo作為[].forEach的回調(diào)函數(shù)時(shí)相當(dāng)于普通函數(shù),因此getInfo里的this指向的是window。而window對(duì)象里顯然不存在isJoinParty。
因此正確的調(diào)用方式是,添加第二個(gè)參數(shù),明確指定this的綁定對(duì)象:
group.members.forEach(group.getInfo, group);
//Jack Yes
//Andy No
//Natasha Yes
map映射創(chuàng)建新數(shù)組,函數(shù)聲明:[].map( function(value, index, array) { … }, [thisArg] );
。和forEach一樣,不贅述。唯一需要注意的的是回調(diào)函數(shù)需要有return值,否則新數(shù)組都是undefined。
其實(shí)map能干的事forEach都能干,你可以把map理解為forEach的一個(gè)特例,專(zhuān)門(mén)用于:通過(guò)現(xiàn)有的數(shù)組建立新數(shù)組。例如將舊數(shù)組中字符串都trim一下,去除空格后生成新數(shù)組:
var trimmed = [' Jack','Betty ',' Chirs '].map(function(s) {
return s.trim(); //需要return值,否則新數(shù)組里都是undefined
});
console.log(trimmed); //["Jack", "Betty", "Chirs"]
在沒(méi)有map之前是通過(guò)forEach來(lái)創(chuàng)建新數(shù)組的。你需要先定義一個(gè)空數(shù)組,再將每次trim后的字符串push到新數(shù)組內(nèi),比較麻煩。因?yàn)椤巴ㄟ^(guò)現(xiàn)有的數(shù)組建立新數(shù)組”這個(gè)需求是如此的普遍,因此ES5中干脆追加了map方法,相比f(wàn)orEach代碼簡(jiǎn)單優(yōu)雅多了。
filter用于過(guò)濾數(shù)組,函數(shù)聲明:[].filter( function(value, index, array) { … }, [thisArg] );
。和forEach一樣,不贅述。唯一需要注意的的是回調(diào)函數(shù)需要return布爾值true或false,如果忘記寫(xiě)return語(yǔ)句,返回得到的是空數(shù)組,表示一個(gè)都不匹配。例如:
var newArray = [0, 1, 2].filter(function(value) {});
console.log(newArray); //[],沒(méi)有return語(yǔ)句得到的是空數(shù)組
//過(guò)濾出不超過(guò)10的正數(shù)
var newArray2 = [0, 1, 2, 14].filter(function(value) {
return value > 0 && value <= 10;
});
console.log(newArray2); //[1, 2]
some,every
some表示只要某一個(gè)滿足條件就OK,every表示全部滿足條件才OK。
some的函數(shù)聲明:[].some( function(value, index, array) { … }, [thisArg] );
every的函數(shù)聲明:[].every( function(value, index, array) { … }, [thisArg] );
參照MDN。其實(shí)都和上面的forEach一樣,不贅述。唯一需要注意的的是回調(diào)函數(shù)需要return布爾值true或false,如果忘記寫(xiě)return語(yǔ)句,表示不滿足條件,返回false
[1, 10, 100].some(function(x) { x > 5; }); //false,忘記寫(xiě)return了
[1, 2, 3, 4, 5].every(function(x) { x > 0; }); //false,忘記寫(xiě)return了
[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false
[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false
reduce,reduceRight
兩者都是用于迭代運(yùn)算。區(qū)別是reduce從頭開(kāi)始迭代,reduceRight從尾開(kāi)始迭代。
reduce的函數(shù)聲明:[].reduce( function(previousValue, currentValue, currentIndex, array) { … }, [initialValue] );
第一個(gè)參數(shù)是回調(diào)函數(shù),有4個(gè)參數(shù):previousValue,currentValue,currentIndex,array。看名字也能知道意思:前一個(gè)值,當(dāng)前值,當(dāng)前索引,數(shù)組本身。
第二個(gè)參數(shù)initialValue可選,表示初始值。如果省略,初始值為數(shù)組的第一個(gè)元素,這樣的話回調(diào)函數(shù)里previousValue就是第一個(gè)元素,currentValue是第二個(gè)元素。因此不設(shè)initialValue的話,會(huì)少一次迭代。例如:
var sum = [1, 2, 3, 4].reduce(function (previous, current) {
return previous + current;
});
console.log(sum); //10
//給它加上initialValue初始值10
var sum2 = [1, 2, 3, 4].reduce(function (previous, current) {
return previous + current;
}, 10);
console.log(sum2); //20
上圖清楚地表明了各個(gè)運(yùn)算步驟,很容易理解。如果不設(shè)initialValue,會(huì)少一次迭代。
reduceRight的函數(shù)聲明:[].reduceRight( function(previousValue, currentValue, currentIndex, array) { … }, [initialValue] );
。和reduce一樣,不贅述
用reduce和reduceRight很容易就能實(shí)現(xiàn)二維數(shù)組扁平化,如下:
var flat1 = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b) {
return a.concat(b);
});
console.log(flat1); //[0, 1, 2, 3, 4, 5]
var flat2 = [[0, 1], [2, 3], [4, 5]].reduceRight(function(a, b) {
return a.concat(b);
});
console.log(flat2); //[4, 5, 2, 3, 0, 1]
slice,splice
兩者做的事情還不太一樣,但名字實(shí)在太像了,所以放一起介紹。
slice用于復(fù)制數(shù)組,復(fù)制完后舊數(shù)組不變,返回得到的新數(shù)組是舊數(shù)組的子集。函數(shù)聲明:[].slice(begin, [end])
。參照MDN
第一個(gè)參數(shù)begin是開(kāi)始復(fù)制的位置,需要注意的是,可以設(shè)負(fù)數(shù)。設(shè)負(fù)數(shù)表示從尾往前數(shù)幾個(gè)位置開(kāi)始復(fù)制。例如slice(-2)將從倒數(shù)第2個(gè)元素開(kāi)始復(fù)制。另外需要注意的是,該參數(shù)雖未標(biāo)注為可選,但實(shí)際上是可以省略的,省略的話默認(rèn)為0。
第二個(gè)參數(shù)end可選,表示復(fù)制到該位置的前一個(gè)元素。例如slice(0,3)將得到前3個(gè)元素,但不包含第4個(gè)元素。不設(shè)的話默認(rèn)復(fù)制到數(shù)組尾,即等于array.length。
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'].slice(0, 3);
console.log(fruits); //["Banana", "Orange", "Lemon"]
var fruits2 = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'].slice(-1);
console.log(fruits2); //["Mango"]
當(dāng)然slice最常見(jiàn)的是用在將類(lèi)數(shù)組arguments對(duì)象轉(zhuǎn)換為真正的數(shù)組:
var args = [].slice.call(arguments);
splice用于剝離數(shù)組,從舊數(shù)組中移除元素,返回得到的新數(shù)組是被移除的元素。函數(shù)聲明:[].splice(start, deleteCount, [item…])
。參照MDN
第一個(gè)參數(shù)start是開(kāi)始剝離的位置,需要注意的是,可以設(shè)負(fù)數(shù)。設(shè)負(fù)數(shù)表示從尾往前數(shù)幾個(gè)位置開(kāi)始剝離。例如splice (-2)將從倒數(shù)第2個(gè)元素開(kāi)始剝離。
第二個(gè)參數(shù)deleteCount是要?jiǎng)冸x的元素個(gè)數(shù),設(shè)0表示一個(gè)都不剝離。
第三個(gè)參數(shù)開(kāi)始可選,用于替換舊數(shù)組中被移除的元素
var oldArray = ['a', 'b', 'c'];
var newArray = oldArray.splice(1, 2, 'Jack', 'Betty', 'Andy');
console.log(oldArray); //["a", "Jack", "Betty", "Andy"]
console.log(newArray); //["b", "c"]
一個(gè)常見(jiàn)的應(yīng)用就是刪除數(shù)組內(nèi)某元素,用delete的話會(huì)留下空洞,應(yīng)該用splice方法:
//錯(cuò)誤的方法用delete
var numbers = [0, 1, 2, 3, 4];
delete numbers[2];
console.log(numbers); //[0, 1, undefined, 3, 4]
//正確的方法用splice
numbers.splice(2, 1);
console.log(numbers); //[0, 1, 3, 4]
indexOf,lastIndexOf
兩者都用于返回項(xiàng)目的索引值。區(qū)別是indexOf從頭開(kāi)始找,lastIndexOf從尾開(kāi)始找。如果查找失敗,無(wú)匹配,返回-1
indexOf的函數(shù)聲明:[].indexOf( searchElement, [fromIndex = 0] );
。參照MDN
lastIndexOf的函數(shù)聲明:[].lastIndexOf( searchElement, [fromIndex = arr.length – 1] );
第一個(gè)參數(shù)searchElement即需要查找的元素。第二個(gè)參數(shù)fromIndex可選,指定開(kāi)始查找的位置。如果忽略,indexOf默認(rèn)是0,lastIndexOf默認(rèn)是數(shù)組尾。
['a', 'b', 'd', 'e'].indexOf('b'); //1
['a', 'b', 'd', 'e'].indexOf('b', 2); //-1,從2號(hào)位開(kāi)始找沒(méi)找到
['a', 'b', 'd', 'e'].indexOf('c'); //-1,沒(méi)找到
['a', 'b', 'd', 'e'].lastIndexOf('b'); //1
['a', 'b', 'd', 'e'].lastIndexOf('b', 2); //1,逆向2號(hào)位等價(jià)于正向1號(hào)位
['a', 'b', 'd', 'e'].lastIndexOf('c'); //-1,沒(méi)找到
sort
sort用于排序數(shù)組,函數(shù)聲明:[].sort( [sortfunction] );
。參照MDN
它就一個(gè)參數(shù),就是排序函數(shù)指針。而且是可選的,不設(shè)的話有默認(rèn)的排序函數(shù),數(shù)字的話會(huì)升序排列,string會(huì)根據(jù)Unicode升序排列。
var sumArray = [4, 3, 1, 0, 2];
var sumArray2 = ['d', 'z', 'a'];
sumArray.sort();
sumArray2.sort();
console.log(sumArray); //[0, 1, 2, 3, 4]
console.log(sumArray2); //["a", "d", "z"]
但是內(nèi)置的默認(rèn)排序函數(shù),是不可靠的,如下:
var scores = [1, 10, 2, 21];
scores.sort();
console.log(scores); //[1, 10, 2, 21]
因此保險(xiǎn)起見(jiàn)最好自定義排序函數(shù):
var scores = [1, 10, 2, 21];
function compareNumbers(x, y) {
if (x < y) { return -1; }
if (x > y) { return 1; }
return 0;
}
scores.sort(compareNumbers);
console.log(scores); //[1, 2, 10, 21]
而且如果數(shù)組內(nèi)是對(duì)象,或排序邏輯復(fù)雜的話,那默認(rèn)排序函數(shù)更是力不從心了,必須自定義排序函數(shù):
var items = [
{ name: 'Jack', value: 37 },
{ name: 'Betty', value: 21 },
{ name: 'Andy', value: 45 }
];
items.sort(function (a, b) {
if (a.value < b.value) { return -1; }
if (a.value > b.value) { return 1; }
return 0;
});
console.log(items);
//[{ name="Betty", value=21},
// { name="Jack", value=37},
// { name="Andy", value=45}]
剩下的比較簡(jiǎn)單,大致說(shuō)一下,就不詳細(xì)介紹了。
push和pop用于數(shù)組尾處壓入和彈出元素。
unshift和shift用于數(shù)組頭部壓入和彈出元素。
reverse用于反轉(zhuǎn)數(shù)組,concat用于連接數(shù)組,join用于數(shù)組元素間插入些東西后拼接成string。
類(lèi)數(shù)組
JS里有很多類(lèi)數(shù)組對(duì)象。什么叫類(lèi)數(shù)組對(duì)象呢?它們首先是對(duì)象,并沒(méi)有繼承Array,但長(zhǎng)的卻很像數(shù)組。最典型的如arguments對(duì)象,HTMLCollection對(duì)象。
類(lèi)數(shù)組對(duì)象不能直接使用數(shù)組方法,但數(shù)組方法是如此簡(jiǎn)單便利,要在類(lèi)數(shù)組對(duì)象身上使用數(shù)組方法,需要讓數(shù)組函數(shù)通過(guò)call綁定類(lèi)數(shù)組對(duì)象。
處理arguments對(duì)象:
var args = [].slice.call(arguments);
處理HTMLCollection對(duì)象:
//用forEach遍歷頁(yè)面所有div,輸入className
var divs = document.getElementsByTagName("div");
Array.prototype.forEach.call(divs, function(div) {
console.log("該div類(lèi)名是:" + (div.className || "空"));
});
//下面這樣直接調(diào)用forEach將報(bào)錯(cuò),因?yàn)閐ivs是HTMLCollection對(duì)象而非Array
divs.forEach(function(div) {
console.log("該div類(lèi)名是:" + (div.className || "空"));
});
處理字面量對(duì)象:
var arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
var result = Array.prototype.map.call(arrayLike, function(s) {
return s.toUpperCase();
});
console.log(result); //["A", "B", "C"]
處理字符串:
var result = Array.prototype.map.call("abc", function(s) {
return s.toUpperCase();
});
console.log(result); //["A", "B", "C"]
但Array的concat會(huì)檢查參數(shù)的[[Class]]屬性,只有參數(shù)是一個(gè)真實(shí)的數(shù)組才會(huì)將數(shù)組內(nèi)容連接起來(lái),否則將作為單個(gè)元素來(lái)連接。要完全實(shí)現(xiàn)連接,我們需要自己在對(duì)象上增加slice方法:
//單用concat的話,arguments對(duì)象將作為一個(gè)單一整體被連接
function namesColumn() {
return ["Jack"].concat(arguments);
}
var newNames = namesColumn("Betty", "Andy", "Chris");
console.log(newNames); //["Jack", ["Betty", "Andy", "Chris"]]
//配合slice能實(shí)現(xiàn)完全連接
function namesColumn() {
return ["Jack"].concat([].slice.call(arguments));
}
var newNames = namesColumn("Betty", "Andy", "Chris");
console.log(newNames); //["Jack", "Betty", "Andy", "Chris"]
總結(jié)
最后再提一個(gè)小經(jīng)驗(yàn),JS的一個(gè)常見(jiàn)的錯(cuò)誤就是在必須使用數(shù)組時(shí)使用對(duì)象,或反之。經(jīng)驗(yàn)是:當(dāng)屬姓名是小而連續(xù)的整數(shù)時(shí)用數(shù)組。否則用對(duì)象。