ES6 数组

简单谈谈

ES5 中原本就提供了数组,这里谈谈数组下都有哪些方法新增,哪些方法进行了变更。

Array.from

javascript 中有这样一类的存在:类数组,它不是数组,却能像数组那么操作,如下便是一个类数组结构。

let arrayLike = {
    "0": "a",
    "1": "b",
    "2": "c",
    length: 3
};

类数组结构:可以像数组那样取值、 for 循环,但是却不能适用数组 forEach/splice 等数组特定的方法,在 ES5 中必须使用一些特殊的方法来将一个类数组结构转化成数组,而在 ES6 中, Array 对象下提供了 form 方法,该方法用于将一个类数组结构转化成一个真正的数组。

// ES5 的写法
var arr1 = [].slice.call(arrayLike);    // ["a", "b", "c"]

// ES6 的写法
let arr2 = Array.from(arrayLike);       // ["a", "b", "c"]

一些类数组

  • 通过 DOM 操作返回的 NodeList 集合。
let ps = document.querySelectorAll("p");

// 只有将 ps 转化成一个真正的数组才能使用 forEach 方法
Array.from(ps).forEach(function (p) {
    console.log(p);
});
  • 函数内部的 arguments 对象。
// arguments 对象
function foo() {
    var args = Array.from(arguments);
    console.log(args);
}
  • 字符串。
var sArr = Array.from("hello");
console.log(sArr);
// ["h", "e", "l", "l", "o"]
  • Set 集合。
let namesSet = new Set(["a", "b"]);
Array.from(namesSet);
// ["a", "b"]

注: 只要是部署了 Iterator 接口的结构,都可以认为是类数组结构。

当然对一个数组使用 Array.from 是没有任何问题,只会将原来的数组结构复制一遍,并返回一个新的数组。(可以用这个来复制数组)

同时 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]

简单实用

  • 实例1:取一组 DOM 节点的文本内容。
let spans = document.querySelectorAll("span.name");
let names2 = Array.from(spans, s => s.textContent);
  • 实例2:将数组中布尔值为 false 的成员转为 0
Array.from([1, , 2, , 3], (n) => n || 0);
// [1, 0, 2, 0, 3]
  • 实例3:返回各种数据的类型
function typesOf () {
  return Array.from(arguments, value => typeof value);
}
typesOf(null, [], NaN);
// ["object", "object", "number"]
  • 实例4:只要有一个原始的数组结构就能构造出一个数组
Array.from({ length: 2 }, () => "jack");
// ["jack", "jack"]

Array.of

用于将一组值,转化为一组值。

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

这个方法的主要目的,是弥补数组构造函数 Array() 的不足。因为参数个数的不同,会导致 Array() 的行为有差异。

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

copyWithin

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

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

注: 这里的 = 使用了函数中默认值的写法。

参数说明target (必需)从该位置开始替换数据。start (可选)从该位置开始读取数据,默认为 0 。如果为负值,表示取 长度 + startend (可选)到该位置前停止读取数据,默认等于数组长度。如果为负值,表示取 长度 + start

[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}

find & findIndex

方法效果find找出第一个符合条件的数组成员findIndex找出第一个符合条件的数组成员的位置

参数都为一个函数,用于做判断

// 找出第一个值小于 0 的数
[1, 4, -5, 10].find((n) => n < 0)
// -5

// 找出第一个值大于 9 的数
[1, 5, 10, 15].find((value, index, arr) =>  value > 9);
// 10

// 找出第一个大于 9 的数的位置
[1, 5, 10, 15].findIndex((value, index, arr) => value > 9);
// 2

若没有值符合,find 返回 undefinedfindIndex 返回 -1

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

方法效果entries对键值对的遍历keys对键名的遍values对键值的遍历

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"

includes

表示某个数组是否包含给定的值,返回布尔值,与字符串的 includes 方法类似。

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

其他

这些常用,就不一一介绍了。

  • map:提供对应关系,返回新数组。
  • forEach:遍历数组。
  • filter:过滤数组,返回符合要求的数组。
  • reduce:遍历数组,并累积值,从索引小的内容开始。
  • reduceRight:遍历数组,并累计值,从索引大的内容开始。
  • every:判断数组中元素是否都符合要求。
  • some:判断数组中是否有元素符合要求。
  • sort:将数组进行排序。

数组中的空位问题

数组中的空位是一个让人头疼的问题,空位有可能被忽略,而不是当成 undefined 来处理,比如:

  • forEachfilterreducereduceRighteverysome 会跳过空位。
  • map 会跳过空位,但会保留这个值。

以下例子需要好好品品,品完过后放到浏览器上试试:

// 一个含有 4 个空位和一个元素的数组。
let emptyArr = [, , , , 1];

emptyArr.forEach(i => console.log("test"));
// 输出一次 test,空位被跳过了。

emptyArr.filter(i => {
    console.log("test");
    return true;            // 将所有内容都返回
});
// 输出一次 test 空位被跳过,即使需要所有内容。
// 返回:[1] 

emptyArr.reduce((acc, i) => {
    console.log("test");
    acc += 1;
    return acc;
}, 0)
// 输出一次 test 空位被跳过。
// 返回:1 

emptyArr.map(i => true);
// 返回:[, , , , true]

emptyArr.join();
// 返回:",,,,1"

emptyArr.toString();
// 返回:",,,,1"

let emptyArr2 = [, , , ,];

emptyArr2.every(i => {
    console.log("test");
    return true;
})
// 无输出,空位被跳过。
// 返回:true

emptyArr2.some(i => {
    console.log("test");
    return true;
})
// 无输出,空位被跳过。
// 返回:false

但并非所有的空位都会被跳过,如下所示:

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

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

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

空格不会被跳过的方法:

  • 处理成 undefined 的方法:entrieskeysvaluesfindfindIndex、扩展运算符(...
  • 保留空位:copyWithinfor...of

数组空位会导致各种奇奇怪怪的问题,解决方法只有一个:不创建带有空位的数组。