JS学习笔记--ES6新增API Array部分

183 阅读6分钟

这篇文章是我学习了《你不知道的JavaScript》下卷 ES6 新增API Array部分,然后摘抄与整理所学到的多种 Array 函数。

1. 静态函数 Array of(..)

Array(..) 构造器有一个众所周知的陷阱,就是如果只传入一个参数,并且这个参数是数字的话,那么不会构造一个值为这个数字的单个元素的数组,而是构造一个空数组,其length属性为这个数字。

Array.of(..) 取代了 Array(..)成为数组的推荐函数形式构造器,因为Array.of(..)并没有这个特殊的单个数字参数的问题。

var a = Array( 3 );
a.length;                       // 3
a[0];                           // undefined

var b = Array.of( 3 );
b.length;                       // 1
b[0];                           // 3

var c = Array.of( 1, 2, 3 );
c.length;                       // 3
c;                              // [1,2,3]

在以下两种情况可能需要使用Array.of(..)语法创建一个数组:

(1)有一个回调函数需要传入的参数封装成数组,Array.of(..)可以完美解决这个需求。

(2)构建Array的子类,并且想要在子类实例中创建和初始化元素,比如:

class MyCoolArray extends Array {
    sum() {
        return this.reduce( function reducer(acc,curr){
            return acc + curr;
        }, 0 );
    }
}

var x = new MyCoolArray( 3 );
x.length;                       // 3--oops!
x.sum();                        // 0--oops!

var y = [3];                    // Array,而不是MyCoolArray
y.length;                       // 1
y.sum();                        // sum不是一个函数

var z =MyCoolArray.of( 3 );
z.length;                       // 1
z.sum();                        // 3

2. 静态函数 Array.from(..)

JavaScript 中的 “类(似)数组对象” 是指一个有length属性,具体说是大于等于0的整数值的对象。

这样的值在使用时是非常令人沮丧的,普遍的需求就是把他们转换为真正的数组,这样就可以应用各种 Array.prototype 方法 (map(..)indexOf(..)等)。转换过程类似于:

// 类数组对象
var arrLike = {
    length: 3,
    0: "foo",
    1: "bar"
};

var arr = Array.prototype.slice.call( arrLike );

另一个常见的任务是使用slice(..)来复制产生一个真正的数组:

var arr2 = arr.slice();

两种情况下,新的ES6 Array.from(..)方法都是更好理解、更优雅、更简洁的替代方法:

var arr = Array.from( arrLike );

var arrCopy = Array.from( arr );

Array.from(..)检查第一个参数是否为iterable,如果是的话,就使用迭代器来产生值并“复制”进入返回的数组。

而如果把类数组对象作为第一个参数传给Array.from(..),它的行为方式和slice()(没有参数)或者apply(..)是一样的,就是简单地按照数字命名的属性从0开始直到length值在这些值上循环。

考虑:

var arrLike = {
    length: 4,
    2: "foo"
};

Array.from( arrLike );
// [ undefined, undefined, "foo", undefined ]

因为位置0、1和3在arrLike上并不存在,所以在这些位置上是undefined值。

1. 避免空槽位

使用Array.from(..)永远不会产生空槽位。

在ES6之前,如果想要产生一个初始化为某长度,在每个槽位上都是真正的undefined值(不是空槽位!)的数组,不得不做额外的工作:

var a = Array(4);
// 4个空槽位!

var b = Array.apply( null, {length: 4} );
// 4个undefined值

而现在Array.from(..)使其简单了很多:

var c = Array.from( { length: 4 } );
// 4个undefined值

2. 映射

Array.from(..)工具还有另外一个有用的技巧。如果提供了话,第二个参数是一个映射回调,这个函数会被调用,来把来自于源的每个值映射/转换到返回值。考虑:

var arrLike = {
    length: 4,
    2: "foo"
};

Array.from( arrLike, function mapper(val,idx){
    if(typeof val == "string") {
        return val.toUpperCase();
    }
    else {
        return idx;
    }
});
// [ 0, 1, "FOO", 3 ]

Array.from(..)接受一个可选的第三个参数,如果设置了的话,这个参数为作为第二个参数传入的回调指定this绑定。否则 this将会是undefined。

3. 原型方法 copyWithin(..)

Array#copyWithin(..)是一个新的修改器方法,所有数组都支持。copyWithin(..)从一个数组中复制一部分到同一个数组的另一个位置,覆盖这个位置所有原来的值。

参数是 target(要复制到的索引)、start(开始复制的源索引,包括在内)以及可选的 end(复制结束的不包含索引)。如果任何一个参数是负数,就被当做是相对于数组结束的相对值。

考虑:

[1,2,3,4,5].copyWithin( 3, 0 );         // [1,2,3,1,2]

[1,2,3,4,5].copyWithin( 3, 0, 1 );      // [1,2,3,1,5]

[1,2,3,4,5].copyWithin( 0, -2 );        // [4,5,3,4,5]

[1,2,3,4,5].copyWithin( 0, -2, -1 );    // [4,2,3,4,5]

copyWithin(..)方法不会增加数组的长度。到达数组结尾复制就会停止。

4. 原型方法 fill(..)

可以通过ES6原生支持的方法Array#full(..)用指定值完全(或部分)填充已存在的数组:

var a = Array( 4 ).fill( undefined );
a;
// [undefined,undefined,undefined,undefined]

fill(..)可选地接受参数start和end,它们指定了数组要填充的子集位置,比如:

var a = [ null, null, null, null].fill( 42, 1, 3 );

a;                                  // [null,42,42,null]

5. 原型方法 find(..)

一般来说,在数组中搜索一个值的最常用方法一直是indexOf(..)方法,这个方法返回找到值的索引,如果没有找到就返回-1:

var a = [1,2,3,4,5];

(a.indexOf( 3 ) != -1);                 // true
(a.indexOf( 7 ) != -1);                 // false

(a.indexOf( "2" ) != -1);               // false

相比之下,indexOf(..)需要严格匹配===,所以搜索"2"不会找到值2,反之也是如此。indexOf(..)的匹配算法无法覆盖,而且要手动与值-1进行比较也很麻烦/笨拙。

从ES5以来,控制匹配逻辑的最常用变通技术是使用some(..)方法。它的实现是通过为每个元素调用一个函数回调,直到某次调用返回 true/真值时才会停止。你可以可定这个回调函数,因此也就有了对匹配方式的完全控制:

var a = [1,2,3,4,5];

a.some( function matcher(v){
    return v == "2";
} );                            // true

a.some( function matcher(v){
    return v == 7;
} );                            // false

但这种方式的缺点是如果找到匹配的值的时候,只能得到匹配的 true/false 指示,而无法得到真正的匹配值本身。

ES6的find(..)解决了这个问题。基本上它和some(..)的工作方式一样,除了一旦回调返回 true/真值,还会返回实际的数组值:

var a = [1,2,3,4,5];

a.find( function matcher(v){
    return v == "2";
} );                            // 2

a.find( function matcher(v){
    return v == 7;
} );                            // undefined

通过自定义matcher(..)函数也可以支持比较像对象这样的复杂值:

var points = [
    { x: 10, y: 20 },
    { x: 20, y: 30 },
    { x: 30, y: 40 },
    { x: 40, y: 50 },
    { x: 50, y: 50 }
];

points.find( function matcher(point){
    return (
        point.x % 3 == 0 &&
        point.y % 4 == 0
    );
} );                                // { x: 30, y: 40 }

find(..)接受一个可选的第二个参数,如果设定这个参数就绑定到第一个参数回调的this。否则,this就是undefined

6. 原型方法 findIndex(..)

如果要从数组中找到匹配值的位置索引,indexOf(..)会提供这项功能,但是无法控制匹配逻辑,它总是使用 === 严格相等。所以ES6的findIndex(..)才是解决方案:

var points = [
    { x: 10, y: 20 },
    { x: 20, y: 30 },
    { x: 30, y: 40 },
    { x: 40, y: 50 },
    { x: 50, y: 50 }
];

points.findIndex( function matcher(point){
    return (
        point.x % 3 == 0 &&
        point.y % 4 == 0
    );
} );                                // 2

points.findIndex( function matcher(point){
    return (
        point.x % 6 == 0 &&
        point.y % 7 == 0
    );
} );                                // -1

就像其他接受回调的数组方法一样,findIndex(..)方法接受一个可选的第二个参数,如果设定这个参数就绑定到第一个参数回调的this。否则,this就是undefined

7. 原型方法 entries()、values()、keys()

因为Array对于ES6来说已经不是新的了,所以从传统角度来说,它可能不会被看作是“集合”,但是它提供了同样的迭代器方法entries(..)values(..)keys(..),从这个意义上说,它是一个集合。考虑:

var a = [1,2,3];

[...a.values()];                    // [1,2,3]
[...a.keys()];                      // [0,1,2]
[...a.entries()];                   // [ [0,1], [1,2], [2,3] ]

[...a.[Symbol.iterator]()];         // [1,2,3]

就像Set一样,默认的Array迭代器和values()返回的值一样。

原文连接