1. 程式人生 > >從如何優雅的將類陣列物件轉化為陣列談起

從如何優雅的將類陣列物件轉化為陣列談起

今日研讀阮老師的ES6標準入門,讀到函式的擴充套件方法時看到這麼一段程式碼:

let arrayList = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3
}
var arr = [].slice.call(arrayList) // ["a","b","c"]

很明顯,上面程式碼轉化程式碼的關鍵就是[].slice.call()這一句了。
鄙人不才,平常在專案裡碰到需要解構的json資料時都是直接懟上for/forin硬肛的,現在想想真是羞愧啊,往事不提不提~
阮老師的這個demo勾起了我的興趣,那麼我就從call()講起。
要理解var arr = [].slice.call(arrayList) // ["a","b","c"]

這句程式碼的意思,你需要有以下三點需要理解:

  1. [ ]是什麼?
  2. slice()函式起到什麼作用?
  3. call()函式起到什麼作用?

先講第一點。[ ]是什麼,J開頭的程式設計師肯定都知道,js裡的陣列嘛,沒什麼好說的。值得講講的就一點,就是[]與new Array()到底有什麼區別?它們到底哪個效率高?[]到底需不需要分配記憶體?我不做回答,大家可以自行探究。

再講第二點,slice()函式,我們來看官方MDN定義:

slice() 方法返回一個從開始到結束(不包括結束)選擇的陣列的一部分淺拷貝到一個新陣列物件。且原始陣列不會被修改.

這就很明顯了,這就是[]物件(js裡函式也是一個物件)呼叫了一下slice()函式嘛,為啥呼叫它,因為它返回新的陣列物件呀。那麼問題又來了,是不是隻要會返回新的陣列物件的函式都可以在這裡呼叫呢?我們暫且先不談論,先往下講。

最後說說第三點,call( )函式。我們來看MDN定義:

Function.prototype.call()
call() 方法呼叫一個函式, 其具有一個指定的this值和分別地提供的引數(引數的列表)。

當然,MDN對此函式還有補充:

語法

fun.call(thisArg, arg1, arg2, …)

引數

thisArg
在fun函式執行時指定的this值。需要注意的是,指定的this值並不一定是該函式執行時真正的this值,如果這個函式處於非嚴格模式下,則指定為null和undefined的this值會自動指向全域性物件(瀏覽器中就是window物件),同時值為原始值(數字,字串,布林值)的this會指向該原始值的自動包裝物件。

返回值

返回值是你呼叫的方法的返回值,若該方法沒有返回值,則返回undefined。

簡單來說呢,就是我們呼叫call()函式的根本目的,就是為了去解決js裡單個object物件沒有length屬性,也調用不了slice()函式的痛點,當然這個呼叫也是通過prototype.call()這個原型鏈去呼叫。
如果你嘗試用這句程式碼

var arr = [].slice.call(arrayList,1)  //["b","c"]

call(this,args1,args2…)函式中this之後的引數會隨之傳遞給slice()函式。

如果我們把call()函式換成apply()可以得到一樣的結果嗎?答案是肯定的。

let arrayList = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3
}
var arr = [].slice.apply(arrayList) // ["a","b","c"]

但是如果我們把call()換成bind()呢?還可以得到陣列嗎?答案是否定的。

let arrayList = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3
}
var arr = [].slice.bind(arrayList) // function slice(){...]

經測試,bind()函式果然不負眾望,給咱們返回了一個slice()函式。究其原因,是因為:

bind 是返回對應函式,便於稍後呼叫;apply 、call 則是立即呼叫 。

那麼,除了以上幾個方法外還有什麼可以‘優雅’的轉化陣列的方式嗎?答案是毋庸置疑的。
首先掛上ES6裡已經實現的函式Array.from()
MDN:

Array.from() 方法從一個類似陣列或可迭代物件中建立一個新的陣列例項。

使用起來也異常暴力,深諳簡單就是藝術的美學:

let arrayList = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3
}
var arr = Array.from(arrayList) // ["a","b","c"]

至於相容性情況,對於萬惡之源IE瀏覽器來說肯定是不支援的,這輩子都不可能支援的。(不接受拿Edge來跟我擡槓)

我們還有一個Object.values()函式可以達到類似的目的。下面關門放MDN:

Object.values()方法返回一個給定物件自己的所有可列舉屬性值的陣列,值的順序與使用for…in迴圈的順序相同 ( 區別在於for-in 迴圈列舉原型鏈中的屬性 )。

關於什麼是可列舉性,這個就不深說,簡單來說就是用for-in迭代時能取到物件裡所有的屬性。
我們來看程式碼:

let arrayList = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3
}
var arr = Object.values(arrayList) // ["a","b","c"3]

看到區別了嗎?這次返回的陣列多了一個5,很顯然它是把物件裡的所有key對應的value返回出來了。再看幾個例子:

let arrayList = {
    "a":"1",
    "b":"2",
    "c":"3",
}
var arr = Object.values(arrayList) // ["a","b","c"3]

let arrayList1 = {
    "a":"aa",
    "b":"bb",
    "c":"cc",
}
var arr1 = Object.values(arrayList1) // ["aa","bb","cc"]

以上的例子充分說明Object.values()對引用的引數物件是沒有類陣列物件那種格式限制的。

當然,相容性方面除了IE外,其他瀏覽器稍微高一點版本都是支援的。