2020年2月13日 星期四

[轉貼] Array.slice 8 種不同用法

出處:https://juejin.im/post/5d25259af265da1bad572d75

JS數組slice方法是JS語言中最強大、最常用的內建函數之一。
隨著React和其他面向功能的JavaScript實踐的興起,它變得越來越重要,原因有兩個:
  1. 函數式編程,尤其是高階函數,與數據列表密切配合
  2. 函數式編程需要純函數,即不會產生副作用或修改輸入數據的函數
JavaScript 數組slice方法符合這兩個標准。
slice方法可以在不修改原始列表的情況下創建列表子集的淺拷貝。因此,它為編寫函數式 JS 提供了一個關鍵的構建塊。
在這篇文章中,我們將通過實例來掌握slice方法,探索它的8種不同用法。
注意:slice 方法不要與splice方法混淆,splice方法會修改原始數組。

slice 工作原理

在深入研究一些更高級的用法之前,讓我們看一下slice方法的基礎知識。
MDN文檔,slice 是數組上的一個方法,它最多有兩個參數:
arr.slice([begin[, end]])
復制代碼
begin
從該索引處開始提取原數組中的元素,如果該參數為負數,則表示從原數組中的倒數第幾個元素開始提取,slice(-2)表示提取原數組中的倒數第二個元素到最後一個元素(包含最後一個元素)。 如果省略 begin,則 slice 從索引 0 開始。
end
在該索引處結束提取原數組元素(從0開始)。slice會提取原數組中索引從 begin 到 end 的所有元素(包含begin,但不包含end)。
slice(1,4) 提取原數組中的第二個元素開始直到第四個元素的所有元素 (索引為 1, 2, 3的元素)。
如果該參數為負數, 則它表示在原數組中的倒數第幾個元素結束抽取。 slice(-2,-1)表示抽取了原數組中的倒數第二個元素到最後一個元素(不包含最後一個元素,也就是只有倒數第二個元素)。
如果 end 被省略,則slice 會一直提取到原數組末尾。如果 end 大於數組長度,slice 也會一直提取到原數組末尾。

基本用法

我們的前4個例子突出slice的核心功能。

用法1:簡單的復制

const arr2 = arr.slice()
復制代碼
沒有任何參數的slice執行一個簡單的淺拷貝。當前,主流的用法還是使用展開運算符合來實現,但是如果在舊的代碼庫中,或者沒有使用babel的構建步驟,可能仍然希望使用slice

用法2:獲取從 N 開始的子數組

使用slice方法最簡單的方法就是原始數組從N開始抽取的所有元素。
一種情況是希望彈出數組的第一個元素並使用它,返回剩余的數組,但希望在不修改原始數組的情況下執行此操作。
function useone (arr) {
  const usedItem = arr[0]
  return arr.slice(1)
}
復制代碼

用法3:獲取從末尾 N 開始的子數組

slice的另一種使用方法是獲取數組的末尾,利用的是負索引從末尾開始計數。
這種負索引使刪除任意數量的元素變得超級簡單。例如,如果你只想抓取3個
const last3 = arr.slice(-3)
復制代碼

用法4:獲取數組的前n個

獲取數組的前面的數,我們需要使用第二個參數:end
當有兩個參數時,slice方法返回一個從begin開始但不包括end的集合。
由於JavaScript數組是從0開始的(索引從0開始),這使得獲取前N個元素變得非常簡單:
const first4 = arr.slice(0, 4)

用法5:獲取數組中某段子數組

如果我們想要使用slice從任何索引開始獲取數組的一段,該怎麼辦?
為此,我們需要從 (begin, length) 轉換為(begin, end)。 計算邏輯很簡單,我們可以定義一個簡單的函數來做到這一點:
function pullSegment(arr, begin, length) {
  return arr.slice(begin, begin + length);
}
復制代碼

處理類似數組的對象

JavaScript中,數組是一個特殊的對象,其property名為正整數,且其length屬性會隨著數組成員的增減而發生變化,同時又從Array構造函數中繼承了一些用於進行數組操作的方法。
而對於一個普通的對象來說,如果它的所有property名均為正整數,同時也有相應的length屬性,那麼雖然該對象並不是由Array構造函數所創建的,它依然呈現出數組的行為,在這種情況下,這些對象被稱為 「類數組對象」 。
slice方法也可用於類似數組的對象。
一些類似數組包如arguments(用於訪問傳遞給函數的所有參數的關鍵字),NodeLists(從返回節點列表的任何DOM API方法返回),甚至是使用數字索引並添加length屬性的原始對象。
要在類似數組的對象上使用slice方法,需要直接從Array.prototype引用它,如下所示:
Array.prototype.slice.call(arguments)
復制代碼
在這特定的場合中會很有用處。

用法6:將類似數組的對象轉換為數組

slice在類似數組的對象上的一個常見用途是將它們轉換為實際數組。 例如:
const args = Array.prototype.slice.call(arguments);
你為什麼要這麼做?為了使用數組方法。例如,想象一個像這樣的函數
function addOne() { 
  return arguments.map(i => i+1); 
}
復制代碼
這看起來可行,但如果你試著去做,你就會得到錯誤:
> addOne(1, 2, 3)
TypeError: arguments.map is not a function
    at test (repl:2:18)
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:44:33)
    at REPLServer.defaultEval (repl.js:239:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:440:10)
    at emitOne (events.js:120:20)
    at REPLServer.emit (events.js:210:7)
    at REPLServer.Interface._onLine (readline.js:279:10)
復制代碼
這是因為arguments 實際上不是數組,而是類似數組的對象。 可以使用slice實現此功能,如下所示:
function addOne() {
  return Array.prototype.slice.call(arguments).map(i => i+1)
}
復制代碼
現在就可以得到了你所希望的數據:
> addOne(1, 2, 3) 
 [ 2, 3, 4 ]
復制代碼

用法7:將任意長度多余的參數強制轉換為數組

有時希望接受函數的多余參數,組成一個數組。
較新版本的JavaScript引入了所謂的Rest語法來處理這個問題,但是如果為為了兼容舊瀏覽器,你可以使用slice做到這一點:
function myFunc(a, b) { 
  const extraArgs = Array.prototype.slice.call(arguments, 2); 
}
復制代碼
這允許使用任意數量的參數調用myFunc, 例如:
myFunc(1, 2, 3, 4, 5, 6, 7, 8)
復制代碼
在函數裡面會得到a == 1b === 2extraArgs=== [3,4,5,6,7,8]

用法8:修改數組中的特定索引

slice在函數上下文中一個強大而常見的用法是替換數組中特定項的值。
從本質上講,這很簡單,只需要分配新值,但是在函數世界中,不能修改原始數組。
相反,可以將slice與擴展運算符一起使用,以返回一個相同但對於要更新的​​索引的新數組:
function replaceIdx(arr, index, newVal) {
  return [
    ...arr.slice(0, index),
    newVal,
    ...arr.slice(index + 1)
  ]
}
復制代碼

偏函數應用

偏函數應用,英文是partial application,也可以譯作「局部應用」、「部分應用」、「偏應用」
函數式編程中的另一種常見模式是所謂的偏函數應用:將函數預先應用於函數,然後返回一個新函數。
這種模式允許你組合函數,通過使用具有不同預應用參數的相同核心函數來創建更大的可重用性。
雖然像Haskell這樣的純函數語言本身支持偏函數應用程序,但是在JavaScript中,我們可以使用slice實現一個函數來實現它
var partial = function() {
  const fn = arguments[0];
  const args = Array.prototype.slice.call(arguments, 1);

  // Return a function that calls fn
  return function() {
    var remainingArgs = Array.prototype.slice.call(arguments);
    return fn.apply(this, args.concat(remainingArgs));
  }
}
復制代碼

2020年2月6日 星期四

[轉貼] JavaScript Array 陣列操作方法大全 ( 含 ES6 )

出處:https://www.oxxostudio.tw/articles/201908/js-array.html

陣列的操作是 JavaScript 裡很重要也很常用到的技巧,這篇文章一次整理常用的陣列操作方法 ( 包含 ES6 的 map、forEach、every、some、filter、find、from、of...等 ),熟悉了這些陣列操作的方法,寫起程式也就會更加簡潔俐落呦。

操作方法分類
因為陣列操作的方法有很多,下面是我自己針對這些方法做的簡單分類,點選方法名稱,就能夠跳轉看對應的說明和用法呦~
分類方法
會改變原始陣列push()pop()shift()unshift()reverse()splice()sort()copyWithin()fill()
回傳陣列元素資訊或索引值lengthindexOf()lastIndexOf()find()findIndex()filter()
針對每個元素處理forEach()
產生新的陣列或新的值join()concat()slice()map()reduce()reduceRight()flat()flatMap()Array.from()Array.of()toString()
判斷並回傳布林值every()some()includes()Array.isArray()
其他用法keys()valueOf()

push()

push()可以將某些值加入到陣列的最後一個位置,不限制添加數量,欲添加的內容使用逗號隔開即可,加入後陣列長度會增加。
使用push()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.push(9, 10);
console.log(a);  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

pop()

pop()會移除 ( 取出 ) 陣列的最後一個元素。
使用pop()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
let b = a.pop();
console.log(a);  // [1, 2, 3, 4, 5, 6, 7]
console.log(b);  // 8

shift()、unshift()

shift()會移除 ( 取出) 陣列的第一個元素。
使用shift()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
let b = a.shift();
console.log(a);  // [2, 3, 4, 5, 6, 7, 8]
console.log(b);  // 1
unshift()會將指定的元素添加到第一個位置。
使用nushift()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.unshift(100,200,300);
console.log(a);  // [100, 200, 300, 1, 2, 3, 4, 5, 6, 7, 8]

reverse()

reverse()會將陣列反轉。
使用push()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.reverse();
console.log(a); // [8, 7, 6, 5, 4, 3, 2, 1]

splice()

splice()可以移除或新增陣列的元素,它包含了三個參數,第一個是要移除或要添加的序列號碼 ( 必填 ),第二個是要移除的長度 ( 選填,若不填則第一個號碼位置後方的所有元素都會被移除,若設定為 0 則不會有元素被移除 ),第三個是要添加的內容 ( 選填 )
使用splice()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.splice(5,1);
console.log(a);   // [1, 2, 3, 4, 5, 7, 8] ( 6 被移除了 )
設定第三個參數就能夠添加或取代元素。
let a = [1,2,3,4,5,6,7,8];
a.splice(5,1,100);
console.log(a);   // [1, 2, 3, 4, 5, 100, 7, 8] ( 6 被移除,100 加到第 5 個位置  )

let b = [1,2,3,4,5,6,7,8];
b.splice(5,3,100,200,300);
console.log(b);   // [1, 2, 3, 4, 5, 100, 200, 300] ( 6,7,8 被移除,100,200,300 加到第 5,6,7 個位置  )

let c = [1,2,3,4,5,6,7,8];
c.splice(5,0,100);
console.log(c);   // [1, 2, 3, 4, 5, 100, 6, 7, 8] ( 沒有元素被移除,100 加到第 5 個位置 )

sort()

sort()可以針對陣列的元素進行排序,裡頭包含了一個排序用的判斷函式,函式內必須包含兩個參數,這兩個參數分別代表陣列裡第 n 個和第 n+1 個元素,透過比較第 n 和第 n+1 個元素的大小來進行排序。
使用sort()後會改變原本的陣列內容。
let a = [1,3,8,4,5,7,6,2];
a.sort((x,y) => y - x);
console.log(a);   // [8, 7, 6, 5, 4, 3, 2, 1]
a.sort((x,y) => x - y);
console.log(a);   // [1, 2, 3, 4, 5, 6, 7, 8]
如果不使用判斷函式,預設會將元素轉換成字串,並採用 unicode 來判斷,這也會造成某些數字的排序錯誤。
let a = [1,3,8,4,5,7,6,2,9,10,11];
a.sort();
console.log(a);   // [1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9]

copyWithin()

copyWithin()能複製陣列中的某些元素,並將它們放到並取同一個陣列指定的位置,copyWithin()有三個參數,第一個是要置換的位置 ( 必填 ),第二個是從什麼位置開始複製 ( 選填,預設 0 ),第三個是停止複製的元素的前一個位置 ( 選填,預設等於陣列長度 )。
使用copyWithin()後會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.copyWithin(2);
console.log(a); // [1,2,1,2,3,4,5,6] ( 因 7 和 8 超過陣列長度,只出現到 6 )

let b = [1,2,3,4,5,6,7,8];
b.copyWithin(3,1,3);
console.log(b); // [1,2,3,2,3,6,7,8] ( 複製 2,3 取代 4,5 )

fill()

fill()會把陣列中所有元素,置換為指定的值,fill()有三個參數,第一個是準備要置換的內容 ( 必填 ),第二個是從什麼位置開始置換 ( 選填,不設定就全部置換 ),第三個是停止置換的元素的前一個位置 ( 選填,預設等於陣列長度 )。
使用fill()會改變原本的陣列內容。
let a = [1,2,3,4,5,6,7,8];
a.fill('a');
console.log(a); // ['a','a','a','a','a','a','a','a']

let b = [1,2,3,4,5,6,7,8];
b.fill('b',3,5);
console.log(b); // [1,2,3,'b','b',6,7,8]

length()

length可以取得陣列的長度 ( 所有元素的數量 )。
let a = [1,2,3,4,5,6,7,8];
console.log(a.length);  // 8

indexOf()、lastIndexOf()

indexOf()會判斷陣列中是否包含某個值,判斷的方式為「由左而右」,如果有包含就回傳這個值在陣列中的索引值,如果沒有就回傳 -1,有兩個參數,第一個參數表示要判斷的值 ( 必填 ),第二個參數表示從陣列的哪個位置開始判斷 ( 選填,預設為 0 )。
let a = [1,2,3,4,5,6,7,8];
console.log(a.indexOf(4));   // 3
console.log(a.indexOf(4,5)); // -1 ( 搜尋 5,6,7,8 沒有 4 )
lastIndexOf()會判斷陣列中是否包含某個值,判斷的方式為「由右而左」,如果有包含就回傳這個值在陣列中的索引值,如果沒有就回傳 -1,有兩個參數,第一個參數表示要判斷的值 ( 必填 ),第二個參數表示判斷到陣列的哪個位置 ( 選填,預設為整個陣列長度 - 1 )。
let a = [1,2,3,4,5,6,7,8];
console.log(a.lastIndexOf(3));   // 2
console.log(a.lastIndexOf(3,1)); // -1 ( 只判斷 1,2,所以沒有 3 )

find()

find()會將陣列中的「每一個」元素帶入指定的函式內做判斷,並會回傳第一個符合判斷條件的元素,如果沒有元素符合則會回傳 undefined。
let a = [1,2,3,4,5,6,7,8];
console.log(a.find(e => e > 3)); // 4
console.log(a.find(e => e < 0)); // undefined

findIndex()

findIndex()會將陣列中的「每一個」元素帶入指定的函式內做判斷,並會回傳第一個符合判斷條件元素的位置號碼,如果沒有元素符合則會回傳 -1。
let a = [1,2,3,4,5,6,7,8];
console.log(a.findIndex(e => e > 3)); // 3
console.log(a.findIndex(e => e < 0)); // -1

filter()

filter()會將陣列中的「每一個」元素帶入指定的函式內做判斷,如果元素符合判斷條件則會傳出,成為一個新的陣列元素。
let a = [1,2,3,4,5,6,7,8];
console.log(a.filter(e => e > 3));    // [4, 5, 6, 7, 8]
console.log(a.filter(e => e%2 == 0)); // [2, 4, 6, 8]

forEach()

forEach()會將陣列中每個元素套用到指定的函式裡進行運算,函式有三個參數,第一個參數表示每個元素的值 ( 必填 ),第二個參數為該元素的索引值 ( 選填 ),第三個參數則表示原本的陣列 ( 選填 )。
let a = [1,2,3,4,5];
let b = 0;
a.forEach(item => {
    b = b + item;
});
console.log(b); // 15 ( 1+2+3+4+5 )
如果透過第二和第三個參數搭配,就能做到改變原本陣列的效果。
let a = [1,2,3,4,5];
a.forEach((item, index, arr) => {
    arr[index] = item * 10;
});
console.log(a); // [10,20,30,40,50]

join()

join()可以將陣列中所有元素,藉由指定的字符合併在一起變成字串呈現,若沒有指定字符預設會用「逗號」合併。
let a = [1,2,3,4,5,6,7,8];
console.log(a.join());      // 1,2,3,4,5,6,7,8
console.log(a.join(''));    // 12345678
console.log(a.join('@@'));  // 1@@2@@3@@4@@5@@6@@7@@8

concat()

concat()可以將兩個陣列合併在一起,如果是使用 ES6 語法也可以用擴展運算符...來代替。
let a = [1,2,3,4,5];
let b = [6,7,8,9];

let c = a.concat(b);
let d = [...a, ...b]; // 使用 ...

console.log(c); // [1,2,3,4,5,6,7,8,9]
console.log(d); // [1,2,3,4,5,6,7,8,9]

slice()

slice()可以截取出陣列某部份的元素為一個新的陣列,有兩個必填的參數,第一個是起始位置,第二個是結束位置 ( 操作時數字減 1 )。
let a = [1,2,3,4,5,6,7,8];
let b = a.slice(2,4);
console.log(b); // [3, 4]

map()

map()會處理陣列中每個元素,最後回傳出一個新的陣列,裡頭有一個函式 ( 必填 ) 和一個回傳函式裡的 this 參數 ( 選填 ),函式內又包含三個參數,第一個是每個元素的值 ( 必填 ),第二個是當前元素的索引值 ( 選填 ),第三個是當前的陣列 ( 選填 )。
let a = [1,2,3,4,5,6,7,8];
let b = a.map(e => {
    return e + 10;
});
console.log(b); // [11, 12, 13, 14, 15, 16, 17, 18]
套用第二個和第三個參數的變化
let a = [1,2,3,4,5,6,7,8];
let b = a.map((e,i,arr) => {
    return `${e}${i}${arr.find(e => e%5 == 1)}`; // 組合成「元素 + 索引值 + 除以五餘數為 1 的元素」
});
console.log(b); // ['101', '211', '321', '431', '541', '651', '761', '871']
如果要使用回傳函式裡 this 的參數,則「不能使用」箭頭函式,因為箭頭函式的 this 指向和函式的 this 指向不同,所以要用一般的函式處理。
let a = [1,2,3,4,5,6,7,8];
let b = a.map(function(e){
    return e + this; // 此處的 this 為 10
}, 10);
console.log(b); // [11, 12, 13, 14, 15, 16, 17, 18]

reduce()、reduceRight()

reduce()可以將陣列中每個元素進行計算,每次計算的結果會再與下個元素作計算,直到結束為止,裡頭包含一個函式 ( 必填 ) 和初始計算的數值 ( 選填 ),函式內有四個參數,第一個是計算的值 ( 必填 ),第二個是取得的元素 ( 必填 ),第三個是該元素的索引值 ( 選填 ),第四個是原本的陣列 ( 選填 )。
let a = [1,2,3,4,5,6,7,8];
let b = a.reduce(function(total, e){
    return total + e;
});
console.log(b); // 36 ( 1+2+3+4+5+6+7+8=36 )
reduceRight()reduce()大同小異,只是其計算方式是由右到左,對於加法來說沒什麼影響,但對於減法而言就有差異。
let a = [1,2,3,4,5,6,7,8];
let b = a.reduce(function(total, e){
    return total - e; 
});
console.log(b); // -34 ( 1-2-3-4-5-6-7-8 = -34 )
let c = a.reduceRight(function(total, e){
    return total - e;
});
console.log(c); // -20 ( 8-7-6-5-4-3-2-1 = -20 )

flat()

flat()可以將一個多維陣列的深度轉成一維 ( 扁平化 ),它有一個選填的參數,代表要轉換的深度數字,預設為 1,如果深度有很多層,可使用Infinity來全部展開成一維陣列。
let a = [1,2,[3],[4,[5,[6]]]];
let b = a.flat();
let c = a.flat(2);
let d = a.flat(Infinity);
console.log(b);   // [1, 2, 3, 4, [5, [6]]]
console.log(c);   // [1, 2, 3, 4, 5, [6]]
console.log(d);   // [1, 2, 3, 4, 5, 6]

flatMap()

flatMap()的方法等於map()flat()的組合,在運算後直接將陣列扁平化處理。
let a = [1,2,[3],[4,5]];
let b = a.flatMap(e => e+1);
let c = a.map(e => e+1).flat();
console.log(b);  // [2, 3, "31", "4,51"] ( 可以看到 b 和 c 得到的結果相同 )
console.log(c);  // [2, 3, "31", "4,51"]

Array.isArray()

Array.isArray()能判斷一個物件是否為陣列,如果是就回傳 true,不然就回傳 false。
let a = [1,2,3,4,5,6,7,8];
let b = 123;
let c = 'hello';
let d = {d1:1,d2:2};
console.log(Array.isArray(a)); // true
console.log(Array.isArray(b)); // false
console.log(Array.isArray(c)); // false
console.log(Array.isArray(d)); // false

Array.from()

Array.from()會將「類陣列物件」或是「可迭代的物件」轉換成陣列,Array.from()有兩個參數,第一個參數為「類陣列物件」或「可迭代的物件」( 必填 ),第二個參數則是改變轉換成陣列元素的函式 ( 選填 )。
類陣列物件具有 length 屬性以及索引化 index 的元素,可迭代物件表示具有可以利用迭代的方式取得它自己本身的元素,例如 Map 和 Set...等。( 參考 MDN 說法 )
let a = 'abcde';
let b = Array.from(a);
console.log(b); // ['a','b','c','d','e']

let c = Array.from(a, e => e + e);
console.log(c); // ['aa','bb','cc','dd','ee']
類陣列物件寫法必須包含 length 屬性,且物件 key 須為 0 開始的數字,對應轉換後的元素索引。
let a = {
    '0': 14, 
    '2': 13,
    '1': 7,
    '3': 9,
    '4': 6,
    length: 5
};
let b = Array.from(a);
console.log(b);  // [14,7,13,9,6]

Array.of()

Array.of()可以快速將數字、字串等內容,轉換成陣列。
let a = Array.of(1,'a',2,'b',3);
console.log(a);   // [1, "a", 2, "b", 3]

toString()

toString()會把整個陣列轉換成文字。
let a = [1,2,3,4,5,6,7,8];
let b = a.toString();
console.log(b);   // 1,2,3,4,5,6,7,8

every()

every()會將陣列中的「每一個」元素帶入指定的函式內做判斷,只要有任何一個元素不符合判斷條件,會回傳 false,如果全部符合,就會回傳 true。
let a = [1,2,3,4,5,6];
console.log(a.every(e => e > 3)); // fasle ( 因為 1、2 小於 3,3 等於 3 )
console.log(a.every(e => e > 0)); // true

some()

some()會將陣列中的「每一個」元素帶入指定的函式內做判斷,只要有任何一個元素符合判斷條件,就會回傳 true,如果全都不符合,就會回傳 false。
let a = [1,2,3,4,5,6];
console.log(a.some(e => e > 3)); // 回傳 true,因為 4、5、6 大於 3
console.log(a.some(e => e > 6)); // 回傳 fasle,因為全都小於或等於 6

includes()

includes()會判斷陣列中是否包含某個值,如果有包含就回傳 true,否則回傳 false,有兩個參數,第一個參數表示要判斷的值 ( 必填 ),第二個參數表示從陣列的哪個位置開始判斷 ( 選填 )。
let a = [1,2,3,4,5,6,7,8];
console.log(a.includes(2));   // true
console.log(a.includes(2,2)); // false ( 搜尋 3,4,5,6,7,8 沒有 2 )

valueOf()

valueOf()會回傳陣列的原始值,如果原本的陣列有修改,那麼回傳的原始值也會跟著改變
let a = [1,2,3,4,5,6,7,8];
let b = a.valueOf();
console.log(a);    // [1, 2, 3, 4, 5, 6, 7, 8]
let c = a.valueOf();
a.shift();
console.log(a);    // [2, 3, 4, 5, 6, 7, 8]
console.log(b);    // [2, 3, 4, 5, 6, 7, 8] ( 因為 a 的原始值更動了,所以 b 也變了 )
console.log(c);    // [2, 3, 4, 5, 6, 7, 8]

keys()

keys()會回傳陣列中的每一個索引值 ( key ) 成為一個新的 Array Iterator 物件,因為是 Array Iterator 物件,可以透過for...of來取得。
let a = ['a','b','c','d','e'];
let b = a.keys(); 

for (let key of b) {
    console.log(key); // 連續出現 1、2、3、4、5
}