这篇文章是我学习了《你不知道的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()返回的值一样。
完