一, 扩展运算符
1. 含义
扩展运算符是三个点(...), 好比 rest 参数的逆运算, 将一个数组转为用逗号分隔的参数序列.
console.log(...[1, 2, 3]);// 1 2 3
该运算符主要用于函数调用. 可以与正常函数参数结合使用.
- function f(v, w, x, y, z){
- console.log(v+w+x+y+z);
- }
- const args = [0, 1];
- f(-1, ...args, 2, ...[3]);//5
扩展运算符后面还可以放置表达式.
- var x = 2;
- const arr = [
- ...(x> 0 ? ['a'] : []),
- 'b'
- ];
- arr;// ['a', 'b']
如果扩展运算符后面是一个空数组, 则不产生任何效果.
2. 扩展运算符的应用
(1)代替函数的 apply 方法 由于扩展运算符可以展开数组, 所有不再需要 apply 方法, 将数组转为函数的参数了. 例如: 获取数组中最大的元素
- //ES5 写法
- Math.max.apply(null, [14, 3, 77]);//77
- //ES6 写法
- Math.max(...[14, 3, 77]);// 77
- // 等同于
- Math.max(14, 3, 77);// 77
上例中, ES5 写法的写法, 是因为 JavaScript 不提供求数组最大元素的函数, 所以只能套用 Math.max 函数, 将数组转为一个参数序列, 然后求最大值. ES6 的写法用扩展运算符, 则可以直接使用 Math.max 方法. (2)复制数组 数组是复合的数据类型, 直接复制的话, 只是复制了指向底层数据结构的指针, 而不是克隆一个全新的数组. 扩展运算符提供了复制数组的简便写法:
- //ES5 写法
- const a1 = [1, 2];
- const a2 = a1.concat();
- a2 ;// [1, 2]
- a2[0] = 2;
- a2; // [2, 2]
- a1;// [1, 2]
- //ES6 写法
- const a1 = [1, 2];
- // 写法 1:
- const a2 = [...a1];
- a2[0] = 2;
- a2; // [2, 2]
- a1;// [1, 2]
- // 写法 2
- const [...a2] = a1;
- a2[0] = 2;
- a2; // [2, 2]
- a1;// [1, 2]
(3)合并数组 扩展运算符提供了数组合并的新写法.
- var arr1 = ['a', 'b'], arr2 = ['c'], arr3 = ['d', 'e'];
- //ES5 写法
- var arr4 = arr1.concat(arr2, arr3);
- arr4;// ["a", "b", "c", "d", "e"]
- //ES6 写法
- var arr5 = [...arr1,...arr2,...arr3];
- arr5;// ["a", "b", "c", "d", "e"]
但是上面两种方法都是浅拷贝, 使用的时候需要注意. 例如:
- var a1 = [{foo: 1}];
- var a2 = [{bar: 2}];
- var a3 = a1.concat(a2);
- var a4 = [...a1, ...a2];
- a3[0] === a1[0] // true
- a4[0] === a1[0] // true
- a1[0].foo = 3;
- a3[0];//{foo: 3}
- a4[0];//{foo: 3}
上例中, a3 和 a4 是用两种不同方法合并而成的新数组, 但是它们的成员都是对原数组成员的引用, 这就是浅拷贝. 如果修改了原数组的成员, 会同步反映到新数组. (4)与解构赋值结合 扩展运算符可以与解构赋值结合起来, 用于生成数组.
- var [first, ...rest] = [1, 2, 3, 4, 5];
- first; // 1
- rest; // [2, 3, 4, 5]
如果将扩展运算符用于数组赋值, 只能放在参数的最后一位, 否则会报错.
- var [...butLast, last] = [1, 2, 3, 4, 5];
- //SyntaxError: Rest element must be last element
- var [first, ...middle, last] = [1, 2, 3, 4, 5]
- //SyntaxError: Rest element must be last element
(5)转换字符串为数组 扩展运算符还可以将字符串转为数组.
[...'hello'];// ["h", "e", "l", "l", "o"]
这种写法的好处是, 能够正确识别四个字节的 Unicode 字符.
- 'x\uD83D\uDE80y';// "xy"
- [...'x\uD83D\uDE80y'].length; // 3
(6)转化实现了 Iterator 接口的对象 任何 Iterator 接口的对象. 都可以用扩展运算符转化为真正的数组.
- let nodeList = document.querySelectorAll('div');
- let arr = [...nodeList];
- arr;// [div, div, div]
上例中, nodeList 不是一个数组, 而是一个类数组的对象, 使用扩展运算符可以将其转为一个真正的数组. 原因在于, nodeList 对象实现了 Iterator. 对于那些没有部署 Iterator 接口的类数组对象, 扩展运算符无法将其转为真正的数组. 例如:
- let arrlike = {
- '0': 'a',
- '1': 'b',
- '2': 'c',
- '3': 'd',
- length: 3
- };
- let arr = [...arrlike];// TypeError: arrlike is not iterable
(7)Map 和 Set 结构, Generator 函数 扩展运算符内部调用的是数据结构的 Iterator 接口, 因此只要具有 Iterator 接口的对象都可以使用扩展运算符, 比如 Map 结构.
- let map = new Map([
- [1, 'one'],
- [2, 'two'],
- [3, 'three'],
- ]);
- let arr = [...map.keys()];
- arr; // [1, 2, 3]
Generator 函数运行后, 返回一个遍历器, 因此可以使用扩展运算符.
- const go = function*(){
- yield 1;
- yield 2;
- yield 3;
- };
- [...go()] // [1, 2, 3]
上例中, go 函数是一个 Generator 函数, 执行后返回的是一个遍历器对象, 对这个遍历器对象执行扩展运算符, 就会将内部遍历得到的值转为一个数组.
二, Array.from()
Array.form 方法用于将两类对象转为真正的数组: 类数组对象和可遍历的对象(包括 ES6 的新增数据结构 Set 和 Map ). 注: 类数组对象, 必须有 length 属性.
1. 转化类数组对象
例如:
- var arrlike = {
- '0': 'a',
- '1': 'b',
- '2': 'c',
- '3': 'd',
- length: 4
- };
- //ES5 写法
- var arr1 = [].slice.call(arrlike);
- arr1;// ["a", "b", "c", "d"]
- //ES6 写法
- var arr2 = Array.from(arrlike);
- arr2;// ["a", "b", "c", "d"]
实际应用中, 常见的类数组对象是操作 DOM 返回的 NodeList 集合, 以及函数内部的 arguments 对象. Array.form 都可以将其转为对象.
- //nodeList 对象
- var ps = document.querySelectorAll('p');
- Array.from(ps).filter( p => {
- return p.textContent.length;
- });
- //arguments 对象
- function foo() {
- var args = Array.from(arguments);
- console.log(args);
- }
- foo(1,2,3,4,5);// [1, 2, 3, 4, 5]
2. 转化 Iterator 接口的数据结构
只要是部署了 Iterator 接口的数据结构, Array.from 都能将其转为数组.
- Array.from('hello');// ["h", "e", "l", "l", "o"]
- let set = new Set(['a', 'b']);
- Array.from(set);// ["a", "b"]
上例中, 字符串和 Set 结构都具有 Iterator 接口. Array.from 可以正确处理各种 Unicode 字符.
Array.from('x\uD83D\uDE80y');// ["x", "", "y"]
如果参数是一个真正的数组, Array.from 会返回一个一模一样的新数组.
Array.from([1, 2]);// [1, 2]
3. 第二个参数
Array.from 还可以接受第二个参数, 作用类似于数组的 map 方法, 用于对每个元素进行处理, 将处理后的值放入返回的数组.
- Array.from('hello',(item) => item + 1);
- // ["h1", "e1", "l1", "l1", "o1"]
三, Array.of()
Array.of 方法用于将一组值, 转为数组.
- Array.of(3, 11, 8);// [3, 11, 8]
- Array.of(3);// [3]
- Array.of(3).length;// 1
这个方法主要弥补数组构造函数 Array() 的不足. 因为参数的个数不同, 会导致 Array() 的行为有差异.
- Array();// []
- Array(3);//[, , ,]
- Array.of();//[]
- Array.of(3).length;// 1
Array.of 基本上可以用来代替 Array() 或 new Array(), 并且不存在由于参数的个数而导致的重载.
四, 数组实例的 copyWithin()
数组实例的 copyWithin 方法, 在当前数组内部, 将指定位置的成员复制到其他位置(会覆盖原有成员), 然后返回当前数组.
Array.prototype.copyWithin(target, start = 0, end = this.length);
这个方法接受三个参数: --a. target(必需): 从该位置开始替换数据. 如果为负值, 表示倒数. --b. start(可选): 从该位置开始读取数据, 默认为 0. 如果为负值, 表示倒数. --c. end(可选): 到该位置前停止读取数据, 默认等于数组长度. 如果为负值, 表示倒数. 这三个参数都应该是数值, 如果不是, 会自动转为数值.
- [1, 2, 3, 4, 5].copyWithin(0, 3);// [4, 5, 3, 4, 5]
- // 从 3 号开始读取数据到到末尾得到 4 和 5, 将 4 和 5 复制到从 0 开始的位置, 覆盖原有成员 1 和 2
- [1, 2, 3, 4, 5].copyWithin(0, 3, 4);// [4, 2, 3, 4, 5]
- // 复制 3 号位置到 0 号位置
- [1, 2, 3, 4, 5].copyWithin(0, -2, -1);// [4, 2, 3, 4, 5]
- //-2 倒数第二位相当于 3 号位,-1 倒数第一位相当于 4 号位
注: copyWithin() 方法会改变原数组,
五, 数组实例的 find() 和 findIndex()
1.find()
数组实例的 find() 方法, 用于找出第一个符合条件的数组成员. 它的参数是一个回调函数, 所有数组成员依次执行该回调函数, 直到找出第一个返回值为 true 的成员, 然后返回该成员. 如果没有符合条件的成员, 则返回 undefined .
[1, 4, -5, 10].find( (n) => n <0 );// -5
find 方法的回调函数可以接受三个参数, 依次为: 当前的值, 当前的位置和元素组.
- [1, 5, 10, 15].find( (value, index, arr) => {
- if(value> 9){
- console.log('index:'+index);
- return;
- }
- });
- // index: 2
- // index: 3
- 2.findIndex()
数组实例的 findIndex 方法的用法与 find 方法非常类似, 返回第一个符合条件的数组成员的位置, 如果所有成员都不符合条件, 则返回 -1 .
- [1, 5, 10, 15].findIndex( (value, index, arr) => {
- return value> 9
- } );
- // 2
- [1, 5, 10, 15].findIndex( (value, index, arr) => {
- return value <0
- } );
- // -1
3. 第二个参数
find() 和 findIndex() 这两个方法都接受第二个参数, 用来绑定回调函数的 this 对象.
- function f(value, index, arr){
- return value> this.age;
- }
- var person = {name: 'John', age: 20};
- [10, 12, 26, 15].find(f, person);
- // 26
上例中, find 回调函数的 this 对象指向 person.
4. 发现 NaN
这两个方法都可以发现 NaN, 弥补了数组的 indexOf 方法的不足.
- [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"]
注: 如果填充的类型是对象, 那么被赋值的是同一个内存地址的对象, 而不是深拷贝对象.
- var arr = new Array(3).fill({name: 'Mike'});
- arr[0].name = 'Ben';
- arr;// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
- var arr = new Array(3).fill([]);
- arr[0].push(5);
- arr;// [[5], [5], [5]]
七, 数组实例的 entries(),keys(), values()
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 循环, 可以手动调用遍历器对象的 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']
八, 数组实例的 includes()
1. 用法
Array.prototype.includes 方法返回一个布尔值, 表示某个数组是否包含给定的值, 与字符串的 includes 方法类似.
- [1, 2, 3].includes(2);// true
- [1, 2, 3].includes(4);// false
- [1, 2, NaN].includes(NaN);// true
该方法接受第二个参数, 表示开始搜索的起始位置, 默认值为 0. 如果第二个参数为负数, 则表示倒数的位置. 如果第二个参数大于数组长度, 则会重置为从 0 开始.
- [1, 2, 3].includes(3,3);// false
- [1, 2, 3].includes(3,-1);// true
没有这个方法之前, 通常使用数组的 indexOf 方法, 检查是否包含某个值. indexOf 方法有两个确定: 一是不够语义化, 它的含义是找到参数值的第一个出现的位置, 所有要去判断是否不等于 - 1, 表达不直观. 二是, 它的内部使用严格相等运算符 (===) 进行判断, 这导致对 NaN 的误判.
[NaN].indexOf(NaN);// -1
includes 使用的是不一样的算法, 没有这个问题.
2. 注意问题
Set 和 Map 数据结构有一个 has 方法, 需要注意与 includes 区分: --a. Map 结构的 has 方法, 是用来查找键名的, 比如 Map.prototype.has(key) --b. Set 结构的 has 方法, 是用来查找值的, 比如 Set.prototype.has(value)
九, 数组实例的 flat(),flatMap()
在 chrome 控制台测试, 这两个方法还没有实现.
1.flat()
数组的成员有时还是数组, Array.prototype.flat() 用于将嵌套的数组 "拉平", 变成一维的数组. 该方法返回一个新数组, 对原数组没有影响.
flat() , 默认只会 "拉平" 一层, 如果想要 "拉平" 多层的嵌套数组, 可以将 flat()方法的参数写成一个整数, 表示想要拉平的层数, 默认为 1.
如果不管有多少层嵌套, 都要转成一维数组, 可以用 Infinity 关键字作为参数.
如果原数组有空位, flat 方法会跳过空位.
- [1, 2, [3, [4, 5]]].flat()
- // [1, 2, 3, [4, 5]]
- [1, 2, [3, [4, 5]]].flat(2)
- // [1, 2, 3, 4, 5]
- [1, [2, [3]]].flat(Infinity)
- // [1, 2, 3]
- [1, 2, , 4, 5].flat()
- // [1, 2, 4, 5]
- 2.flatMap()
flatMap() 方法对原数组的每一个成员执行一个函数(相当于 Array.prototype.map()), 然后对返回值组成执行 flat 方法. 该方法返回一个新数组, 不改变原数组.
- // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
- [2, 3, 4].flatMap((x) => [x, x * 2])
- // [2, 4, 3, 6, 4, 8]
flatMap() 方法只能展开一层数组.
- // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
- [1, 2, 3, 4].flatMap(x => [[x * 2]])
- // [[2], [4], [6], [8]]
flatMap() 方法的参数是一个遍历函数, 该函数可以接受三个参数, 分别是当前数组成员, 当前数组成员的位置(从 0 开始), 原数组.
- arr.flatMap(function callback(currentValue[, index[, array]]) {
- // ...
- }[, thisArg])
flatMap() 方法还可以有第二个参数, 用来绑定遍历函数里面的 this.
十, 数组的空位
数组的空位是指: 数组的某一个位置没有任何值. 比如, Array 构造函数返回的数组都是空位.
Array(3);// [, , ,]
注: 空位不是 undefined, 一个位置的值等于 undefined, 依然是有值的. 空位是没有任何值. in 运算符可以说明这一点.
- 0 in [undefined, undefined, undefined]; // true
- 0 in [, , ,]; // false
上例说明, 第一个数组的 0 号位是有值的, 第二个数组的 0 号位是没有值的. ES5 对空位的处理很不一致: --a. forEach(),filter(),reduce(),every(), 和 some() 都会跳过空位. --b. map() 会跳过空位, 但会保留这个值. --c. join() 和 toString() 会将空位视为 undefined, 而 undefined 和 null 会被处理成空字符串. ES6 则是明确将空位处理为 undefined. --a. Array.from() 方法会将数组的空位, 转为 undefined, 即不会忽略这个空位. --b. 扩展运算符(...), 也会将空位转为 undefined. --c. copyWthin() 会将空位一起拷贝. --d. fill() 会将空位视为正常的数组位置. --e. for...of 循环也会遍历空位. --f. entries(),keys(),values(),find(),findIndex() 会将空位处理成 undefined. 由于空位的处理不统一, 建议避免出现空位.
来源: http://www.qdfuns.com/article/46690/6bf93a14d04b936b483f48cf43d314d4.html