ES6扩展 数组扩展

数组新增的方法们,结合扩展运算符会有很多便捷处理方案

结合扩展运算符(...)

复习:数组合并、复制数组

  • 在解构赋值中讲解了使用扩展运算符将数组合并以及复制数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr1 = [1, 2, 3, 4];
const arr2 = [4, 3, 2, 1];
const arr3 = ["nihao", 2.2, false];

//数组合并
const cArr1 = [1, 2, 3, ...arr1];
console.log(cArr1);//[1, 2, 3, 1, 2, 3, 4]
const cArr2 = [...arr1, ...arr2, ...arr3];
console.log(cArr2);// [1, 2, 3, 4, 4, 3, 2, 1, "nihao", 2.2, false]

//复制数组arr3
const cArr3 = [...arr3];
console.log(cArr3);//["nihao", 2.2, false]
const [...cArr4] = arr3;
console.log(cArr4);//["nihao", 2.2, false]

复习:apply()

  • 语法func.apply(thisArg, [argsArray])
  • 可以通过 apply() 方法调用属于另一个对象的方法。
  • 但不仅限于此,当设置第一个参数为null时可以在某些本来需要写成遍历数组变量的任务中使用内建的函数,也就是说此时apply()的作用不是调用属于另一个对象的方法,而是使参数数组中的每一个元素都去执行func函数。【如:下方“读取并展开数组用作多个参数”的“实用例子”中】
参数 描述
thisArg 可选的。
在 func 函数运行时使用的 this 值。
请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时this会自动替换为指向全局对象,原始值会被包装。
argsArray 可选的。
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。
如果该参数的值为 null 或 undefined,则表示不需要传入任何参数
从ECMAScript 5 开始可以使用类数组对象。
  • 例子:在“读取并展开数组用作多个参数”的“实用例子”中。

读取并展开数组用作多个参数

  • 另一个作用:在调用函数时把一个数组展开来作为这个函数的参数。
  • 简单例子
    1
    2
    3
    4
    5
    6
    function foo(a, b, c) {
    console.log(a);//1
    console.log(b);//3
    console.log(c);//2
    }
    foo(...[1, 3, 2]);
  • 实用例子
    在实际运用中,我们经常将用户信息按照一定的顺序放在数组中而不是对象,这可以节省流量。
    在这个例子中,我们可以使用扩展运算符简便的读取数组中的每一个元素并作用与对应的参数,避免了重复使用[]去读取各个数组元素)。
    补充:需要使用数组的join()将数组hobby转换为字符串。
    1
    2
    3
    4
    5
    6
    const user = ["小明", 15, ["吃饭", "睡觉", "打游戏"], "我没有女朋友"];
    function say(name, age, hobby, desc) {
    console.log(`我叫${name},我今年${age}岁,我平时喜欢${hobby.join("和")}${desc}。`);
    }
    say(user[0],user[1],user[2],user[3]);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
    say(...user);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
  • 还可以**使用apply()(不推荐)**:
    1
    2
    3
    4
    5
    6
    7
    8
    const user = ["小明", 15, ["吃饭", "睡觉", "打游戏"], "我没有女朋友"];
    function say(name, age, hobby, desc) {
    console.log(`我叫${name},我今年${age}岁,我平时喜欢${hobby.join("和")}${desc}。`);
    }
    say.apply(null,user);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
    say.apply("",user);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
    say.apply(0,user);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
    say.apply(1,user);//我叫小明,我今年15岁,我平时喜欢吃饭和睡觉和打游戏,我没有女朋友。
    他会将数组元素将作为单独的参数传给 say 函数】进行数组变对应参数的调用,但是使用扩展运算符语义更加清晰,而且参数如果传入过多会有超出JavaScript引擎的参数长度限制的风险。故不推荐使用apply()

补充:生成器函数

  • 语法function* name([param[, param[, ... param]]]) { statements }
  • 参数
    name:函数名
    param:要传递给函数的一个参数的名称,一个函数最多可以有255个参数。
    statements:普通JS语句。
  • 描述:生成器函数在执行时能暂停,后面又能从暂停处继续执行。
    调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 (iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。【也就是说,首次执行生成器函数需要调用它的next(),当然也可以使用扩展运算符进行迭代调用,例子在下方“扩展运算符与生成器函数”中
  • 通过yield关键字可暂停函数的执行
  • 通过next关键字可继续函数的执行
  • 与promise类似

扩展运算符与生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* g() {
console.log(1);
yield "hi~";
console.log(2);
yield "imooc~";
}
//使用扩展运算符相当于迭代(循环)执行生成器函数【在这也就是执行了两次函数g()】
// const arr = [...g()];//分别输出1,2
//使用扩展运算符一行代码的效果类似于下面的代码
const gg = g();
gg.next();//1
setTimeout(function () {
gg.next();//2
}, 1000);

结合去重函数Set()

  • Set()参数可放入数组,返回值是字符串类型,想要让他完成去重后返回数组则可使用扩展运算符
    1
    2
    3
    let set = new Set([1, 2, 3, 3]);
    console.log(set);//{1, 2, 3}
    console.log([...set]);//[1, 2, 3]

新的方法

复习:构造函数法注意事项与数组字面量法创建数组

  • 数组字面量可用于创建数组。(类似的,对象字面量用于创建对象)
  • 创建数组有两种方法:构造函数法、数组字面量法。

第一:构造函数法

1
var arr=new Array();

注意:使用Array()创建数组对象时,如果只有一个数字参数则默认是用于设置数组长度

1
2
3
4
5
let arr1 = new Array("10");
console.log(arr1);//["10"]

let arr2 = new Array(10);
console.log(arr2);// [empty × 10]

第二:数组字面量法

1
var arr=["zhu","zhen","wei"];

在调用数组的内建方法时可以使用Array.prototype.内建方法名,也直接使用数组字面量[],写作[].内建方法名即可少写Array.prototype。(具体例子在下方“Array.from()将类数组转换为数组”例子中)


Array.from()将类数组转换为数组

  • Array.from()将类数组对象或者Iterable对象转换为数组对象
  • 比如:arguments函数document.querySelectorAlldocument.getElementByTagNamedocument.getElementsByNamedocument.getElementsByClassName返回的都是类数组对象。
  • 注意
    类数组对象的属性名必须是数值型或者字符串型的数字
    类数组对象的length属性值会直接影响数组长度。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Array.from(): 遍历的可以是伪数组,如 String、Set结构,Node节点
    const obj = {
    0: 1,
    1: "22",
    2: false,
    length: 2,
    };
    console.log(Array.from(obj)); // [1, "22"]
    // Array.from()还可接收一个回调函数作为参数2对类数组对象进行一些处理
    // "22"隐式转换为22后也乘以2倍
    console.log(Array.from(obj, item => item * 2)); // [2, 44]
  • 补充其他3种可以将类数组转换为数组的方法,但无法使用回调函数对数组对象进行处理
    • 方法3:添加遍历器接口(Symbol.iterator)后使用扩展运算符,具体例子在下面“使用扩展运算符将类数组转换为数组”中
      1
      2
      3
      4
      5
      // 方法1:使用call()让对象obj调用Array的slice方法以此转换成数组对象
      console.log(Array.prototype.slice.call(obj)); // [1, "22"]

      // 方法2:直接使用数组字面量`[]`即可少写`Array.prototype`
      console.log([].slice.call(obj)); // [1, "22"]

补充:使用扩展运算符将类数组转换为数组

  • 扩展运算符...背后调用的是遍历器接口(Symbol.iterator) ,所以任何有interator接口的对象都可以用...转化为真正的数组。
  • MDN 关于遍历器接口(Symbol.iterator)
  • 类数组对象是有interator接口的,所以我们也可以使用扩展运算符将类数组转换为数组。
  • 注意:扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。所以如果这个类数组对象是我们自己写的就一定要记得为它部署 Iterator 接口,否则无法使用扩展运算符将它转换为数组。(如下所示)
    1
    2
    3
    4
    5
    6
    7
    const obj = {
    0: 1,
    1: "22",
    2: false,
    length: 2
    };
    console.log([...obj]);
  • 在这个例子中,我们没有部署Symbol.iterator接口,就无法转换为数组并报错:TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
  • 正确写法:添加遍历器接口(Symbol.iterator):
    1
    2
    3
    4
    5
    6
    7
    8
    const obj = {
    0: 1,
    1: "22",
    2: false,
    length: 2,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]//添加遍历器接口
    };
    console.log([...obj]);
    注意:
    不能在对象外面使用obj.obj[Symbol.iterator]= Array.prototype[Symbol.iterator];去添加遍历器接口。
    只有类数组对象才可以直接使用 Array 的 Iterator,普通对象部署数组的Symbol.iterator方法,并无效果。普通对象需要手动添加 Iterator 方法,添加 Iterator 接口的一种比较常用的方法,是使用Generator函数。普通的对象就算没有实现 Iterator 接口,不能使用 for … of 的情况下,仍然可以使用 for … in 来遍历 key。

Array.of()将参数合成数组

  • Array.of()将传入的参数合成数组,并且不考虑参数的数量或类型
  • 语法Array.of(参数1,参数2,参数3)
1
console.log(Array.of(1, 2, "123", 2.2, false));//[1, 2, "123", 2.2, false]

Array.fill()固定值替换数组的元素

  • 注意:fill不是静态方法,不能通过Array.fill来调用。
  • fill()方法用于将一个固定值替换数组的元素。并且可以指定填充的范围(起始值与结束值的数组下标)。
  • 语法array.fill(value, start, end)
  • 返回原数组
参数 描述
value 必需。填充的值。
start 可选。开始填充位置的数组下标。
end 可选。停止填充位置的数组下标 (默认为 array.length)
注意:不包括end,也就是说array.fill(1, 0, 3)替换的是数组[0]、[1]、[2]】
  • 注意:数组原有参数会被填充的值覆盖。
  • 例子:
    1
    2
    let fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.fill("lalala", 2, 4);
  • 输出结果:
    1
    Banana,Orange,lalala,lalala

Array.includes()判断是否含有某元素

  • 与字符串类似,数组也有includes()用于检测数组中是否包含某元素,是则返回true,否则返回false。
  • 可以代替以前的indexOf()来判断元素是否存在,但indexOf()可以找到数组下标,这个不可替代。
  • 对 NaN 也有效
    1
    2
    3
    4
    5
    6
    var arr = [1, 2, 3, 4];
    console.log(arr.includes(1));//true
    console.log(arr.includes(55));//false

    console.log([1, 2, NaN].includes(NaN)); // true
    console.log([1, 2, NaN, undefined].includes(undefined)); // true

keys()values()还有entries()

  • 与对象类似的,数组也有keys()values()还有entries(),不过它们是作用在实例上的,即arr.keys()arr.values()还有arr.entries()。而对象是作用在Object上Object.keys(obj实例)

arr.keys()拿到数组下标

  • 补充:拥有迭代(遍历)器接口iterator的对象都可以使用for-of语句。
  • 可以使用keys()拿到数组每一项元素的数组下标。
    1
    2
    3
    4
    5
    6
    const arr = [1, 2, 3, 4.4, "lalal", false];
    console.log(arr.keys());
    // Array Iterator {},说明arr.keys()含有迭代器接口Iterator ,故可以使用`for-of`
    for (let i of arr.keys()) {
    console.log(i); // 依次返回 1 2 3 4 5
    }

arr.values()拿到数组元素

使用values()可以按顺序拿到数组中的每一项。

1
2
3
4
5
const arr = [1, 2, 3, 4.4, "lalal", false];

for (let v of arr.values()) {
console.log(v); // 依次返回 1 2 3 4.4 label false
}

arr.entries()同时拿到数组下标和元素

  • 使用entries()可同时拿到数组下标和元素。

  • 返回值:数组,[数组下标,对应的数组元素]

  • 例子

    1
    2
    3
    4
    5
    6
    const arr = [1, 2, 3, 4.4, "lalal", false];

    for (let e of arr.entries()) {
    console.log(e);
    // 依次返回 [0, 1] [1, 2] [2, 3] [3, 4.4] [4, 'lalal'] [5, false]
    }
  • 上面例子拿到的e都是数组形式的,所以我们可以对取出来的e进行数组的解构赋值,同时拿到数组下标i和数组元素v:

    1
    2
    3
    4
    5
    const arr = [1, 2, 3, 4.4, "lalal", false];

    for (let [i,v] of arr.entries()) {//对取出来的e进行数组的解构赋值,e = [i,v]
    console.log(i,v);
    }

    运行结果


find()返回数组中满足回调函数的第一个元素

  • find()方法会按顺序对数组中的每一项元素执行一次 callback 函数,直至有一个 callback 返回 true。当找到了这样一个元素后,该方法会立即返回这个元素的值,否则返回 undefined
  • 语法arr.find(callback[, thisArg])
参数 描述
callback 在数组每一项上执行的**测试函数(回调函数)**,接收 3 个参数:
element:当前遍历到的元素。
index:可选,当前遍历到的索引。
array:可选,数组本身。
thisArg 可选,执行回调时用作this的对象。
  • 返回值:数组中满足提供的测试函数(回调函数)的第一个元素的值。否则返回 undefined

  • 例子(使用find()找到双数的数组元素):

    1
    2
    3
    4
    5
    6
    7
    const res = [1, 7, 6, 3].find(function (value) {
    console.log("value", value);

    return value % 2 === 0;//通过取余找到双数

    });
    console.log(res);//6

    运行结果

  • 例子分析:可以从运行结果上看到,1和7循环以后都不符合要求,循环到6时符合要求,输出6。

  • 使用箭头函数简化例子

    1
    2
    const res = [1, 7, 6, 3].find(value => value % 2 === 0);//通过取余找到双数
    console.log(res);//6

findIndex()返回数组中找到的元素的下标

  • 根据条件(回调函数)按顺序遍历数组,当回调返回true时就返回当前遍历到的下标,语法、参数都与find()非常相似。
  • 语法arr.findIndex(callback[, thisArg])
参数 描述
callback 在数组每一项上执行的**测试函数(回调函数)**,接收 3 个参数:
element:当前遍历到的元素。
index:可选,当前遍历到的索引。
array:可选,数组本身。
thisArg 可选,执行回调时用作this的对象。
  • 返回值:数组中满足提供的测试函数的**第一个元素的索引(下标)**。否则返回-1
  • 例子(找到双数的数组元素的下标):
    1
    2
    const res = [1, 7, 6, 3].findIndex(value => value % 2 === 0);//通过取余找到双数
    console.log(res);//2

findIndex()indexOf()的区别

  • findIndex()indexOf()都是返回查找元素的数组下标的,但是findIndex()多了一个回调函数(测试函数),所以可以做一些indexOf()做不到的事情。
  • indexOf()无法查询数组中是否含有NaN:
    `indexOf()`无法查询数组中是否含有`NaN`
  • 但是findIndex()可以通过回调函数里调用NumberisNaN()来判断数组里是否含有NaN
    1
    2
    const res = [1, 7, 6, 3, NaN].findIndex(value => Number.isNaN(value));
    console.log(res);//4,也就是说找到了NaN的下标为4