ES6 - Spread 展開與其餘參數

展開

先宣告兩個陣列

1
2
let arr1 = ['A', 'B', 'C']
let arr2 = ['D', 'E', 'F']

合併陣列

1
2
3
4
5
// ES5
let arrAll = arr1.concat(arr2) // ["A", "B", "C", "D", "E", "F"]

// ES6 展開
let arrAll = [...arr1, ...arr2] // ["A", "B", "C", "D", "E", "F"]

複製陣列

1
2
3
4
// ES5
let copy = arr1.concat() // ['A', 'B', 'C']
// ES6 展開
let copy = [...arr1] // ['A', 'B', 'C']

陣列開頭插入陣列(物件)

1
2
3
4
5
// ES5
Array.prototype.unshift.apply(arr1, arr2) //arr1現在是 ["D", "E", "F", "A", "B", "C"]

// ES6 展開
arr1 = [...arr2, ...arr1] //arr1現在是 ["D", "E", "F", "A", "B", "C"]

原理

1
console.log(...arr1) // A B C

單純印出 ...arr1 會出現陣列內的內容,我們可以理解 ... 這個運算子 的作用是將它後方的陣列值一個個取出來,然後再 push 回去。


類陣列 (array like)

定義:擁有一個 length 屬性若干索引屬性物件

手動創造一個類陣列

以物件實體語法創造一個物件,其成員的屬性皆為索引(數字),並賦予額外 length 屬性。

1
2
3
4
5
6
var arrayLike = { 
0: 'name',
1: 'age',
2: 'sex',
length: 3
}

操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 讀寫
console.log(arrayLike[0]) // name
arrayLike[0] = 'new name'

//長度
console.log(arrayLike.length) // 3

//遍歷
//1
for(var i = 0, len = arrayLike.length; i < len; i++) {
console.log(arrayLike[i])
}
//2 類陣列本身還是屬於物件,因此要使用陣列方法必須這麼做
Array.prototype.forEach.call(arrayLike, function(item){
console.log(item)
})

Arguments

Arguments 是當函數的執行環境被創造時與 環境變數this外部環境一起被 JavaScript 引擎創造出來的物件。他也是一個類陣列,而裡面的屬性是所有被傳入該函數的參數

1
2
3
4
5
function foo() {
console.log(arguments);
}

foo('Dylan', 25, 'Eva', 25);

輸出:

可以看到輸出結果產生了類陣列的物件,而__proto__內的方法皆是物件的原生方法。

如果嘗試用 arguments 使用陣列的 push 方法,會跳出錯誤。因為 arguments是物件不是陣列

1
2
3
4
5
6
7
8
function foo() {
arguments.push(['John', 40]); // Uncaught TypeError: arguments.push is not a function

//補充:如果要取用原生陣列方法要使用
// Array.prototype.push.apply(arguments, ['John', 40])
}

foo('Dylan', 25, 'Eva', 25);

利用展開將類陣列轉換成陣列,如此也可以順利取用陣列的方法。

1
2
3
4
5
6
7
8
function foo() {
let arg = [...arguments]
arg.push('John', 40)

console.log(arg)
}

foo('Dylan', 25, 'Eva', 25);

結果:

querySelectorAll()

1
2
3
4
5
<ul>
<li></li>
<li></li>
<li></li>
</ul>
1
2
let doms = document.querySelectorAll('li')
console.log(doms)

可以發現用 querySelectorAll 所產生的變數doms,看起來好像是個陣列,仔細看發現紅框上面寫的是 NodeList 而不是陣列會顯示的 Array,這是一個典型的類陣列例子。

進一步做驗證,打開 __proto__ 看看內部的原生方法,可以很快的發現和我們所知的陣列方法是有出入的,真正的陣列方法並沒有這麼少。

而陣列方法 concatdoms.__proto__ 也找不到,如果試著對 doms 使用這個方法的話,不意外的跳錯了。


如果類陣列就是很任性的想使用陣列的方法,可以嗎?

假設變數 arrayLike是一個類陣列

ES6
先轉為陣列,之後可以合理使用陣列方法了。

1
2
3
4
//1
[...arrayLike]
//2
Array.from(arrayLike)

ES5
使用callapplybind,呼叫陣列的方法。

1
2
3
4
5
6
//1
Array.prototype.concat.apply([], arrayLike) //指向一個空陣列,將doms連接進去。
//2
Array.prototype.slice.call(arrayLike)
//3
Array.prototype.splice.call(arrayLike, 0) //不適用於querySelectorAll產生的NodeList類陣列

輸出:

看看它的 __proto__,可以發現他已經有所有陣列該有的原生方法了,現在就可以直接取用這些方法,不需要再使用callapplybind,也可以證明它已經變成一個真正的陣列了。


其餘

克服JS奇怪部分 4-39最後提到的Speard,就是指展開與其餘,當時影片錄製時,ES6功能還不夠完整,講師提到未來這個功能如果完善了,將可以取代 Arguments,下面來看看如何使用吧。

1
2
3
4
5
function foo(...people) { // 自定義陣列名稱 (裡面包含傳入的參數)
console.log(people) // 透過自定義名稱,可以取用一個包含所有參數的陣列
}

foo('Dylan', 25, 'Eva', 25)

結果可以發現不同於 Arguments,其餘參數所創造的陣列是純粹的陣列而不是偽陣列:


另外如果還有其他參數並不想加入其餘參數陣列當中,我們可以

1
2
3
4
5
6
7
function foo(area, road, ...people) { // 自定義陣列名稱 (裡面包含傳入的參數)
console.log(area)
console.log(road)
console.log(people)
}

foo('台北市', '羅斯福路', 'Dylan', 25, 'Eva', 25)

結果可以發現若有額外設計參數,一樣會一一對應並帶入,而剩餘的參數將直接被放入其餘參數的陣列當中。

注意:其餘參數一定要在最後,且只能有一個

資料來源

六角學院 - Vue出一個電商網站