1.扩展运算符


含义

扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

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

该运算符主要用于函数调用。

  1. function push(array,..items){
  2. array.push(...items);
  3. }
  4. function add(x,y){
  5. return x+y;
  6. }
  7. const number = [4,38];
  8. add(...number) //42

注意,只有函数调用时,扩展运算符才可以放在圆括号里,否则会报错。


替代函数的apply方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

  1. //ES5的写法
  2. function f(x,y,z){
  3. //...
  4. }
  5. var args = [0,1,2];
  6. f.apply(null,args);
  7. //ES6的写法
  8. function f(x,y,z){
  9. //...
  10. }
  11. let args = [0,1,2];
  12. f(...args);

扩展运算符的应用

  1. 复制数组

数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
扩展运算符提供了复制数组的简便写法。

  1. const a1 = [1,2];
  2. //写法一
  3. const a2 = [...a1];
  4. //写法二
  5. const [...a2] = a1;
  1. 合并数组

扩展运算符提供了数组合并的新写法。

  1. const arr1 = ['a','b'];
  2. const arr2 = ['c'];
  3. const arr3 = ['d','e'];
  4. //ES5的合并数组
  5. arr1.concat(arr2,arr3);
  6. //ES6
  7. [...arr1,...arr2,...arr3]

不过这两种方法都是浅拷贝,使用的时候需要注意。

  1. 与解构赋值结合

扩展运算符可以与解构赋值结合起来,用于生成数组。

  1. //ES5
  2. a = list[0],rest=list.slice(1)
  3. //ES6
  4. [a,...rest] = list
  5. const [first,...rest] = [1,2,3,4,5];
  6. first //1
  7. rest //2,3,4,5
  8. const [first,...rest] = [];
  9. first //undefined
  10. rest //[]
  11. const [first,...rest] = ["foo"];
  12. first //"foo"
  13. rest //[]
  1. 字符串

扩展运算符还可以将字符串转为真正的数组。

  1. [...'hello']
  2. //["h","e","l","l","o"]
  1. 实现了Iterator接口的对象

任何定义了遍历器接口的对象,都可以用扩展运算符转为真正的数组。

  1. let nodeList = document.querySelectorAll('div');
  2. let array = [...nodeList];
  1. Mao和Set结构,Generator函数

扩展运算符内部调用的是数据结构的Iterator接口,因此只要有Iterator接口的对象,都可以使用扩展预算符,比如Map结构。


2.Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象。

  1. let arrayLike = {
  2. '0':'a',
  3. '1':'b',
  4. '2':'c',
  5. length:3
  6. };
  7. //ES5的写法
  8. var arr1 = [].slice.call(arrayLike); //['a','b','c']
  9. //ES6的写法
  10. let arr2 = Array.from(arrayLike); //['a','b','c']

3.Array.of()

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

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

这个方法的主要目的,是弥补数组构造函数Array()的不足。因为函数参数个数的不同,会导致Array()的行为有差异。只有当参数个数不小于2个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。


4.数组实例的copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。

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

参数:

  • target:从该位置开始替换数据。如果为负值,表示倒数。
  • start:从该位置开始读取数据,默认为0。如果为负值,表示从末尾开始计算。
  • end:可到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。

5.数组实例的find()和findIndex()

数组实例的find方法,用于找出第一个符合条件的数组数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

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

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

  1. function f(v){
  2. return v > this.age;
  3. }
  4. let person = {name: 'John', age: 20};
  5. [10, 12, 26, 15].find(f, person); // 26

上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

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

上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。

6.数组实例的 fill()

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

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

上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

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

上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

  1. let arr = new Array(3).fill({name: "Mike"});
  2. arr[0].name = "Ben";
  3. arr
  4. // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
  5. let arr = new Array(3).fill([]);
  6. arr[0].push(5);
  7. arr
  8. // [[5], [5], [5]]

7.数组实例的 entries(),keys() 和 values()

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

  1. for (let index of ['a', 'b'].keys()) {
  2. console.log(index);
  3. }
  4. // 0
  5. // 1
  6. for (let elem of ['a', 'b'].values()) {
  7. console.log(elem);
  8. }
  9. // 'a'
  10. // 'b'
  11. for (let [index, elem] of ['a', 'b'].entries()) {
  12. console.log(index, elem);
  13. }
  14. // 0 "a"
  15. // 1 "b"

如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

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

8.数组实例的 includes()

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

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

该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

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

没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。

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

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

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

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

  1. [NaN].includes(NaN)
  2. // true

下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。

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

另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分。

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

    9.数组实例的 flat(),flatMap()

    数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

    1. [1, 2, [3, 4]].flat()
    2. // [1, 2, 3, 4]

    上面代码中,原数组的成员里面有一个数组,flat()方法将子数组的成员取出来,添加在原来的位置。
    flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

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

    上面代码中,flat()的参数为2,表示要“拉平”两层的嵌套数组。
    如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

    1. [1, [2, [3]]].flat(Infinity)
    2. // [1, 2, 3]

    如果原数组有空位,flat()方法会跳过空位。

    1. [1, 2, , 4, 5].flat()
    2. // [1, 2, 4, 5]

    flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

    1. // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
    2. [2, 3, 4].flatMap((x) => [x, x * 2])
    3. // [2, 4, 3, 6, 4, 8]

    flatMap()只能展开一层数组。

    1. // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
    2. [1, 2, 3, 4].flatMap(x => [[x * 2]])
    3. // [[2], [4], [6], [8]]

    上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。
    flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。

    1. arr.flatMap(function callback(currentValue[, index[, array]]) {
    2. // ...
    3. }[, thisArg])

    flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this

    10.数组的空位

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

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

    上面代码中,Array(3)返回一个具有 3 个空位的数组。
    注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

    1. 0 in [undefined, undefined, undefined] // true
    2. 0 in [, , ,] // false

    上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
    ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach(), filter(), reduce(), every()some()都会跳过空位。

  • map()会跳过空位,但会保留这个值
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。

    1. // forEach方法
    2. [,'a'].forEach((x,i) => console.log(i)); // 1
    3. // filter方法
    4. ['a',,'b'].filter(x => true) // ['a','b']
    5. // every方法
    6. [,'a'].every(x => x==='a') // true
    7. // reduce方法
    8. [1,,2].reduce((x,y) => x+y) // 3
    9. // some方法
    10. [,'a'].some(x => x !== 'a') // false
    11. // map方法
    12. [,'a'].map(x => 1) // [,1]
    13. // join方法
    14. [,'a',undefined,null].join('#') // "#a##"
    15. // toString方法
    16. [,'a',undefined,null].toString() // ",a,,"

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

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

    扩展运算符(...)也会将空位转为undefined

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

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

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

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

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

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

    1. let arr = [, ,];
    2. for (let i of arr) {
    3. console.log(1);
    4. }
    5. // 1
    6. // 1

    上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。
    entries()keys()values()find()findIndex()会将空位处理成undefined

    1. // entries()
    2. [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
    3. // keys()
    4. [...[,'a'].keys()] // [0,1]
    5. // values()
    6. [...[,'a'].values()] // [undefined,"a"]
    7. // find()
    8. [,'a'].find(x => true) // undefined
    9. // findIndex()
    10. [,'a'].findIndex(x => true) // 0

    由于空位的处理规则非常不统一,所以建议避免出现空位。成员。它的参数是一个回调函数,所有数组成员一次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有满足条件的成员,则返回undefined。
    数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

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

    这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

    1. function f(v){
    2. return v > this.age;
    3. }
    4. let person = {name: 'John', age: 20};
    5. [10, 12, 26, 15].find(f, person); // 26

    上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
    另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

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

    上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。

    11.数组实例的 fill()

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

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

    上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
    fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

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

    上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。
    注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

    1. let arr = new Array(3).fill({name: "Mike"});
    2. arr[0].name = "Ben";
    3. arr
    4. // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
    5. let arr = new Array(3).fill([]);
    6. arr[0].push(5);
    7. arr
    8. // [[5], [5], [5]]

    12.数组实例的 entries(),keys() 和 values()

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

    1. for (let index of ['a', 'b'].keys()) {
    2. console.log(index);
    3. }
    4. // 0
    5. // 1
    6. for (let elem of ['a', 'b'].values()) {
    7. console.log(elem);
    8. }
    9. // 'a'
    10. // 'b'
    11. for (let [index, elem] of ['a', 'b'].entries()) {
    12. console.log(index, elem);
    13. }
    14. // 0 "a"
    15. // 1 "b"

    如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

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

    13.数组实例的 includes()

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

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

    该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

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

    没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。

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

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

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

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

    1. [NaN].includes(NaN)
    2. // true

    下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。

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

    另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分。

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

  • Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

    14.数组实例的 flat(),flatMap()

    数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

    1. [1, 2, [3, 4]].flat()
    2. // [1, 2, 3, 4]

    上面代码中,原数组的成员里面有一个数组,flat()方法将子数组的成员取出来,添加在原来的位置。
    flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

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

    上面代码中,flat()的参数为2,表示要“拉平”两层的嵌套数组。
    如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

    1. [1, [2, [3]]].flat(Infinity)
    2. // [1, 2, 3]

    如果原数组有空位,flat()方法会跳过空位。

    1. [1, 2, , 4, 5].flat()
    2. // [1, 2, 4, 5]

    flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

    1. // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
    2. [2, 3, 4].flatMap((x) => [x, x * 2])
    3. // [2, 4, 3, 6, 4, 8]

    flatMap()只能展开一层数组。

    1. // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
    2. [1, 2, 3, 4].flatMap(x => [[x * 2]])
    3. // [[2], [4], [6], [8]]

    上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。
    flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。

    1. arr.flatMap(function callback(currentValue[, index[, array]]) {
    2. // ...
    3. }[, thisArg])

    flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this

    15.数组的空位

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

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

    上面代码中,Array(3)返回一个具有 3 个空位的数组。
    注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

    1. 0 in [undefined, undefined, undefined] // true
    2. 0 in [, , ,] // false

    上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
    ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach(), filter(), reduce(), every()some()都会跳过空位。

  • map()会跳过空位,但会保留这个值
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。

    1. // forEach方法
    2. [,'a'].forEach((x,i) => console.log(i)); // 1
    3. // filter方法
    4. ['a',,'b'].filter(x => true) // ['a','b']
    5. // every方法
    6. [,'a'].every(x => x==='a') // true
    7. // reduce方法
    8. [1,,2].reduce((x,y) => x+y) // 3
    9. // some方法
    10. [,'a'].some(x => x !== 'a') // false
    11. // map方法
    12. [,'a'].map(x => 1) // [,1]
    13. // join方法
    14. [,'a',undefined,null].join('#') // "#a##"
    15. // toString方法
    16. [,'a',undefined,null].toString() // ",a,,"

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

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

    扩展运算符(...)也会将空位转为undefined

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

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

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

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

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

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

    1. let arr = [, ,];
    2. for (let i of arr) {
    3. console.log(1);
    4. }
    5. // 1
    6. // 1

    上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。
    entries()keys()values()find()findIndex()会将空位处理成undefined

    1. // entries()
    2. [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
    3. // keys()
    4. [...[,'a'].keys()] // [0,1]
    5. // values()
    6. [...[,'a'].values()] // [undefined,"a"]
    7. // find()
    8. [,'a'].find(x => true) // undefined
    9. // findIndex()
    10. [,'a'].findIndex(x => true) // 0

    由于空位的处理规则非常不统一,所以建议避免出现空位。数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

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

    这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

    1. function f(v){
    2. return v > this.age;
    3. }
    4. let person = {name: 'John', age: 20};
    5. [10, 12, 26, 15].find(f, person); // 26

    上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
    另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。

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

    上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。

    16.数组实例的 fill()

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

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

    上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
    fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

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

    上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。
    注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

    1. let arr = new Array(3).fill({name: "Mike"});
    2. arr[0].name = "Ben";
    3. arr
    4. // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
    5. let arr = new Array(3).fill([]);
    6. arr[0].push(5);
    7. arr
    8. // [[5], [5], [5]]

    17.数组实例的 entries(),keys() 和 values()

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

    1. for (let index of ['a', 'b'].keys()) {
    2. console.log(index);
    3. }
    4. // 0
    5. // 1
    6. for (let elem of ['a', 'b'].values()) {
    7. console.log(elem);
    8. }
    9. // 'a'
    10. // 'b'
    11. for (let [index, elem] of ['a', 'b'].entries()) {
    12. console.log(index, elem);
    13. }
    14. // 0 "a"
    15. // 1 "b"

    如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。

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

    18.数组实例的 includes()

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

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

    该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

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

    没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。

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

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

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

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

    1. [NaN].includes(NaN)
    2. // true

    下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。

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

    另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分。

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

  • Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

    19.数组实例的 flat(),flatMap()

    数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

    1. [1, 2, [3, 4]].flat()
    2. // [1, 2, 3, 4]

    上面代码中,原数组的成员里面有一个数组,flat()方法将子数组的成员取出来,添加在原来的位置。
    flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

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

    上面代码中,flat()的参数为2,表示要“拉平”两层的嵌套数组。
    如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

    1. [1, [2, [3]]].flat(Infinity)
    2. // [1, 2, 3]

    如果原数组有空位,flat()方法会跳过空位。

    1. [1, 2, , 4, 5].flat()
    2. // [1, 2, 4, 5]

    flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

    1. // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
    2. [2, 3, 4].flatMap((x) => [x, x * 2])
    3. // [2, 4, 3, 6, 4, 8]

    flatMap()只能展开一层数组。

    1. // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
    2. [1, 2, 3, 4].flatMap(x => [[x * 2]])
    3. // [[2], [4], [6], [8]]

    上面代码中,遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组。
    flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。

    1. arr.flatMap(function callback(currentValue[, index[, array]]) {
    2. // ...
    3. }[, thisArg])

    flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this

    20.数组的空位

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

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

    上面代码中,Array(3)返回一个具有 3 个空位的数组。
    注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

    1. 0 in [undefined, undefined, undefined] // true
    2. 0 in [, , ,] // false

    上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
    ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach(), filter(), reduce(), every()some()都会跳过空位。

  • map()会跳过空位,但会保留这个值
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。
    1. // forEach方法
    2. [,'a'].forEach((x,i) => console.log(i)); // 1
    3. // filter方法
    4. ['a',,'b'].filter(x => true) // ['a','b']
    5. // every方法
    6. [,'a'].every(x => x==='a') // true
    7. // reduce方法
    8. [1,,2].reduce((x,y) => x+y) // 3
    9. // some方法
    10. [,'a'].some(x => x !== 'a') // false
    11. // map方法
    12. [,'a'].map(x => 1) // [,1]
    13. // join方法
    14. [,'a',undefined,null].join('#') // "#a##"
    15. // toString方法
    16. [,'a',undefined,null].toString() // ",a,,"
    ES6 则是明确将空位转为undefined
    Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
    1. Array.from(['a',,'b'])
    2. // [ "a", undefined, "b" ]
    扩展运算符(...)也会将空位转为undefined
    1. [...['a',,'b']]
    2. // [ "a", undefined, "b" ]
    copyWithin()会连空位一起拷贝。
    1. [,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
    fill()会将空位视为正常的数组位置。
    1. new Array(3).fill('a') // ["a","a","a"]
    for...of循环也会遍历空位。
    1. let arr = [, ,];
    2. for (let i of arr) {
    3. console.log(1);
    4. }
    5. // 1
    6. // 1
    上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。
    entries()keys()values()find()findIndex()会将空位处理成undefined
    1. // entries()
    2. [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
    3. // keys()
    4. [...[,'a'].keys()] // [0,1]
    5. // values()
    6. [...[,'a'].values()] // [undefined,"a"]
    7. // find()
    8. [,'a'].find(x => true) // undefined
    9. // findIndex()
    10. [,'a'].findIndex(x => true) // 0
    由于空位的处理规则非常不统一,所以建议避免出现空位。