# 数组的扩展

## 扩展运算符

### 含义

console.log(...[1, 2, 3])// 1 2 3console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5[...document.querySelectorAll('div')]// [<div>, <div>, <div>]

function push(array, ...items) {  array.push(...items);}function add(x, y) {  return x + y;}const numbers = [4, 38];add(...numbers) // 42

function f(v, w, x, y, z) { }const args = [0, 1];f(-1, ...args, 2, ...[3]);

const arr = [  ...(x > 0 ? ['a'] : []),  'b',];

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

### 替代数组的 apply 方法

// ES5 的写法function f(x, y, z) {  // ...}var args = [0, 1, 2];f.apply(null, args);// ES6的写法function f(x, y, z) {  // ...}let args = [0, 1, 2];f(...args);

// ES5 的写法Math.max.apply(null, [14, 3, 77])// ES6 的写法Math.max(...[14, 3, 77])// 等同于Math.max(14, 3, 77);

// ES5的 写法var arr1 = [0, 1, 2];var arr2 = [3, 4, 5];Array.prototype.push.apply(arr1, arr2);// ES6 的写法let arr1 = [0, 1, 2];let arr2 = [3, 4, 5];arr1.push(...arr2);

// ES5new (Date.bind.apply(Date, [null, 2015, 1, 1]))// ES6new Date(...[2015, 1, 1]);

### 扩展运算符的应用

（1）复制数组

const a1 = [1, 2];const a2 = a1;a2[0] = 2;a1 // [2, 2]

ES5 只能用变通方法来复制数组。

const a1 = [1, 2];const a2 = a1.concat();a2[0] = 2;a1 // [1, 2]

const a1 = [1, 2];// 写法一const a2 = [...a1];// 写法二const [...a2] = a1;

（2）合并数组

// ES5[1, 2].concat(more)// ES6[1, 2, ...more]var arr1 = ['a', 'b'];var arr2 = ['c'];var arr3 = ['d', 'e'];// ES5的合并数组arr1.concat(arr2, arr3);// [ 'a', 'b', 'c', 'd', 'e' ]// ES6的合并数组[...arr1, ...arr2, ...arr3]// [ 'a', 'b', 'c', 'd', 'e' ]

（3）与解构赋值结合

// ES5a = list[0], rest = list.slice(1)// ES6[a, ...rest] = list

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

const [...butLast, last] = [1, 2, 3, 4, 5];// 报错const [first, ...middle, last] = [1, 2, 3, 4, 5];// 报错

（4）字符串

[...'hello']// [ "h", "e", "l", "l", "o" ]

'x\uD83D\uDE80y'.length // 4[...'x\uD83D\uDE80y'].length // 3

function length(str) {  return [...str].length;}length('x\uD83D\uDE80y') // 3

let str = 'x\uD83D\uDE80y';str.split('').reverse().join('')// 'y\uDE80\uD83Dx'[...str].reverse().join('')// 'y\uD83D\uDE80x'

（5）实现了 Iterator 接口的对象

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

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

（6）Map 和 Set 结构，Generator 函数

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

Generator 函数运行后，返回一个遍历器对象，因此也可以使用扩展运算符。

const go = function*(){  yield 1;  yield 2;  yield 3;};[...go()] // [1, 2, 3]

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

## 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']

// NodeList对象let ps = document.querySelectorAll('p');Array.from(ps).forEach(function (p) {  console.log(p);});// arguments对象function foo() {  var args = Array.from(arguments);  // ...}

Array.from('hello')// ['h', 'e', 'l', 'l', 'o']let namesSet = new Set(['a', 'b'])Array.from(namesSet) // ['a', 'b']

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

// arguments对象function foo() {  const args = [...arguments];}// NodeList对象[...document.querySelectorAll('div')]

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

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

Array.from还可以接受第二个参数，作用类似于数组的map方法，用来对每个元素进行处理，将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);// 等同于Array.from(arrayLike).map(x => x * x);Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]

let spans = document.querySelectorAll('span.name');// map()let names1 = Array.prototype.map.call(spans, s => s.textContent);// Array.from()let names2 = Array.from(spans, s => s.textContent)

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

function typesOf () {  return Array.from(arguments, value => typeof value)}typesOf(null, [], NaN)// ['object', 'object', 'number']

Array.from()可以将各种值转为真正的数组，并且还提供map功能。这实际上意味着，只要有一个原始的数据结构，你就可以先对它的值进行处理，然后转成规范的数组结构，进而就可以使用数量众多的数组方法。

Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack']

Array.from()的另一个应用是，将字符串转为数组，然后返回字符串的长度。因为它能正确处理各种 Unicode 字符，可以避免 JavaScript 将大于\uFFFF的 Unicode 字符，算作两个字符的 bug。

function countSymbols(string) {  return Array.from(string).length;}

## Array.of()

Array.of方法用于将一组值，转换为数组。

Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array.of(3).length // 1

Array() // []Array(3) // [, , ,]Array(3, 11, 8) // [3, 11, 8]

Array.of基本上可以用来替代Array()new Array()，并且不存在由于参数不同而导致的重载。它的行为非常统一。

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

Array.of总是返回参数值组成的数组。如果没有参数，就返回一个空数组。

Array.of方法可以用下面的代码模拟实现。

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

## 数组实例的 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号位[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号位let 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]

## 数组实例的 find() 和 findIndex()

[1, 4, -5, 10].find((n) => n < 0)// -5

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

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

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

## 数组实例的 fill()

fill方法使用给定值，填充一个数组。

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

fill方法还可以接受第二个和第三个参数，用于指定填充的起始位置和结束位置。

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

## 数组实例的 entries()，keys() 和 values()

ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象（详见《Iterator》一章），可以用for...of循环进行遍历，唯一的区别是keys()是对键名的遍历、values()是对键值的遍历，entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {  console.log(index);}// 0// 1for (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"

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']

## 数组实例的 includes()

Array.prototype.includes方法返回一个布尔值，表示某个数组是否包含给定的值，与字符串的includes方法类似。ES2016 引入了该方法。

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

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

if (arr.indexOf(el) !== -1) {  // ...}

indexOf方法有两个缺点，一是不够语义化，它的含义是找到参数值的第一个出现位置，所以要去比较是否不等于-1，表达起来不够直观。二是，它内部使用严格相等运算符（===）进行判断，这会导致对NaN的误判。

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

includes使用的是不一样的判断算法，就没有这个问题。

[NaN].includes(NaN)// true

const contains = (() =>  Array.prototype.includes    ? (arr, value) => arr.includes(value)    : (arr, value) => arr.some(el => el === value))();contains(['foo', 'bar'], 'baz'); // => false

• Map 结构的has方法，是用来查找键名的，比如Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target, propertyKey)
• Set 结构的has方法，是用来查找值的，比如Set.prototype.has(value)WeakSet.prototype.has(value)

## 数组的空位

Array(3) // [, , ,]

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

ES5 对空位的处理，已经很不一致了，大多数情况下会忽略空位。

• forEach(), filter(), every()some()都会跳过空位。
• map()会跳过空位，但会保留这个值
• join()toString()会将空位视为undefined，而undefinednull会被处理成空字符串。
// forEach方法[,'a'].forEach((x,i) => console.log(i)); // 1// filter方法['a',,'b'].filter(x => true) // ['a','b']// every方法[,'a'].every(x => x==='a') // true// some方法[,'a'].some(x => x !== 'a') // false// map方法[,'a'].map(x => 1) // [,1]// join方法[,'a',undefined,null].join('#') // "#a##"// toString方法[,'a',undefined,null].toString() // ",a,,"

ES6 则是明确将空位转为undefined

Array.from方法会将数组的空位，转为undefined，也就是说，这个方法不会忽略空位。

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

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

copyWithin()会连空位一起拷贝。

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

fill()会将空位视为正常的数组位置。

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

for...of循环也会遍历空位。

let arr = [, ,];for (let i of arr) {  console.log(1);}// 1// 1

entries()keys()values()find()findIndex()会将空位处理成undefined

// entries()[...[,'a'].entries()] // [[0,undefined], [1,"a"]]// keys()[...[,'a'].keys()] // [0,1]// values()[...[,'a'].values()] // [undefined,"a"]// find()[,'a'].find(x => true) // undefined// findIndex()[,'a'].findIndex(x => true) // 0