前言:
在開發(fā)或者面試中,我們經(jīng)常會遇到或者被問到關(guān)于“類數(shù)組”的問題,什么是類數(shù)組?什么是類數(shù)組對象?js里面哪些可以稱為類數(shù)組?類數(shù)組又有哪些特點(diǎn)?怎么把類數(shù)組轉(zhuǎn)成js數(shù)組?那么既然這個(gè)問題被反復(fù)問到,以及在我們的編碼過程中難免會遇到類數(shù)組,那我們應(yīng)該怎么去操作類數(shù)組呢,類數(shù)組又隱含著多少知識點(diǎn)呢下面我們開始探討下,可能自己收集的不是那么全面,并且有什么不對的地方也希望被指出,一起學(xué)習(xí)一起進(jìn)步
1. 想想js里面哪些可以稱為類數(shù)組?
首先我也列出來(部分):字符串,arguments, document.getElementByClassName獲取到的元素集合等...
接下來是幾張上面列出來的類數(shù)組的在控制臺打印出來的截圖:
在上面幾張截圖里面都是被可以被稱為類數(shù)組幾種類型,那他們有什么共同特點(diǎn)呢,什么樣的才能被稱為類數(shù)組
2.類數(shù)組的定義 (ArrayLike)
其實(shí)在網(wǎng)上找了很多資料也沒有找到,關(guān)于js里面類數(shù)組的官方定義,但是這不代表我們就不能定義一種具有特定結(jié)構(gòu)的數(shù)據(jù)類型的叫法或者昵稱
根據(jù)網(wǎng)上以及自己理解,總結(jié)下來滿足具有以下兩點(diǎn)特性的結(jié)構(gòu)類型我們稱之為“類數(shù)組”或者有些叫法也叫“偽數(shù)組”,what is this in English? ArrayLike.
- 擁有l(wèi)ength屬性,其它屬性(索引)為非負(fù)整數(shù)
- 不具有數(shù)組所具有的方法(非必須)
上面的定義是非官方的,但是很具有參考性,一般情況下在網(wǎng)絡(luò)上的資料以及我們在使用或者判定過程中都是遵循這樣的一些約定俗成的定義。
再回頭看上面的截圖可以看出上面列出的幾種數(shù)據(jù)類型,都是滿足上面的條件。
3.如何判斷給定的類型是否是類數(shù)組(類數(shù)組對象)?
我們可以根據(jù)關(guān)于類數(shù)組的定義來判斷一個(gè)給定的類型是不是類數(shù)組
function isArrayLike(arrlike) {
return typeof arrlike !== "undefined" && arrlike.length > 0;
}
這是我自己編寫的判斷邏輯,來我們看看著名的類庫“魯大師”(lodash)是怎么做的,
var MAX_SAFE_INTEGER = 9007199254740991;
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
比較一下,比我自己寫的要嚴(yán)謹(jǐn),雖然在我們的業(yè)務(wù)場景下不會遇到這么大的值,但是終究還是嚴(yán)謹(jǐn)。
上面是判斷類數(shù)組,下面在提供一下判斷類數(shù)組對象的方法,同樣來自于“魯大師”
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value);
}
其實(shí)就是在原來類數(shù)組的判斷上加了關(guān)于對象的判斷。
4.類數(shù)組轉(zhuǎn)成數(shù)組
顧名思義,之所以我稱之為類數(shù)組,肯定是有一定原因的不僅是“長得像”(有人還說我長得像雨神, 然后我就喜歡聽雨神的歌了),
首先我們看一下類數(shù)組的遍歷和數(shù)組的遍歷比較:
let arr = ["a", "b", "c"],
arrLike = {
0: "a",
1: "b",
2: "c",
length: 3
},
newArr = [],
newArrLikeArr = [];
for (let i = 0, j = arr.length; i < j; i++ ) {
newArr.push(arr[i]);
}
console.log(newArr); // ["a", "b", "c"]
for (let i = 0, j = arrLike.length; i < j; i++ ) {
newArrLikeArr.push(arrLike[i]);
}
console.log(newArrLikeArr); // ["a", "b", "c"]
可以看出除了變量的,命名不一樣的以外,好像別的什么都一樣,果然是“長得像”,既然長得這么像,但是類數(shù)組看起來好像是很單薄的,因?yàn)樗砩现挥袑傩?,沒有像數(shù)組一樣可以通過一些方法來操作里面的屬性以及值(這時(shí)我們把數(shù)組的索引值以及元素當(dāng)做鍵值對),其實(shí)從上面的對比我們可大膽的猜一下,如果數(shù)組的方法內(nèi)部是通過取length值以及對象取值的形式arr["1"]來做操作的,是不是我們就可以讓類數(shù)組去調(diào)用數(shù)組上面的方法來達(dá)到操作自身的目的呢?
既然上面都是我們的猜測,那我們看下數(shù)組方面的方法內(nèi)部實(shí)現(xiàn),是不是如我們所想,雖然我們看不到數(shù)組原生方法的源碼,但是我們可以從開源的polyfill上面找到我們要的答案:
Array.prototype.find = function (predicate, thisValue) {
var arr = Object(this);
if (typeof predicate !== 'function') {
throw new TypeError();
}
for(var i=0; i < arr.length; i++) {
if (i in arr) {
var elem = arr[i];
if (predicate.call(thisValue, elem, i, arr)) {
return elem;
}
}
}
return undefined;
}
正如我們所想,看上面代碼,如果我們操作的是類數(shù)組對象,同樣能達(dá)到相同的效果。
那么我們就可以讓類數(shù)組對象借用數(shù)組上面的方法了,下面這段代碼是不是很熟悉呢:
function Foo() {
var args = [].slice.call(arguments);
.... // something do
}
在es6之前我們做一個(gè)函數(shù)柯里化或者寫一些不定參數(shù)的函數(shù)或者根據(jù)參數(shù)個(gè)數(shù)來實(shí)現(xiàn)面向?qū)ο缶幊汤锩娴闹剌d方法的時(shí)候,會經(jīng)常用到這樣的寫法。當(dāng)然在es6出來以后,我們有了一種更為簡單的方獲取參數(shù):
function foo(...arguments) {
console.log(arguments);
}
foo(1, 2, 3); // [1, 2, 3]
出了上面非常常用的一種方法來實(shí)現(xiàn)類數(shù)組對象轉(zhuǎn)為數(shù)組的方式,我們還有另外一種:
Array.prototype.splice.call({
length: 2,
0: "a",
1: "b"
}, 0); // ["a", "b"]
上面只是了類數(shù)組怎么轉(zhuǎn)為數(shù)組,既然上面已經(jīng)討論了,類數(shù)組與數(shù)組如此的“長得像”,那么有些情況下,我們在操作類數(shù)組的時(shí)候,是不是不用先轉(zhuǎn)為數(shù)組,而是直接借用數(shù)組的方法來操作類數(shù)組對象呢。答案是肯定的,那么是不是數(shù)組上面所有的方法,類數(shù)組對象都可以借用的?或者我們在編碼過程中怎么去實(shí)現(xiàn)一個(gè)類數(shù)組對象?jQuery的類數(shù)組對象是怎么構(gòu)造的呢?又和我們平時(shí)創(chuàng)建的類數(shù)組對象有什么不同呢?帶著這些疑問我們一起繼續(xù)探討“js類數(shù)組更深層的秘密”。
請移步 解開那一層面紗,js類數(shù)組的小秘密(下篇)