ES6学习笔记9--增强的数组功能

  |  

前言

数组从一开始就是JS中的一种基本对象。但是随着ES的版本迭代,数组的功能却基本不变,直到ES6。ES6为数组添加了许多功能来强化数组,并且添加了创建类型化数组的能力


42



创建数组

Array.of()方法

在ES6之前通过Array构造器创建数组时总有一个怪了一点。调用new Array()构造器时,根据传入的参数与数量的不同,实际上会导致一些不同的结果

1
2
3
4
let items = new Array(2);
console.log(items.length); //2
console.log(items[0]); // undefined
console.log(times[1]); // undefined

及在使用单个数值作为参数来调用Array构造器时,数组的长度属性会被设置为该参数;而使用单个非数值型来调用,该参数就会成为目标数组的唯一项。

ES6引入了Array.of()方法来解决这个怪异的点。该方法的作用非常类似于Array构造器,但在使用单个数值参数的时候并不导致特殊结果。Array.of()方法总会创建一个包含所有传入参数的数值,而不管参数的数量和类型。。

需要注意的是:

Array.of()方法并没有使用Symbol.species属性来决定返回值的类型,而是使用了当前的构造器(即of()方法内部的this)来决定的。

Array.from()方法

在ES5版本前,我们将类数组对象转换为数组总是显得很麻烦。我们通常使用

1
2
Array.prototype.slice.call(arrLike);
[].slice.call(arrLike);

来实现类数组对象转换为数组。尽管这种方法使用较小的代码量即可完成转换,但是从代码的表现形式来看,我们并无法得知其功能就是完成类数组向数组的转换。也就是说这样的代码形式在功能的体现上不够直观。于是在ES6版本中引入了Array.from()方法。

Array.from()方法传入一个可迭代对象或类数组对象,返回其数组的表现形式。

Array.from()方法同样使用this来决定返回什么类型的数组,而不是使用知名符号Symbol.species

映射转换

如果你想做进一步的转换,可以想Array.from()方法传入第二个参数,一个映射用的函数。该函数会将类数组对象的每一个值转换为目标形式,并将其存储在目标数组的对应位置上。

1
2
3
4
5
function translate() {
return Array.from(arguments, (value) => value + 1);
}
let numbers = translate(1,2,3,4);
console.log(numbers);

如果映射函数需要在对象上进行工作,这时我们可以想Array.from()方法传入第三个参数,从而指定映射函数执行时内部的this值

1
2
3
4
5
6
7
8
9
10
11
let helper = {
diff : 1,
add(value) {
return value + this.diff;
}
}
function translate() {
return Array.from(arguments, helper.add, helper);
}
let numbers = translate(1,2,3,4);
console.log(numbers);

在可迭代对象上使用

Array.from()方法不仅可用于类数组对象,也可以用于可迭代对象,这也就意味着我们可以将任意包含Symbol.iterator属性的对象转换为数组

1
2
3
4
5
6
7
8
9
let numbers = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
let numbers2 = Array.from(numbers, (value) => value + 1);
console.log(numbers2)

所有数组上的新方法

find()与findIndex()方法

在ES5之前,检索数组是件很麻烦的事,我们只能通过遍历数组来实现一定的检索。ES5增加了indexOf()和lastIndexOf()方法。但是这两个函数只能检索特定的值。

ES6引入了find()和findIndex()函数使检索更加的灵活。

find()与findIndex()均接受两个参数。一个回调函数,一个可选值用于指定回调函数执行时内部的this。回调函数接受三个参数:数组的中的当前值、数组当前值的索引以及该数组自身。当给定的元素满足回调函数定义的条件时,回调函数应该返回true。find()和findIndex()均会在回调函数第一次返回true时停止检索,并对应返回匹配的值或匹配的索引。

1
2
3
let numbers = [25,30,35,40,45];
console.log(numbers.find(n => n > 33)); //35
console.log(numbers.findIndex(n => n > 33)); //2

fill()方法

fill()方法能使用特定的值填充数组中的一个或多个元素。

1
array.fill(num, start, end);

num表示要填充的值,start表示填充的起始位置,end表示填充的结束位置,但不包括在内。

如果省略start,则从数组最开始处开始填充,如果省略end,则填充的数组结束。

如果提供的起始位置或结束位置为负数,则实际位置是提供的数加上数组长度。

copyWithin()方法

copyWithin()方法从数组内部复制自身元素来填充数组。

1
array.copyWithin(填充起始位,复制起始位,复制结束位)

数组从复制起始位开始复制,并对应填充到有填充起始位对应的位中。如果没有复制结束位,则一直填充到数组结束。如果具有复制结束位,则当复制到复制结束位前一位停止复制填充。

同样的,如果copyWithin的参数为负数,它同样会加上数组长度以获得最终正确的索引位置。

类型化数组

类型化数组是具有特殊用途的数组,被设计用来处理数值类型数据。类型化数组的起源可以追溯到WebGL——Open GL ES 2.0的一个接口,设计用于配合网页上的<canvas>标签。类型化数组作为该接口实现的一部分,为JS提供了快速的按位运算能力。

对于WebGL的需求来说,JS的原生数学运算实在太慢了,因为它使用64位浮点数格式来存储数值,并在必要时将其转换为32位整数。引入类型化数组突破了格式限制并带来了更好的数学运算性能,其基本的设计概念是:单个数值可以被视为由位构成的数组,并且可以对其使用与JS数组现有方法类似的方法。

ES6采用了类型化数组,将其作为语言的一个正式部分,以确保在JS引擎之间有更好的兼容性,并确保与JS数组有更好的互操作性。

数值数据类型

JS使用IEEE 754标准格式存储,使用64位来存储一个数值的浮点数表示形式,该格式在JS中同时用来表示整数与浮点数:当值改变时,可能会频繁发生整数与浮点数之间的格式转换。而类型化数组则允许存储并操作八种不同的数值类型:

  • 8位有符号整数
  • 8位无符号整数
  • 16位有符号整数
  • 16位无符号整数
  • 32位有符号整数
  • 32位无符号整数
  • 32位浮点数
  • 64位浮点数

数组缓冲区

数组缓冲区是内存中包含一定数量字节的区域,而所有的类型化数组都基于数组缓冲区。床架你数组缓冲区类似于在C语言中使用malloc()来分配内存,而不需要指定这块内存包含什么。

创建方式

1
let buffer = new ArrayBuffer(10); // 分配了10个字节

调用ArrayBuffer构造器时,只需要传入单个数值用于指定缓冲区包含的字节数。创建完毕后可以通过检查对象的byteLength属性来获得缓冲区的字节数

1
console.log(buffer.byteLength); //10

此外可以使用slice方法来创建要给新的、包含已有缓冲区部分内容的数组缓冲区。这个方法可以使用起始位置与结束位置参数,返回由原缓冲区元素组成的一个新的ArrayBuffer实例,例:

1
2
3
let buffer = new ArrayBuffer(10); 
let buffer2 = buffer.slice(4,6);
console.log(buffer2.btyeLength); // 2

此代码创建了buffer2数组缓冲区,提取了原缓冲区索引值为4与5的元素,结束参数所对应的元素不会包含在结果中。

数组缓冲区总是保持创建时指定的字节数,你可以修改其内部的数据,但永远不能修改它的容量。

使用视图操作数组缓冲区

数组缓冲区代表一块内存区域,而试图则是你操作这块区域的接口。视图工作在数组缓冲区或其子集上,你可以读写某种数值类型的数据。DateView类型是数组缓冲区的通用视图,允许对前述所有八种数值数据类型进行操作。

1
2
let buffer = new ArrayBuffer(10);
let view = new DataView(buffer, 5, 2)

DataView类型的创建需要一个必须参数-缓冲区对象。此外还有两个可选参数-字节偏移量和所要包含的字节数。如果没有提供这两个参数,则从缓冲区开始到缓冲区结束。

获取视图信息

可以通过查询DataView类型对象的一下只读属性来获取试图的信息:

  • buffer:该试图所绑定的数组缓冲区
  • byteOffset:传给DataView构造器的第二个参数,及字节偏移量,默认值为0
  • byteLength:传给DataView构造器的第三个参数,默认值为该缓冲区的byteLength属性

读取与写入数据

对于JS所有八种数值数据类型,DataView视图的原型分别提供了在数组缓冲区上写入数据的一个方法和读取数据的一个方法。

例如操作int8或unint8的读取/写入方法:

1
2
3
4
getInt8(btyeOffset, littleEndian):从byteOffset处读取一个int8值
setInt8(btyeOffset, value, littleEndian):从byteOffset处写入一个int8值
getUInt8(btyeOffset, littleEndian):从byteOffset处读取一个无符号int8值
setUInt8(btyeOffset, value, littleEndian):从byteOffset处写入一个无符号int8值

littleEndian为一个布尔值,用来决定读写的值是否采用小端序。默认为false,及采用大端序。

将方法名中的8改成16或32,便可以用来处理16位或32位值。

而浮点数采用下列读写方法

1
2
3
4
getFloat32(byteOffset, littleEndian):从byteOffset处开始读取一个32位的浮点数;
setFloat32(byteOffset, value, littleEndian):从byteOffset 处开始写入一个32位的浮点数;
getFloat64(byteOffset, littleEndian):从byteOffset处开始读取一个64位的浮点数;
setFloat64(byteOffset, value, littleEndian):从byteOffset处开始写入一个64位的浮点数。

视图允许使用任意格式对任意位置进行读写,而无需考虑这些数据此前是使用什么格式存储的。例如可以向缓冲区写入两个int8值,将其作为一个int16值读取出来。

1
2
3
4
5
let buffer = new ArrayBuffer(2);
let view = new DataView(buffer);
view.setInt8(0, 5);
view.setInt8(1, -1);
console.log(view.getInt16(0)); //1535

类型化数组即为视图

ES6的类型化数组实际上就是针对数组缓冲区的特定类型视图,你可以使用这些数组对象来处理特定的数据类型,而不必使用通用的DataView对象

1

Uint8ClampedArray的特性与Unit8Array基本相同,只是当缓冲区包含的值小于1时,将值转换为0,超过255时,存储255.

类型化数组只能在特定的一种数据类型上工作,且类型化数组的元素可以使用数值型索引来访问,就像常规数组一样。

创建特定类型视图

第一种方式就是使用与创建DataView时相同的参数

第二种方式就是传递单个数值给类型化数组的构造器,此值表示该数组包含的元素数量。

1
2
3
4
5
6
let ints = new Int16Array(2),				
floats = new Float32Array(5);
console.log(ints.byteLength);// 4
console.log(ints.length) // 2
console.log(floats.byteLength);// 20
console.log(floats.length); // 5

第三种方式是向构造器传递单个对象参数,可以是下列四种对象之一:

  • 类型化数组:数组所有元素都会被复制到新的类型化数组中。例如,如果你传递一个int8 类型的数组给Int16Array构造器,这些int8的值会被复制到Int16数组中。新的类型化数组与原先的类型化数组会使用不同的数组缓冲区。
  • 可迭代对象:该对象的迭代器会被调用以便将数据插入到类型化数组中。如果其中包含 了不匹配视图类型的值,那么构造器就会抛出错误。
  • 数组:该数组的元素会被插入到新的类型化数组中。如果其中包含了不匹配视图类型的 值,那么构造器就会抛出错误。
  • 类数组对象:与传入数组的表现一致

类型化数组与常规数组的相似点

公共方法

类型化数组也拥有大量与常规数组等效的方法,你可以对类型化数组使用下列这些方法:

  • 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()方法。其中的区别是类型化数组的版本会返回类型化数组,而不返回常规数组。

类型化数组与常规数组的区别

二者最重要的区别就是类型化数组并不是常规数组,类型化数组并不是从Array对象派生 的,使用Array.isArray()去检测会返回false

行为差异

常规数组可以被伸展或是收缩,然而类型化数组却会始终保持自身大小不变。你可以对常规数组一个不存在的索引位置进行赋值,但在类型化数组上这么做则会被忽略

遗漏的方法

尽管类型化数组拥有常规数组的很多同名方法,但仍然缺少了几个数组方法,包括下列这些:

  • concat()
  • pop()
  • push()
  • shift()
  • splice()
  • unshift()

除了concat()方法之外,该列表中的其余方法都会改变数组的大小,而由于类型化数组的大小不可变,因此这些方法都不能作用于类型化数组

concat()方法不可用的原因则是: 连接两个类型化数组的结果是不确定的(特别是当它们处理的数据类型不同时),这种不确定情况原本就不应当使用类型化数组。

附加的方法

最后,类型化数组还有两个常规数组所不具备的方法:set()方法与subarray()方法。这 两个方法作用相反:set()方法从另一个数组中复制元素到当前的类型化数组,而subarray()方法则是将当前类型化数组的一部分提取为新的类型化数组。
set()方法接受一个数组参数(无论是类型化的还是常规的)、以及一个可选的偏移量参数,后者指示了从什么位置开始插入数据(默认值为0)。数组参数中的数据会被复制到目标类型化数组中,并会确保数据值有效

文章目录
  1. 1. 创建数组
    1. 1.1. Array.of()方法
    2. 1.2. Array.from()方法
      1. 1.2.1. 映射转换
      2. 1.2.2. 在可迭代对象上使用
  2. 2. 所有数组上的新方法
    1. 2.1. find()与findIndex()方法
    2. 2.2. fill()方法
    3. 2.3. copyWithin()方法
  3. 3. 类型化数组
    1. 3.1. 数值数据类型
    2. 3.2. 数组缓冲区
    3. 3.3. 使用视图操作数组缓冲区
      1. 3.3.1. 获取视图信息
      2. 3.3.2. 读取与写入数据
      3. 3.3.3. 类型化数组即为视图
      4. 3.3.4. 创建特定类型视图
  4. 4. 类型化数组与常规数组的相似点
    1. 4.1. 公共方法
    2. 4.2. 相同的迭代器
    3. 4.3. of()与from()方法
  5. 5. 类型化数组与常规数组的区别
    1. 5.1. 行为差异
    2. 5.2. 遗漏的方法
    3. 5.3. 附加的方法
|