數組的擴展

文章編寫參考 阮一峯《ECMAScript 6 入門》


1.擴展運算符

1.1 基本用法

擴展運算符(spread)是三個點(…)。它好比 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

還記得這樣的方式在函數的參數中也是有應用的,也就是我們的rest參數

let pushArr = (arr, ...values) => arr.push(...values);

上面代碼中使用擴展運算符將數組轉換成參數序列

let add = (x, y) => x + y;
let arr = [1, 2];
add(...arr);    //3

上面代碼中將一個數組用擴展運算符轉換成add的參數序列,使得函數的調用更加簡潔直觀。

由於擴展運算符可以將數組轉換成用逗號分隔的參數序列,所以在函數參數中的應用是很方便的。

let fun = (v, w, x, y, z) => v + w + x + y + z;

let args = [4, 5];

let r = fun(1, ...args, 6, ...[7]); //23

上面代碼中使用一個現有的數組進行擴展運算,作爲求和函數的參數序列的一部分,使得函數參數更加多元化更加靈活。

【擴展運算符後面還使用表達式】

const arr = [...(true ? [1, 2, 3] : [4, 5, 6]), 7]
//[ 1, 2, 3, 7 ]

如果擴展運算符後面是一個空數組,則不會產生任何的效果

[...[], 1]
//[ 1 ]

1.2 利用擴展運算符替換數組的apply方法

在ES6之前我們要展開一個數組作爲函數的參數時,就一般使用數組的apply方法

function add(x, y, z) {
    return x + y + z;
}
var args = [1, 2, 3];
add.apply(null, args);  //6

上面代碼中也就是ES5的常用做法,有了擴展運算符之後,我們可以向下面這樣寫了

function add(x, y, z) {
    return x + y + z;
}
var args = [1, 2, 3];
add(...[args]);

再看一個例子,下面例子是用Math.max方法來求數組中最大值的兩種寫法

//ES5 的寫法
Math.max.apply(null, [1, 2, 3]);

//ES6的寫法
Math.max(...[1, 2, 3]);

//等同於
Math.max(1, 2, 3);

上面代碼片段中可以看出使用擴展運算符明顯簡潔了很多。

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
//ES5
Array.prototype.push.apply(arr1, arr2);
//ES6
arr1.push(...arr2);

上面代碼中,push方法的參數不能是數組,ES5所以只好apply方法變通的進行數組的轉換,ES6使用了擴展運算符之後,就可以直接將數組扔進push方法中。

1.3 擴展運算符的應用

1.3.1 合併數組

有了擴展運算符,數組的合併變得很簡單

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
[...arr1, ...arr2]
//[ 1, 2, 3, 4, 5, 6 ]

1.3.2 與解構賦值相結合

擴展運算符可以與解構賦值結合起來生成數組。

const [first, ...rest] = [1, 2, 3, 4, 5];
first;  //1
rest;   //[ 2, 3, 4, 5 ]

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

【注意】如果擴展運算符用於數組賦值,放在的最後一位,否則會報錯

const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯

1.3.3 函數的返回值

JavaScript 的函數只能返回一個值,如果需要返回多個值,只能返回數組或對象。擴展運算符提供瞭解決這個問題的一種變通方法。

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

上面代碼從數據庫取出一行數據,通過擴展運算符,直接將其傳入構造函數Date。

1.3.4 將字符串轉換爲數組

由於字符串是類數組對象,所以應用擴展運算符可以很輕鬆的將字符串轉換爲真正的數組

[...'Blue']
//[ 'B', 'l', 'u', 'e' ]

1.3.5 實現了 Iterator 接口的對象

任何 Iterator 接口的對象,都可以用擴展運算符轉爲真正的數組。

var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

上面代碼中,querySelectorAll方法返回的是一個nodeList對象。它不是數組,而是一個類似數組的對象。這時,擴展運算符可以將其轉爲真正的數組,原因就在於NodeList對象實現了 Iterator 。

對於那些沒有部署 Iterator 接口的類似數組的對象,擴展運算符就無法將其轉爲真正的數組。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};
let arr = [...arrayLike];
// TypeError: Cannot spread non-iterable object.

上面代碼中,arrayLike是一個類似數組的對象,但是沒有部署 Iterator 接口,擴展運算符就會報錯。這時,可以改爲使用Array.from方法將arrayLike轉爲真正的數組。

1.3.6 Map 和 Set 結構,Generator 函數

擴展運算符內部調用的是數據結構的 Iterator 接口,因此只要具有 Iterator 接口的對象,都可以使用擴展運算符,比如 Map 結構。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

Generator 函數運行後,返回一個遍歷器對象,因此也可以使用擴展運算符。

var go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

上面代碼中,變量go是一個 Generator 函數,執行後返回的是一個遍歷器對象,對這個遍歷器對象執行擴展運算符,就會將內部遍歷得到的值,轉爲一個數組。

如果對沒有 Iterator 接口的對象,使用擴展運算符,將會報錯。

var obj = { a: 1, b: 2 };
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

2.Array.from( )

Array.from方法用於將兩類對象轉爲真正的數組:類似數組的對象(array-like object)和可遍歷(iterable)的對象(包括ES6新增的數據結構Set和Map)。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

上面代碼是一個類似數組的對象,Array.from將它轉爲真正的數組。

// NodeList對象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
  console.log(p);
});

// arguments對象
function foo() {
  var args = Array.from(arguments);
  // ...
}

上面代碼中,querySelectorAll方法返回的是一個類似數組的對象,可以將這個對象轉爲真正的數組,再使用forEach方法。

只要是部署了Iterator接口的數據結構,Array.from都能將其轉爲數組。

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

上面代碼中,字符串和Set結構都具有Iterator接口,因此可以被Array.from轉爲真正的數組。

【如果參數是一個真正的數組,Array.from會返回一個一模一樣的新數組】

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

上面我們提到過擴展運算符,也可以一些特殊的數據結構方便的轉成真正的數組

function fun() {
    let agrs = [...arguments];
    //...
}

擴展運算符背後調用的遍歷器接口(Symbol.iterator),如果一個對象沒有部署該接口,那麼久不能使用擴展運算符進行轉換。Array.from方法還支持類似數組對象,所謂的類似數組對象,本質特徵只有一點,即必須擁有length屬性。因此,任何有length屬性的對象,都可以通過Array.from屬性轉換成數組,而此時擴展運算符值沒法做到的。

let obj = {
    length: 3
}
Array.from(obj);
//[ undefined, undefined, undefined ]

上面代碼中將一個擁有length屬性的對象用Array.from方法轉換成了一個長度爲3的數組,這個是擴展運算符所無法做到的。

對於還沒有部署Array.from方法的瀏覽器,可以使【Array.prototype.slice】方法替代。

const toArray = (
    () => Array.from ? Array.from : obj => [].slice.call(obj)
)();

【Array.from的第二個參數】

Array.from還可以接受第二個參數,作用類似於數組的map方法,用來對每個元素進行處理,將處理後的值放入返回的數組

Array.from([1, 2, 3], x => x * x);
//[ 1, 4, 9 ]

let obj = {
    length: 3
}
Array.from(obj, x => x ? x : 'Blue');
//[ 'Blue', 'Blue', 'Blue' ]

Array.from([1, , 2, , 3], (n) => n || 0);
// [1, 0, 2, 0, 3]

上面代碼中可以看出,Array.from的第二個參數可以理解爲Array.from處理完成之後對生成的數組對象的一個【回調遍歷函數】


3.Array.of( )

Array.of方法用於將一組參數值,轉換爲數組

Array.of(1, 2, 3, 4);
//[ 1, 2, 3, 4 ]

Array.of(1);
//[ 1 ]

Array.of(1, 2);
//[ 1, 2 ]

這個方法的主要目的,是彌補數組構造函數Array()的不足。因爲參數個數的不同,會導致Array()的行爲有差異。

new Array();
//[ ]

new Array(1,2,3);
//[1,2,3]

new Array(3);
//長度爲三的空數組

上面代碼中構造函數Array的參數數量不同時,產生的結果不一致,但是Array.of就不存在這樣的情況,它的行爲非常統一。

【如果Array.of沒有參數則返回一個空的數組】

Array.of方法可以用下面的代碼模擬實現

function ArrayOf() {
    return [].slice.call(arguments);
}

4.數組實例copyWithin( )

這個實例方法根據名稱就很容易知道是幹嘛使的,在當前數組內部,將指定位置的成員複製到其他位置,【會覆蓋掉原來的數組】。

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三個參數

  • target(必需):從該位置開始替換數據。
  • start(可選):從該位置開始讀取數據,默認爲0。如果爲負值,表示倒數。
  • end(可選):到該位置前停止讀取數據,默認等於數組長度。如果爲負值,表示倒數。

    這三個參數都應該是數值,如果不是,會自動轉爲數值。

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

上面代碼表示,從索引爲3(包含)的位置讀取數據以替換索引爲0處開始的元素。

// 將3號位複製到0號位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相當於3號位,-1相當於4號位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 將3號位複製到0號位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 將2號位到數組結束,複製到0號位
var i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 對於沒有部署 TypedArray 的 copyWithin 方法的平臺
// 需要採用下面的寫法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

5.數組實例的find( )和findIndex( )

數組實例的find方法,用於找出【第一個符合條件的數組成員】。它的參數是一個回調函數,所有數組成員依次執行該回調函數,【直到找出第一個返回值爲true的成員,然後返回該成員】。如果沒有符合條件的成員,則返回undefined。

[1, 2, 3, 4, 5].find(x => x > 3);
//4

【find( )】函數參數可以接受3個參數,依次爲當前值,當前位置和當前數組

[1, 2, 3].find((value, index, arr) => index > 1);
//3

上面代碼中,利用函數參數的位置作爲篩選條件,索引大於1的第一個值被篩選了出來。

數組實例的【findIndex】方法的用法與find方法非常類似,返回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

這兩個方法都可以接受第二個參數,用來綁定回調函數的this對象。

另外,這兩個方法都【可以發現NaN】,彌補了數組的IndexOf方法的不足。

[NaN].indexOf(NaN)
// -1

[NaN].findIndex(y => Object.is(NaN, y))
// 0

上面代碼中,indexOf方法無法識別數組的NaN成員,但是findIndex方法可以藉助Object.is方法做到。


6.數組實例的fill( )

fill方法使用給定值,填充一個數組。

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

上面代碼表明,fill方法用於空數組的初始化非常方便。數組中已有的元素,會被全部抹去。

fill方法還可以接受第二個和第三個參數,用於指定填充的起始位置和結束位置。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

上面代碼表示,fill方法從1號位開始,向原數組填充7,到2號位之前結束。


7.數組實例的 entries(),keys() 和 values()

ES6 提供三個新的方法——entries(),keys()和values()——用於遍歷數組。它們都返回一個遍歷器對象(詳見《Iterator》一章),可以用for…of循環進行遍歷,唯一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b', 'c'].keys()) {
    console.log(index);
}
//0
//1
//2

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

如果不使用for…of循環,可以手動調用遍歷器對象的next方法,進行遍歷。

let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

8.數組實例的includes( )

Array.prototype.includes方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法類似。ES2016 引入了該方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

該方法的【第二個參數表示搜索的起始位置】,默認爲0。如果第二個參數爲負數,則表示倒數的位置,如果這時它大於數組長度(比如第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

9.數組的空位

數組的空位指,數組的某一個位置沒有任何值。比如,Array構造函數返回的數組都是空位。

Array(3) // [, , ,]

上面代碼中,Array(3)返回一個具有3個空位的數組。

【注意】,【空位不是undefined】,一個位置的值等於undefined,依然是有值的。空位是沒有任何值,in運算符可以說明這一點。

0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

ES5 對空位的處理,已經很不一致了,大多數情況下會忽略空位,【ES6 則是明確將空位轉爲undefined】

Array.from方法會將數組的空位,轉爲undefined,也就是說,這個方法不會忽略空位。

Array.from(['a',,'b'])
// [ "a", undefined, "b" ]

【擴展運算符(…)也會將空位轉爲undefined】

[...['a',,'b']]
// [ "a", undefined, "b" 

【copyWithin()會連空位一起拷貝】

[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

【fill()會將空位視爲正常的數組位置】

new Array(3).fill('a') // ["a","a","a"]

【for…of循環也會遍歷空位】

et arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章