主要知识点:创建数组、数组上的新方法、类型化数组
1. 创建数组
在 ES6 之前创建数组主要存在两种方式: Array
构造器与数组字面量写法。这两种方式都需要将数组的项分别列出,并且还要受到其他限制。将“类数组对象”(即:拥有数值类型索引与长度属性的对象) 转换为数组也并不自由,经常需要书写额外的代码。为了使数组更易创建,ES6 新增了Array.of()
与 Array.from()
方法。
Array.of()方法
Array.of()方法会将方法的传入参数全部作为数组里的数据内容,而不管参数的数量与类型:
let items = Array.of(1,'2');
console.log(items); //Array(2)
console.log(items.length); //2
复制代码
Array.from()方法
可使用Array.from()方法可以将类数组对象和可迭代对象转换成数组。该方法接收三个参数:
- 待转换的可迭代对象或者类数组对象(具有数值索引和长度属性的对象);
- 可选的映射函数;
- 指定映射函数内部的
this
值;
映射转换
如果你想实行进一步的数组转换,你可以向 Array.from() 方法传递一个映射用的函数作为第二个参数。此函数会将类数组对象的每一个值转换为目标形式,并将其存储在目标数组的对应位置上。例如:
function trans(values){
return Array.from(arguments,item=>item+1);
}
console.log(trans(1,2,3)); //[2, 3, 4]
复制代码
可迭代对象上使用
Array.from()
方法不仅可用于类数组对象,也可用于可迭代对象,也就是说可以将任意包含 Symbol.iterator
属性的对象转换成数组,例如:
let obj = {
*[Symbol.iterator](){
yield 1;
yield 2;
yield 3;
}
}
console.log(Array.from(obj)); //[1, 2, 3]
复制代码
2. 数组上所有的新方法
2.1 find()和findIndex()方法
indexOf()
和lastIndexOf()
方法用于查找特定值在数组中的位置,而如果需要查找在数组中满足特定条件的元素就需要使用find()
和findIndex()
方法。
find()
方法和 findIndex()
方法均接受两个参数:一个是回调函数、一个是可选的指定回调函数中的 this
值。回调函数中有三个参数:
- 数组中的数据项元素;
- 元素在数组中的索引位置;
- 数组实例对象本身;
回调函数与 map()
和 forEach()
等方法的回调函数中的参数一致。例如,需要找到在数组中大于5的元素:
let arr = Array.of(1,5,8,9);
console.log(arr.find(item=>item>5)); //8
console.log(arr.findIndex(item=>item>5)); //2
复制代码
可以看出,find()
方法返回的是满足特定条件的数据项,而 findIndex()
方法返回的是满足特定条件的元素索引。
2.2 fill()方法
fill()
方法能使用特定值填充数组中的一个或多个元素。当只使用一个参数的时候,该方法会用该参数的值填充整个数组。若你不想改变数组中的所有元素,而只想改变其中一部分,那么可以使用可选的起始位置参数与结束位置参数(不包括结束位置的那个元素)。
let arr = Array.of(1,5,8,9);
console.log(arr.fill(1,2,4)); //[1, 5, 1, 1]
复制代码
若只给定起始位置,不指定结束位置的话,默认结束位置为数组末尾。
2.3 copyWithin()方法
copyWithin()
方法与 fill()
类似,可以一次性修改数组的多个元素。不过,与 fill()
使用单个值来填充数组不同, copyWithin()
方法允许你在数组内部复制自身元素。为此你需要传递两个参数给 copyWithin()
方法:从什么位置开始进行填充,以及被用来复制的数据的起始位置索引,以及可选的复制结束的位置(不包含该位置)
//copyWithin()
let arr = Array.of(1,5,8,9);
console.log(arr.copyWithin(2,0,2)); //[1, 5, 1, 5]
复制代码
3. 类型化数组
ES6 采纳了类型化数组,将其作为语言的一个正式部分,以确保在 JS 引擎之间有更好的兼容性,并确保与 JS 数组有更好的互操作性。
3.1 数值数据类型
JS 数值使用 IEEE 754 标准格式存储,使用 64 位来存储一个数值的浮点数表示形式,该格式在 JS 中被同时用来表示整数与浮点数;当值改变时,可能会频繁发生整数与浮点数之间的格式转换。而类型化数组则允许存储并操作八种不同的数值类型:
- 8 位有符号整数(int8)
- 8 位无符号整数(uint8)
- 16 位有符号整数(int16)
- 16 位无符号整数(uint16)
- 32 位有符号整数(int32)
- 32 位无符号整数(uint32)
- 32 位浮点数(float32)
- 64 位浮点数(float64)
所有与类型化数组相关的操作和对象都围绕着这八种数据类型。为了使用它们,要先创建一个数组缓冲区用于存储数据。
3.2 数组缓冲区
数组缓冲区(array buffer) 是内存中包含一定数量字节的区域,而所有的类型化数组都基于数组缓冲区。创建数组缓冲区使用 ArrayBuffer 构造器:
let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); //10
复制代码
调用 ArrayBuffer
构造器时,只需要传入单个数值用于指定缓冲区包含的字节数,而本例就创建了一个 10 字节的缓冲区。当数组缓冲区被创建完毕后,你就可以通过检查 byteLength
属性来获取缓冲区的字节数。
还可以使用slice()
方法来创建一个包含已有缓冲区部分内容的数组缓冲区,其中slice()
方法可以使用起始位置和结束位置(不包含结束位置):
let buffer = new ArrayBuffer(10);
let buf = buffer.slice(4,8);
console.log(buf.byteLength); //4
复制代码
仅仅创建一个数组缓冲器不能写入数据,是没有任何意义的。要想写入数据,需要创建视图(view)。
3.3 使用视图操作数组缓冲区
数组缓冲区代表了一块内存区域,而视图(views) 则是你操作这块区域的接口。视图工作在数组缓冲区或其子集上,可以读写某种数值数据类型的数据。 DataView 类型是数组缓冲区的通用视图,允许你对前述所有八种数值数据类型进行操作。
创建视图,需要使用 DataView() 构造器,可以指定可选参数-字节偏移量以及所要包含的字节数。如果不指定所要包含的字节数,则默认为从字节偏移量直到数组缓冲区的末尾。
let buffer = new ArrayBuffer(10),
view = new DataView(buffer, 5, 2);
复制代码
此例中的 view
只能使用索引值为 5 与 6 的字节。使用这种方式,你可以在同一个数组缓冲区上创建多个不同的视图,这样有助于将单块内存区域供给整个应用使用,而不必每次在有需要时才动态分配内存。
获取视图信息
可以通过视图的可读属性来获取视图的信息:
- buffer :该视图所绑定的数组缓冲区;
- byteOffset :传给 DataView 构造器的第二个参数,如果当时提供了的话(默认值为0);
- byteLength :传给 DataView 构造器的第三个参数,如果当时提供了的话(默认值为该缓冲区的 byteLength 属性。
读取或写入数据
对应于 JS 所有八种数值数据类型, DataView
视图的原型分别提供了在数组缓冲区上写入数据的一个方法、以及读取数据的一个方法。所有方法名都以“set”或“get”开始,其后跟随着对应数据类型的缩写。下面列出了能够操作 int8
或 uint8
类型的读取/写入方法:
- getInt8(byteOffset, littleEndian) :从 byteOffset 处开始读取一个 int8 值;
- setInt8(byteOffset, value, littleEndian) :从 byteOffset 处开始写入一个 int8 值;
- getUint8(byteOffset, littleEndian) :从 byteOffset 处开始读取一个无符号 int8 值;
- setUint8(byteOffset, value, littleEndian) :从 byteOffset 处开始写入一个无符号int8 值。
get”方法接受两个参数:开始进行读取的字节偏移量、以及一个可选的布尔值,后者用于指定读取的值是否采用低字节优先方式(注:默认值为 false ) 。“set”方法则接受三个参数:开始进行写入的字节偏移量、需要写入的数据值、以及一个可选的布尔值用于指定是否采用低字节优先方式存储数据。
如果针对的是16位或者32位整数的话,只需要将上面的方法中的8,相应的改变成16或者32,就可以操作16位或者32值。
操作浮点数,提供了下面这些方法:
- getFloat32(byteOffset, littleEndian) :从 byteOffset 处开始读取一个 32 位的浮点数;
- setFloat32(byteOffset, value, littleEndian) :从 byteOffset 处开始写入一个 32 位的浮点数;
- getFloat64(byteOffset, littleEndian) :从 byteOffset 处开始读取一个 64 位的浮点数;
- setFloat64(byteOffset, value, littleEndian) :从 byteOffset 处开始写入一个 64 位的浮点数;
例如使用上述这些视图上的方法来进行操作:
let buffer = new ArrayBuffer(2),
view = new DataView(buffer);
view.setInt8(0,1);
console.log(view.getInt8(0)); //1
复制代码
3.4 类型化数组就是视图
ES6 的类型化数组实际上也是针对数组缓冲区的特定类型视图,你可以使用这些数组对象来处理特定的数据类型,而不必使用通用的 DataView 对象。一共存在八种特定类型视图,对应着八种数值数据类型。类型化数组的构造器有:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array。
创建特定类型视图
创建特定类型视图有三种方式:
-
第一种方式是使用与创建 DataView 时相同的参数,即:一个数组缓冲区、一个可选的字节偏移量、以及一个可选的字节数量;
-
第二种方式是传递单个数值给类型化数组的构造器,此数值表示该数组包含的元素数量(而不是分配的字节数) 。构造器将会创建一个新的缓冲区,分配正确的字节数以便容纳指定数量的数组元素,而你也可以使用 length 属性来获取这个元素数量;
-
第三种方式是向构造器传递单个对象参数,可以是下列四种对象之一:
- 类型化数组:数组所有元素都会被复制到新的类型化数组中。例如,如果你传递一个 int8类型的数组给 Int16Array 构造器,这些 int8 的值会被复制到 int16 数组中。新的类型化数组与原先的类型化数组会使用不同的数组缓冲区。
- 可迭代对象:该对象的迭代器会被调用以便将数据插入到类型化数组中。如果其中包含了不匹配视图类型的值,那么构造器就会抛出错误。
- 数组:该数组的元素会被插入到新的类型化数组中。如果其中包含了不匹配视图类型的值,那么构造器就会抛出错误。
- 类数组对象:与传入数组的表现一致。
//第一种方式 let buffer = new ArrayBuffer(10), view1 = new Int8Array(buffer), view2 = new Int8Array(buffer, 5, 2);
//第二种方式 let ints = new Int16Array(2), floats = new Float32Array(5);
//第三种方式 let ints1 = new Int16Array([25, 50]), ints2 = new Int32Array(ints1);
3.5 类型化数组与常规数组的相似点
类型化数组在很多场景中都可以像常规数组那样被使用。例如,你可以使用 length 属性来获取类型化数组包含的元素数量,还可以使用数值类型的索引值来直接访问类型化数组的元素。它们二者具有的相似点:
- 公共方法
类型化数组拥有大量与常规数组等效的方法:
- copyWithin()
- entries()
- fill()
- filter()
- find()
- findIndex()
- forEach()
- indexOf()
- join()
- keys()
- lastIndexOf()
- map()
- reduce()
- reduceRight()
- reverse()
- slice()
- some()
- sort()
- values()
相同的迭代器
与常规数组相同,类型化数组也拥有三个迭代器,它们是 entries()
方法、 keys()
方法与values()
方法。这就意味着你可以对类型化数组使用扩展运算符,或者对其使用 for-of
循环,就像对待常规数组。
of()和from()方法
所有的类型化数组都包含静态的of()
与 from()
方法,作用类似于 Array.of()
与Array.from()
方法。其中的区别是类型化数组的版本会返回类型化数组,而不返回常规数组。
3.5 类型化数组与常规数组的差异
二者最重要的区别就是类型化数组并不是常规数组,类型化数组并不是从 Array 对象派生的,使用 Array.isArray()
去检测会返回 false
。
行为差异
常规数组可以被伸展或是收缩,然而类型化数组却会始终保持自身大小不变。你可以对常规数组一个不存在的索引位置进行赋值,但在类型化数组上这么做则会被忽略。
let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[2] = 5;
console.log(ints.length); // 2
console.log(ints[2]); // undefined
复制代码
在本例中,尽管对索引值 2 的位置进行了赋值为 5 的操作,但 ints 数组却完全没有被伸展,数组的长度属性保持不变,所赋的值也被丢弃了。
类型化数组也会对数据类型进行检查以保证只使用有效的值,当无效的值被传入时,将会被替换为 0 。
let ints = new Int16Array(["hi"]);
console.log(ints.length); // 1
console.log(ints[0]); // 0
复制代码
这段代码试图用字符串值 "hi" 创建一个 Int16Array ,而字符串对于类型化数组来说当然是无效的值,因此该字符串被替换为 0 并插入数组。此数组的长度仅仅是 1 ,而 ints[0]只包含了 0 这个值。
所有在类型化数组上修改项目值的方法都会受到相同的限制,例如当 map() 方法使用的映射函数返回一个无效值的时候,类型化数组会使用 0 来代替返回值:
let ints = new Int16Array([25, 50]),
mapped = ints.map(v => "hi");
console.log(mapped.length); // 2
console.log(mapped[0]); // 0
console.log(mapped[1]); // 0
console.log(mapped instanceof Int16Array); // true
console.log(mapped instanceof Array); // false
复制代码
由于字符串值 "hi" 并不是一个 16 位整数,它在结果数组中就被替换成为 0 。
遗漏的方法
尽管类型化数组拥有常规数组的很多同名方法,但仍然缺少了几个数组方法,包括下列这些:
- concat()
- pop()
- push()
- shift()
- splice()
- unshift()
附加的方法
类型化数组还有两个常规数组所不具备的方法: set()
方法与 subarray()
方法。这两个方法作用相反: set()
方法从另一个数组中复制元素到当前的类型化数组,而 subarray()
方法则是将当前类型化数组的一部分提取为新的类型化数组。
**set() 方法接受一个数组参数(无论是类型化的还是常规的) 、以及一个可选的偏移量参数,后者指示了从什么位置开始插入数据(默认值为 0 ) **。数组参数中的数据会被复制到目标类型化数组中,并会确保数据值有效。
let ints = new Int16Array(4);
ints.set([25, 50]);
ints.set([75, 100], 2);
console.log(ints.toString()); // 25,50,75,100
复制代码
这段代码创建了一个包含四个元素的 Int16Array
;第一次调用 set()
复制了两个值到数组起始的两个位置;而第二次调用set()
则使用了一个值为 2 的偏移量参数,指明应当从数组的第三个位置(索引 2 ) 开始放置所复制的数据。
subarray()
方法接受一个可选的开始位置索引参数、以及一个可选的结束位置索引参数(像slice()
方法一样,结束位置的元素不会被包含在结果中) ,并会返回一个新的类型化数组。你可以同时省略这两个参数,从而创建原类型化数组的一个复制品。
let ints = new Int16Array([25, 50, 75, 100]),
subints1 = ints.subarray(),
subints2 = ints.subarray(2),
subints3 = ints.subarray(1, 3);
console.log(subints1.toString()); // 25,50,75,100
console.log(subints2.toString()); // 75,100
console.log(subints3.toString()); // 50,75
复制代码
本例中利用 ints
数组创建了三个类型化数组。 subints1
数组是 ints
的一个复制品,包含了原数组的所有信息;而 subints2
则从原数组的索引 2 位置开始复制,因此包含了原数组的最末两个元素(即 75 与 100 ) ;最后的 subints3
数组值包含了原数组的中间两个元素,因为调用 subarray()
时同时使用了起始位置与结束位置参数。
4. 总结
- ES6 延续了 ES5 的工作以便让数组更加有用。新增了两种创建数组的方式: Array.of() 方法、以及 Array.from() 方法,其中后者可以将可迭代对象或类数组对象转换为正规数组;
- fill() 方法与 copyWithin() 方法允许你替换数组内的元素。 find() 方法与 findIndex() 方法在数组中查找满足特定条件的元素时会非常有用,其中前者会返回满足条件的第一个元素,而后者会返回该元素的索引位置;
- 类型化数组并不是严格的数组,它们并没有继承 Array 对象,但它们的外观和行为都与数组有许多相似点。类型化数组包含的数据类型是八种数值数据类型之一,基于数组缓冲区对象建立,用于表示按位存储的一个数值或一系列数值。类型化数组能够明显提升按位运算的性能,因为它不像 JS 的常规数值类型那样需要频繁进行格式转换。