如何理解 JS 的類數(shù)組

其實(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) 這行代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JavaScript 數(shù)組的 API 經(jīng)常會(huì)被 JS 開(kāi)發(fā)者頻繁使用,在整個(gè) JavaScript 的學(xué)習(xí)過(guò)程中尤...
    金融測(cè)試民工閱讀 1,070評(píng)論 2 0
  • 大家好,我是IT修真院萌新分院第3期的學(xué)員張曉琳,一枚正直、純潔、善良的前端程序員今天給大家分享一下,修真院官網(wǎng)j...
    Demon_0481閱讀 641評(píng)論 0 2
  • 引用類型 Object類型 創(chuàng)建創(chuàng)建Object實(shí)例有兩種方式使用new操作符后跟Object構(gòu)造函數(shù)使用對(duì)象字面...
    quanCN閱讀 363評(píng)論 0 2
  • for 循環(huán) for(初始化; 退出條件; 最終表達(dá)式){//執(zhí)行重復(fù)條件} 輸出1到10for(var i=1...
    饑人谷_Chou閱讀 822評(píng)論 0 1
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂(lè)有人憂愁,有人驚喜有人失落,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,834評(píng)論 28 54

友情鏈接更多精彩內(nèi)容