...
为 ES6 中新增的 扩展运算符 。它可以将一个数组转换为用逗号分隔的参数序列。
console.log(...[1, 2, 3]);
// 同 console.log(1, 2, 3);
利用这个特性我们可以很方便的将实现了 Iterator
的对象转为数组:
[...document.getElementsByTagName('div')]
// [div, div, div, ...]
扩展运算符还可以用来取代函数的 apply
方法:
// ECMAScript5
function fn(x, y, z) {
console.log(x + y + z); // 6
}
fn.apply(null, [1, 2, 3]);
// ECMAScript6
function fn(x, y, z) {
console.log(x + y + z); // 6
}
fn(...[1, 2, 3]);
// ... 的实际运用
Math.max(...[1, 2, 3]); // 3
Math.max([1, 2, 3]); // NaN
Math.max.apply(null, [1, 2, 3]); // 3
其它实际运用:
1. 克隆简单数组
// 克隆简单数组
const array1 = [1, 2, 3];
// 写法一 (解构赋值)
const [...array2] = array1;
// 写法二 (扩展运算)
const array2 = [...array1];
通过 ...
运算符来拷贝数组的方法是浅拷贝,所以不能用来直接拷贝深数组。
2. 合并数组
const array1 = [1, 'a'];
const array2 = [2, 'b'];
const array3 = [3, { c: 'c' }]; // 应当避免此种拷贝
const array4 = [...array1, ...array2, ...array3];
arra4[5]['c'] === arra3[1]['c']; // true
3. 将字符串转为数组
[...'hello']
// [h, e, l, l, o]
使用此方法可以正确识别四个字节的 Unicode 字符,可用于字符统计。
'x\uD83D\uDE80y'.length // 4
// 注意前面的分号
;[...'x\uD83D\uDE80y'].length // 3
function length(str) {
return [...str].length;
}
length('x\uD83D\uDE80y') // 3
4. 将任意实现了 Iterator 接口的对象转为数组
在写代码中我们经常遇到类数组不能使用 forEach
方法的问题,如:NodeList、HTMLCollection。之前我们做类型转换可能用到了:
// ECMAScript5
[].slice.call(HTMLCollection, null);
或者
// ECMAScript6
Array.from(HTMLCollection);
现在又多了一个方法:
[...document.getElementsByTagName('div')]
上面的 NodeList
或 HTMLCollection
之所以可以使用扩展运算符将其转为真正的数组,原因就在于它们均实现了 Iterator
。
Number.prototype[Symbol.iterator] = function*() {
let i = 0;
let num = this.valueOf();
while (i < num) {
yield i++;
}
}
console.log([...5]) // [0, 1, 2, 3, 4]
Array.from()
同样来自 ECMAScript6,它与扩展运算符的用途很相似,但又有很大的不同。
Array.from: 该方法支持所有的类数组对象,所谓类数组对象就是具有 length
属性的所有对象。因此,只要有 length
属性的对象都可以通过 Array.from
方法转为数组。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
Array.from
方法还可以接受第二个参数,作用类似于数组的 map
方法,用来对每个元素进行处理,将处理后的值放入返回的数组中去。
Array.from([1, 2, 3], (x) => x * x);
// [1, 4, 9]
Array.from([1, 2, 3]).map(x => x * x);
// [1, 4, 9]
扩展运算符(…): 扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。
Array.of
方法用于将一组值,转为数组,是扩展运算符的逆运算。它的出现弥补了数组构造函数 Array()
的不足。
// 数组构造函数存在行为差异
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
// Array.of 方法弥补了差异
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Array.of(undefined) // [undefined]
在 ES5 中可以用下面的方法模拟实现 Array.of
方法。
function ArrayOf() {
return [].slice.call(arguments);
}
数组实例的 find
方法用于找出第一个符合条件的数组成员。它的参数是一个回调函数,该回调函数可以接收三个参数,依次代表: 当前值、当前索引、源数组。
[0, 1, -2, 3].find((item, index, array) => item < 0);
// -2
findIndex
方法与 find
方法相似。不同的是 findIndex
返回的是第一个符合条件数组成员的下标,find
方法返回的是第一个符合条件数组成员本身。
它们都可以接收第二个参数,用于指定回调函数的 this
指向:
[1, 2, 3, 4].find(item => {
console.log(this); // Window
return item === 1;
}, window);
// 1
fill
方法使用给定值填充数组。
[].fill(2); // []
[ , , ].fill(2); // [2, 2]
[1, 1, 1].fill(2); // [2, 2, 2]
new Array(3).fill(2); // [2, 2, 2]
fill
方法的第二参数和第三个参数代表要填充起始位置和结束位置:
['a', 'b', 'c'].fill(66, 1, 2);
// ['a', 66, 'c']
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
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..循环
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']
Array.prototype.includes
方法用于检测一个数组是否包含给定值,返回的是 Boolean
类型。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
'hello'.includes('he') // true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
在没有 includes
方法前,我们通常使用 indexOf
方法来辅助判断数组中是否包含给定值。但是 indexOf
不够语义化,且使用 indexOf
方法不能判断 NaN
的成员。
[1, NaN, 3].indexOf(1) !== -1; // true
[1, NaN, 3].indexOf(NaN) !== -1; // false
数组实例的 flat
方法可以将嵌套数组一层一层的展开,它接收一个参数,代表要展开多少层,值为 0 的时候代表不展开。
[1, [2, 3, [4], 5], 6].flat(); // [1, 2, 3, [4], 5, 6]
[1, [2, 3, [4], 5], 6].flat(0); // [1, [2, 3, [4], 5], 6]
[1, [2, 3, [4], 5], 6].flat(1); // [1, 2, 3, [4], 5, 6]
[1, [2, 3, [4], 5], 6].flat(2); // [1, 2, 3, 4, 5, 6]
flatMap
方法相当于 map
方法和 flat
方法的组合
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
new Array(3); // [,,,]
虽然 [1, 1, 1, 1]
和 [ , , , ]
都有三个 ‘,’ 但是前者的长度是 4, 后者的长度是 3,请各位不要想当然的去猜测。
空位代表什么都没有,并不等同于 undefined
。
0 in [undefined]; // true
0 in [,]; // false