数组的扩展

扩展运算符(Spread syntax)

基本用法

使用扩展运算符...可以将一个数组转为参数序列

1
console.log(...[1, 2, 3])             // => 1 2 3

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

1
2
3
4
5
6
var array = [];
function pushItems(arr, ...items) {
arr.push(...items);
return arr;
}
console.log(pushItems(array, ...[1, 2, 3])); // => [1, 2, 3]
1
2
var add = (x, y) => (x + y);
console.log(add(...[1, 10])); // => 11

扩展运算符可以与正常的函数参数一起使用,也可以放置表达式;如果后面是空数组,则不产生任何效果

替代数组的apply方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5写法
function f(x, y, z) {
// code
}
var array = [1, 2, 3];
add.apply(null, array);
--------------------------------------------------
// ES6写法
function f(x, y, z) {
// code
}
var array = [1, 2, 3];
add(...array);

例子:

1
2
3
4
5
6
7
8
// ES5 的写法
Math.max.apply(null, [14, 3, 77]) // => 77
----------------------------------------------
// ES6 的写法
Math.max(...[14, 3, 77]) // => 77
----------------------------------------------
// 等同于
Math.max(14, 3, 77) // => 77

扩展运算符的应用

  • 合并数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var array1 = [1, 2];
    var array2 = ['a', 's', 'd'];
    var array3 = ['+'];

    // ES5写法
    array1.concat(array2, array3); // => [1, 2, 'a' ,'s', 'd', '+']

    // ES6写法
    [...array1, ...array2, ...array3] // => [1, 2, 'a' ,'s', 'd', '+']
  • 复制数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var array1 = [1, 2];

    // ES5中若直接令array2 = array1,则复制的是相同的引用,无法真正的复制
    // 可以采用一种变通的方法
    var array2 = array1.concat();

    // ES6写法
    var array2 = [...array1];
    // 或
    var [...array2] = array1;
  • 与解构赋值结合

    1
    2
    3
    4
    5
    6
    7
    var array = [1, 2, 3, 4, 5];
    // ES5
    var a = array[0],
    rest = array.slice(1);

    // ES6
    [a, ...rest] = array;

    如果将扩展运算符用于数组赋值,则只能将其放在参数的最后一位,否则会报错

  • 字符串

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

    1
    [...'hello']                     // => ['h', 'e', 'l', 'l', 'o']

    可以正确识别32位Unicode字符,正确返回字符串长度函数可以这样写:

    1
    2
    3
    function length(str) {
    return [...str].length;
    }
  • 实现Iterator接口的对象

    任何Iterator接口的对象都可以用扩展运算符转为真正的数组

    1
    2
    var nodeList = document.querySelectorAll('div');
    var array = [...nodeList];

Array.from()

Array.from()方法可以将类似数组的对象和可遍历对象转为数组

1
2
3
4
5
6
7
8
9
10
11
12
var arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

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

// ES6写法
var arr2 = Array.from(arrayLike); // => ['a', 'b', 'c']

扩展运算符背后调用的是遍历器接口Symbol.iterator,如果没有该接口则无法转换

Array.from同时还支持任何有length属性的对象

1
Array.from({ length: 3 })         // => [undefined, undefined, undefined]

Array.from还支持第二个参数,用来修改数组的每个元素

1
2
3
4
Array.from(arrayLike, x => x + 1)     // => ['a1', 'b1', 'c1']

// 相当于
Array.from(arrayLike).map(x => x + 1)

Array.of()

Array.of方法用于将一组值转换为数组。如果没有参数,就返回一个空数组

1
2
Array.of(1, 2, 3)                     // => [1, 2, 3]
Array.of() // => []

实现方法

1
2
3
4
5
if (!Array.of) {
Array.of = function() {
return Array.prototype.slice.call(arguments);
};
}

数组实例的方法

  • copyWithin()

    copyWithin方法会在当前数组内部将指定位置的成员复制到其他位置,然后返回当前数组

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

    可以接受3个参数:

    • target(必选):从该位置开始替换数据(length + target)

    • start(可选):从该位置开始读取数据,默认为0;如果为负数,表示倒数(length + start)

    • end(可选):到该位置之前停止读取数据,默认等于数组长度;如果为负数,表示倒数(length + end)

    1
    2
    3
    4
    5
    6
    7
    [1, 2, 3, 4, 5, 6].copyWithin(0, 3, 5)     // => [4, 5, 3, 4, 5, 6]
    [1, 2, 3, 4, 5, 6].copyWithin(0, -4, -1) // => [3, 4, 5, 4, 5, 6]
    ---------------------------------------
    [].copyWithin.call({
    length: 5,
    '3': 1
    }, 0, 3) // => { '0': 1, '3': 1, length: 5 }

    ECMA262中copyWithin如下步骤实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    Array.prototype.copyWithin = function(target, start/*, end*/) {
    if (this == null) {
    throw new TypeError('this is null or not defined');
    }

    var O = Object(this);

    var len = O.length >>> 0;
    var relativeTarget = target >> 0;
    var to = relativeTarget < 0 ? Math.max(len + relativeTarget, 0) : Math.min(relativeTarget, len);
    var relativeStart = start >> 0;
    var from = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
    var end = arguments[2];
    var relativeEnd = end === undefined ? len : end >> 0;
    var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
    var count = Math.min(final - from, len - to);

    var direction = 1;

    if (from < to && to < (from + count)) {
    direction = -1;
    from += count - 1;
    to += count - 1;
    }

    while (count > 0) {
    if (from in O) {
    O[to] = O[from];
    } else {
    delete O[to];
    }
    from += direction;
    to += direction;
    count--;
    }

    return O;
    };
  • find()findIndex()

    find用于找出第一个符合条件的数组成员,参数为一个回调函数,对所有数组成员依次执行,返回第一个返回true的成员

    1
    Array.prototype.find(callback[, thisArg])

    可以接受两个参数

    • callback

      在数组每一项上执行的函数,接收 3 个参数:element(当前遍历到的元素)、index(可选,当前遍历到的索引)、array(可选,数组本身)

    • thisArg(可选)

      执行回调时用作this的对象。

    1
    [1, 3, 4, 7].find(n => n % 2 !== 1)     // => 4

ECMA262中find如下步骤实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Object.defineProperty(Array.prototype, 'find', {
value: function(predicate) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}

var o = Object(this);
var len = o.length >>> 0;

if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}

var thisArg = arguments[1];

var k = 0;
while (k < len) {
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return kValue;
}
k++;
}

return undefined;
}
});

findIndex用于返回第一个符合条件的数组成员位置,如果所有都不符合,则返回-1

  • fill()

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

    1
    Array.prototype.fill(value[, start[, end]])

    可以接受三个参数:

    • value(必选):用于填充的值

    • start(可选):填充的起始位置,默认为0,如果为负数,表示倒数(length + start)

    • end(可选):填充结束位置之前,默认为length,如果为负数,表示倒数(length + end)

    1
    [1, 2, 3, 4].fill(9, 0, 2)      // => [9, 9, 3, 4]

    ECMA262中fill如下步骤实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Object.defineProperty(Array.prototype, 'fill', {
    value: function(value) {
    if (this == null) {
    throw new TypeError('this is null or not defined');
    }

    var O = Object(this);
    var len = O.length >>> 0;
    var start = arguments[1];
    var relativeStart = start >> 0;
    var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
    var end = arguments[2];
    var relativeEnd = end === undefined ? len : end >> 0;
    var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);

    while (k < final) {
    O[k] = value;
    k++;
    }

    return O;
    }
    });
  • entries()keys()values()

    三个方法都返回一个遍历器对象,keys()是对键名的遍历,values()是对键值的遍历,entries()是对键值对的遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for (let index of [1, 2, 3].keys()) {
    console.log(index);
    } // => 0 1 2
    for (let index of [1, 2, 3].values()) {
    console.log(index);
    } // => 1 2 3
    for (let index of [1, 2, 3].entries()) {
    console.log(index);
    } // => [0, 1] [1, 2] [2, 3]
  • includes()

    includes方法返回一个布尔值,表示某个数组是否包含给定的值

    1
    Array.prototype.includes(searchElement[, fromIndex])

    可以接受两个参数

    • searchElement(必选):需要查找的元素值

    • fromIndex(可选):从fromIndex索引处开始查找,如果为负值,则从length + fromIndex开始查找,默认为 0

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

对象的扩展

函数的扩展