愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

《ECMAScript6入门》学习笔记之数组的扩展

一、扩展运算符

ES6引入了扩展运算符...,它可以将一个数组或者一个iterable的对象展开。用法有:

console.log(...[1, 2, 3]);
// 相当于:
console.log(1, 2, 3);

1、展开运算符可以部分代替apply方法,如:

Math.max.apply(null, [1, 2, 3]);
// 可以改写为
Math.max(...[1, 2, 3]);

2、可以用来合并数组,代替concat()方法:

const arr = [1, 2, 3];
arr.concat([4, 5, 6]); // [1, 2, 3, 4, 5, 6]
// 可以改写为
[...arr, ...[4, 5, 6]]; // [1, 2, 3, 4, 5, 6]

3、可以展开字符串

[...'hello'];
// 得到:['h', 'e', 'l', 'l', 'o']

这种写法能够正确展开32位的Unicode,即避免了32位的Unicode被识别为两个字符的问题,如:

const str = 'x\uD83D\uDE80y';
str.length;      // 4
[...str].length; // 3,正确

4、实现Iterator接口的对象,都可以展开
只要一个对象里面实现了Iterator接口,那么这个对象就可以展开,如:

const list = document.querySelectorAll('li');
[...list]; // [li, li, li, li]

使用console.dir(list)来打印,可以发现NodeList的原型对象上,有Symbol.iterator属性,即实现了Iterator。
如果一个对象没有实现Iterator接口,那么即使这个对象是array-like的,也不能展开,即:

const obj = {
    0: 'A',
    1: 'B',
    2: 'C',
    length: 3
};
[...obj]; // obj[Symbol.iterator] is not a function

可以使用Array.from将其转化为真正的数组,如:

[...Array.from(obj)];

5、Map/Set、Generator函数展开
由于MapSetGenerator函数里都实现了Symbol.iterator,所以它们都是可展开的:

[...new Set([1, 2, 3])]; // [1, 2, 3]

const map = new Map([
    [1, 'A'],
    [2, 'B'],
    [3, 'C']
]);
[...map];          // [[1, 'A'], [2, 'B'], [3, 'C']]
[...map.keys()];   // [1, 2, 3]
[...map.values()]; // ['A', 'B', 'C']

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


二、Array.from()

Array.from()可以将两类对象转化为真正的数组:
1)Array-Like对象(如arguments
2)可遍历对象
除了可以转化为一个数组外,Array.from()还支持第二个参数,它相当于map()方法,如:

const obj = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
};
Array.from(obj, x => -x); // [-1, -2, -3];

此外,如果第二个参数指定的map函数里用到了this,那么还可以传入第三个参数来绑定this,如:

const _t = { amount: 5 };
Array.from(obj, function(x) {
    return x + this.amount;
}, _t); // [6, 7, 8];

注意: 由于箭头函数内部没有this,所以如果要传入第三个参数来绑定this的话,那么第二个参数就必须用普通函数,否则不会生效


三、Array.of()

Array.of()可以将一组值,转化为数组,虽然Array构造函数也可以实现,但是会有一些问题,如:

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

Array构造函数,只有在传入的参数大于1个时候,才会当做数组元素传入,如果传入的参数只有一个,那么这个参数指定的是数组的长度,会初始化为n个空位(空位不是undefined),而Array.of()的行为则非常统一,它会完全根据我们传入的参数来构造数组,如:

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


四、Array.prototype.copyWithin()

这个方法可以将数组元素的一部分复制到另一部分,它的语法为:

arr.copyWithin(target, start = 0, end = arr.length)

即会从数组的[start, end)区间内的元素,复制到target开始的位置,例子如:

[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// [3, 4)的元素为4,所以复制后得:[4, 2, 3, 4, 5]

如果位置为负值的话,则是从末尾往前数的,如:

[1, 2, 3, 4, 5].copyWithin(0, -2, -1);
// [-2, -1)的元素为4,所以复制后得:[4, 2, 3, 4, 5]


五、查找方法:find()findIndex()

这两个方法可以用来查找数组中出现的满足过滤函数的第一个元素或者索引,如:

const arr = [1, 2, 3, -2, 4, 5];
arr.find(x => x < 0); // -2
arr.findIndex(x => x < 0); // 3

find()方法中,如果找不到对应的元素,则返回undefined,而在findIndex()中,如果找不到对应的元素,则返回-1
同样的,也可以传入第二个参数,用来绑定回调函数里的this,而这里仍然需要注意回调函数应该是普通函数的时候才能绑定this
由于indexOf()执行的是全等比较,而NaN不等于任何值包括NaN本身,所以有:

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

这时候,就可以使用find()或者findIndex()了:

[NaN].findIndex(x => Object.is(NaN, x));


六、填充方法fill()

可以使用fill()方法来填充一个数组,如:

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

此外,fill()方法还可以指定第二个参数a和第三个参数b来指定填充范围为[a, b)

['a', 'b', 'c', 'd'].fill(7, 1, 4); // ['a', 7, 7, 7]


七、遍历方法entries()keys()values()

ES6提供了三个新的方法:entries()keys()values()可以用来遍历数组,这三个方法返回的都是Iterator对象,所以既可以用for-of遍历,也可以用...展开运算符遍历,区别在于:keys()是对键名进行遍历,而values()是对值进行遍历,entries()则是键值对:

const arr = ['a', 'b', 'c'];
for (let key of arr.keys()) {
    console.log(key);
}
// 输出:0 1 2
[...['a', 'b', 'c'].keys()]; // [0, 1, 2]

for (let [key, value] of arr.entries()) {
    console.log(`arr[${key}]=${value}`);
}
// 输出:arr[0]='a' arr[1]='b' arr[2]='c'

因为返回的是Iterator对象,所以也可以用next()方法来手动遍历:

const arr = ['a', 'b', 'c'];
const it = arr.keys();
it.next(); // {value: 0, done: false}
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}


八、includes()方法

includes()方法可以用来判断一个数组内是否包含有某个值,返回值为true或者false,如:

['a', 'b', 'c'].includes('a'); // true
['a', 'b', 'c'].includes('d'); // false

而查找NaN,也是没问题的:

['a', 'b', 'c', NaN].includes(NaN); // true


九、数组的空位

数组的空位指的是数组的某一个位置没有任何的值,如Array()构造函数返回的数组都是空位:

Array(3);
/*
(3) [undefiend × 3]
 - length: 3
 > __proto__
*/

ES5中对空位的处理,是很不一致的,如:

  • forEach()filter()every()some()会跳过空位
  • map() 会跳过空位,但是会保留这个值,如:
const arr = [1, 2, 3, , 5];
const arr2 = arr.map(x => x+1); // [2, 3, 4, 空位, 6]
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串

ES6中,会明确地将数组的空位,转化为undefined,即这个方法不会忽略空位,如:

Array.from([1, , 2]); [1, undefined, 2];

其他的ES6数组扩展语法,所做的处理如下:
1)...运算符会将空位转为undefined
2)copyWithin()会连空位一起拷贝
3)fill()会将空位视为正常的数组位置
4)for-of循环会遍历空位
5)entries()keys()values()find()findIndex()会将空位处理成undefined