其實(shí) JS 中一直存在一種類數(shù)組的對(duì)象,它們不能直接調(diào)用數(shù)組的方法,但是又和數(shù)組比較類似,在某些特定的編程場(chǎng)景中會(huì)出現(xiàn),這會(huì)讓很多 JS 的初學(xué)者比較困惑。我們先來(lái)看看在 JavaScript 中有哪些情況下的對(duì)象是類數(shù)組呢?主要有以下幾種:
1、函數(shù)里面的參數(shù)對(duì)象 arguments;
2、用 getElementsByTagName/ClassName/Name 獲得的 HTMLCollection;
3、用 querySelector 獲得的 NodeList。
上述這些基本就是你在 JavaScript 編程過(guò)程中經(jīng)常會(huì)遇到的。
類數(shù)組基本介紹
arguments
先來(lái)重點(diǎn)講講 arguments 對(duì)象,我們?cè)谌粘i_(kāi)發(fā)中經(jīng)常會(huì)遇到各種類數(shù)組對(duì)象,最常見(jiàn)的便是在函數(shù)中使用的 arguments,它的對(duì)象只定義在函數(shù)體中,包括了函數(shù)的參數(shù)和其他屬性。我們通過(guò)一段代碼來(lái)看下 arguments 的使用方法,如下所示。
function foo(name, age, sex) {
? ? console.log(arguments);
? ? console.log(typeof arguments);
? ? console.log(Object.prototype.toString.call(arguments));
}
foo('jack', '18', 'male');
這段代碼比較容易,就是直接將這個(gè)函數(shù)的 arguments 在函數(shù)內(nèi)部打印出來(lái),那么我們看下這個(gè) arguments 打印出來(lái)的結(jié)果,請(qǐng)看控制臺(tái)的這張截圖。

從結(jié)果中可以看到,typeof 這個(gè) arguments 返回的是 object,通過(guò) Object.prototype.toString.call 返回的結(jié)果是 '[object arguments]',可以看出來(lái)返回的不是 '[object array]',說(shuō)明 arguments 和數(shù)組還是有區(qū)別的。
length 屬性很好理解,它就是函數(shù)參數(shù)的長(zhǎng)度,我們從打印出的代碼也可以看得出來(lái)。另外可以看到 arguments 不僅僅有一個(gè) length 屬性,還有一個(gè) callee 屬性,我們接下來(lái)看看這個(gè) callee 是干什么的,代碼如下所示。
function foo(name, age, sex) {
? ? console.log(arguments.callee);
}
foo('jack', '18', 'male');
請(qǐng)看這段代碼的執(zhí)行結(jié)果。

從控制臺(tái)可以看到,輸出的就是函數(shù)自身,如果在函數(shù)內(nèi)部直接執(zhí)行調(diào)用 callee 的話,那它就會(huì)不停地執(zhí)行當(dāng)前函數(shù),直到執(zhí)行到內(nèi)存溢出,有興趣的話你可以自己試一下。
接下來(lái)我們?cè)倏纯聪旅孢@種類數(shù)組。
HTMLCollection
HTMLCollection 簡(jiǎn)單來(lái)說(shuō)是 HTML DOM 對(duì)象的一個(gè)接口,這個(gè)接口包含了獲取到的 DOM 元素集合,返回的類型是類數(shù)組對(duì)象,如果用 typeof 來(lái)判斷的話,它返回的是 'object'。它是及時(shí)更新的,當(dāng)文檔中的 DOM 變化時(shí),它也會(huì)隨之變化。
描述起來(lái)比較抽象,還是通過(guò)一段代碼來(lái)看下 HTMLCollection 最后返回的是什么,我們先隨便找一個(gè)頁(yè)面中有 form 表單的頁(yè)面,在控制臺(tái)中執(zhí)行下述代碼。
var elem1, elem2;
// document.forms 是一個(gè) HTMLCollection
elem1 = document.forms[0];
elem2 = document.forms.item(0);
console.log(elem1);
console.log(elem2);
console.log(typeof elem1);
console.log(Object.prototype.toString.call(elem1));
在這個(gè)有 form 表單的頁(yè)面執(zhí)行上面的代碼,得到的結(jié)果如下。

可以看到,這里打印出來(lái)了頁(yè)面第一個(gè) form 表單元素,同時(shí)也打印出來(lái)了判斷類型的結(jié)果,說(shuō)明打印的判斷的類型和 arguments 返回的也比較類似,typeof 返回的都是 'object',和上面的類似。
另外需要注意的一點(diǎn)就是 HTML DOM 中的 HTMLCollection 是即時(shí)更新的,當(dāng)其所包含的文檔結(jié)構(gòu)發(fā)生改變時(shí),它會(huì)自動(dòng)更新。下面我們?cè)倏醋詈笠粋€(gè) NodeList 類數(shù)組。
NodeList
NodeList 對(duì)象是節(jié)點(diǎn)的集合,通常是由 querySlector 返回的。NodeList 不是一個(gè)數(shù)組,也是一種類數(shù)組。雖然 NodeList 不是一個(gè)數(shù)組,但是可以使用 for...of 來(lái)迭代。在一些情況下,NodeList 是一個(gè)實(shí)時(shí)集合,也就是說(shuō),如果文檔中的節(jié)點(diǎn)樹(shù)發(fā)生變化,NodeList 也會(huì)隨之變化。我們還是利用代碼來(lái)理解一下 Nodelist 這種類數(shù)組。
var list = document.querySelectorAll('input[type=checkbox]');
for (var checkbox of list) {
? checkbox.checked = true;
}
console.log(list);
console.log(typeof list);
console.log(Object.prototype.toString.call(list));
從上面的代碼執(zhí)行的結(jié)果中可以發(fā)現(xiàn),我們是通過(guò)有 CheckBox 的頁(yè)面執(zhí)行的代碼,在結(jié)果可中輸出了一個(gè) NodeList 類數(shù)組,里面有一個(gè) CheckBox 元素,并且我們判斷了它的類型,和上面的 arguments 與 HTMLCollection 其實(shí)是類似的,執(zhí)行結(jié)果如下圖所示。

好了,現(xiàn)在你已經(jīng)了解了上面這三種類數(shù)組,那么結(jié)合這些,我們?cè)倏纯搭悢?shù)組有哪些應(yīng)用的場(chǎng)景呢?
類數(shù)組應(yīng)用場(chǎng)景
我在這里為你介紹三種場(chǎng)景,這些也是最常見(jiàn)的。
遍歷參數(shù)操作
我們?cè)诤瘮?shù)內(nèi)部可以直接獲取 arguments 這個(gè)類數(shù)組的值,那么也可以對(duì)于參數(shù)進(jìn)行一些操作,比如下面這段代碼,我們可以將函數(shù)的參數(shù)默認(rèn)進(jìn)行求和操作。
function add() {
? ? var sum =0,
? ? ? ? len = arguments.length;
? ? for(var i = 0; i < len; i++){
? ? ? ? sum += arguments[i];
? ? }
? ? return sum;
}
add()? ? ? ? ? ? ? ? ? ? ? ? ? // 0
add(1)? ? ? ? ? ? ? ? ? ? ? ? ? // 1
add(1,2)? ? ? ? ? ? ? ? ? ? ? // 3
add(1,2,3,4);? ? ? ? ? ? ? ? ? // 10
結(jié)合上面這段代碼,我們?cè)诤瘮?shù)內(nèi)部可以將參數(shù)直接進(jìn)行累加操作,以達(dá)到預(yù)期的效果,參數(shù)多少也可以不受限制,根據(jù)長(zhǎng)度直接計(jì)算,返回出最后函數(shù)的參數(shù)的累加結(jié)果,其他的操作也都可以仿照這樣的方式來(lái)做。我們?cè)倏聪乱环N場(chǎng)景。
定義鏈接字符串函數(shù)
我們可以通過(guò) arguments 這個(gè)例子定義一個(gè)函數(shù)來(lái)連接字符串。這個(gè)函數(shù)唯一正式聲明了的參數(shù)是一個(gè)字符串,該參數(shù)指定一個(gè)字符作為銜接點(diǎn)來(lái)連接字符串。該函數(shù)定義如下。
function myConcat(separa) {
? var args = Array.prototype.slice.call(arguments, 1);
? return args.join(separa);
}
myConcat(", ", "red", "orange", "blue");
// "red, orange, blue"
myConcat("; ", "elephant", "lion", "snake");
// "elephant; lion; snake"
myConcat(". ", "one", "two", "three", "four", "five");
// "one. two. three. four. five"
這段代碼說(shuō)明了,你可以傳遞任意數(shù)量的參數(shù)到該函數(shù),并使用每個(gè)參數(shù)作為列表中的項(xiàng)創(chuàng)建列表進(jìn)行拼接。從這個(gè)例子中也可以看出,我們可以在日常編碼中采用這樣的代碼抽象方式,把需要解決的這一類問(wèn)題,都抽象成通用的方法,來(lái)提升代碼的可復(fù)用性。
下面我們?cè)倏戳硗庖环N使用場(chǎng)景。
傳遞參數(shù)使用
我在第 4 講中已經(jīng)介紹過(guò)了 apply 和 call 的使用訪問(wèn),結(jié)合這一講的內(nèi)容,我們來(lái)研究一下,如果再結(jié)合 arguments,還能實(shí)現(xiàn)什么?可以借助 arguments 將參數(shù)從一個(gè)函數(shù)傳遞到另一個(gè)函數(shù),請(qǐng)看下面這個(gè)例子。
// 使用 apply 將 foo 的參數(shù)傳遞給 bar
function foo() {
? ? bar.apply(this, arguments);
}
function bar(a, b, c) {
? console.log(a, b, c);
}
foo(1, 2, 3)? //1 2 3
上述代碼中,通過(guò)在 foo 函數(shù)內(nèi)部調(diào)用 apply 方法,用 foo 函數(shù)的參數(shù)傳遞給 bar 函數(shù),這樣就實(shí)現(xiàn)了借用參數(shù)的妙用。你可以結(jié)合這個(gè)例子再思考一下,對(duì)于 foo 這樣的函數(shù)可以靈活傳入?yún)?shù)數(shù)量,通過(guò)這樣的代碼編寫(xiě)方式是不是也可以實(shí)現(xiàn)一些功能的拓展場(chǎng)景呢?
如何將類數(shù)組轉(zhuǎn)換成數(shù)組
在互聯(lián)網(wǎng)大廠的面試中,類數(shù)組轉(zhuǎn)換成數(shù)組這樣的題目經(jīng)常會(huì)問(wèn),也會(huì)問(wèn)你 arguments 的相關(guān)問(wèn)題,那么結(jié)合本講的內(nèi)容,下面我?guī)闼伎家幌氯绾螌㈩悢?shù)組轉(zhuǎn)換為數(shù)組。大致的實(shí)現(xiàn)方式有兩種,我將依次講解。
類數(shù)組借用數(shù)組方法轉(zhuǎn)數(shù)組
apply 和 call 方法之前有講過(guò),類數(shù)組因?yàn)椴皇钦嬲臄?shù)組,所以沒(méi)有數(shù)組類型上自帶的那些方法,我們就需要利用下面這幾個(gè)方法去借用數(shù)組的方法。比如借用數(shù)組的 push 方法,請(qǐng)看下面的一段代碼。
var arrayLike = {
? 0: 'java',
? 1: 'script',
? length: 2
}
Array.prototype.push.call(arrayLike, 'jack', 'lily');
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
從中可以看到,arrayLike 其實(shí)是一個(gè)對(duì)象,模擬數(shù)組的一個(gè)類數(shù)組,從數(shù)據(jù)類型上說(shuō)它是一個(gè)對(duì)象,新增了一個(gè) length 的屬性。從代碼中還可以看出,用 typeof 來(lái)判斷輸出的是 'object',它自身是不會(huì)有數(shù)組的 push 方法的,這里我們就用 call 的方法來(lái)借用 Array 原型鏈上的 push 方法,可以實(shí)現(xiàn)一個(gè)類數(shù)組的 push 方法,給 arrayLike 添加新的元素。
從控制臺(tái)的結(jié)果可以看出,數(shù)組的 push 方法滿足了我們想要實(shí)現(xiàn)添加元素的訴求。我們?cè)賮?lái)看下 arguments 如何轉(zhuǎn)換成數(shù)組,請(qǐng)看下面這段代碼。
function sum(a, b) {
? let args = Array.prototype.slice.call(arguments);
// let args = [].slice.call(arguments); // 這樣寫(xiě)也是一樣效果
? console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);? // 3
function sum(a, b) {
? let args = Array.prototype.concat.apply([], arguments);
? console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);? // 3
這段代碼中可以看到,還是借用 Array 原型鏈上的各種方法,來(lái)實(shí)現(xiàn) sum 函數(shù)的參數(shù)相加的效果。一開(kāi)始都是將 arguments 通過(guò)借用數(shù)組的方法轉(zhuǎn)換為真正的數(shù)組,最后都又通過(guò)數(shù)組的 reduce 方法實(shí)現(xiàn)了參數(shù)轉(zhuǎn)化的真數(shù)組 args 的相加,最后返回預(yù)期的結(jié)果。
Array.from 方法類數(shù)組轉(zhuǎn)數(shù)組
對(duì)于類數(shù)組轉(zhuǎn)換成數(shù)組的方式,我們還可以采用 ES6 新增的 Array.from 方法以及展開(kāi)運(yùn)算符的方法。那么還是圍繞上面這個(gè) sum 函數(shù)來(lái)進(jìn)行改變,我們看下用 Array.from 和展開(kāi)運(yùn)算符是怎么實(shí)現(xiàn)轉(zhuǎn)換數(shù)組的,請(qǐng)看下面一段代碼的例子。
function sum(a, b) {
? let args = Array.from(arguments);
? console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);? ? // 3
function sum(a, b) {
? let args = [...arguments];
? console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);? ? // 3
function sum(...args) {
? console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);? ? // 3
從代碼中可以看出,Array.from 和 ES6 的展開(kāi)運(yùn)算符,都可以把 arguments 這個(gè)類數(shù)組轉(zhuǎn)換成數(shù)組 args,從而實(shí)現(xiàn)調(diào)用 reduce 方法對(duì)參數(shù)進(jìn)行累加操作。其中第二種和第三種都是用 ES6 的展開(kāi)運(yùn)算符,雖然寫(xiě)法不一樣,但是基本都可以滿足多個(gè)參數(shù)實(shí)現(xiàn)累加的效果。
講到這里你可以再思考一下這兩種類數(shù)組轉(zhuǎn)換數(shù)組的方法,是不是很好理解呢?
總結(jié)
在這一講中,我把日常開(kāi)發(fā)中有可能遇到的幾種類數(shù)組分別介紹了一遍,又結(jié)合類數(shù)組相關(guān)的應(yīng)用場(chǎng)景進(jìn)行了全方位的講解,類數(shù)組應(yīng)用場(chǎng)景的幾個(gè)例子希望能為你的 JS 編碼能力的提升帶來(lái)幫助和啟發(fā)。最后我又講了類數(shù)組轉(zhuǎn)換成數(shù)組的兩種方式。你可以通過(guò)下面的表格再重新梳理一下類數(shù)組和數(shù)組的異同點(diǎn)。

在前端工作中,開(kāi)發(fā)者往往會(huì)忽視對(duì)類數(shù)組的學(xué)習(xí),其實(shí)在高級(jí) JavaScript 編程中經(jīng)常需要將類數(shù)組向數(shù)組轉(zhuǎn)化,尤其是一些比較復(fù)雜的開(kāi)源項(xiàng)目,經(jīng)常會(huì)看到函數(shù)中處理參數(shù)的寫(xiě)法,例如:[].slice.call(arguments) 這行代碼。