第十章、改进的数组功能
1、创建数组
传统的创建数组的方法:调用Array构造函数和数组字面量语法。
将一个类数组(具有数值型索引和length属性的对象)转换为数组的方法:Array.of()和Array.from()
1、Array.of()方法
因为通过Array构造函数创建数组的时候,传参的类型与数量会影响数组的length:
传入一个数值型的值,则数组的length就是该值;
传入一个非数值型的值,则该值会成为数组的元素;
传入多个值,不管是数值型还是非数值型,都会变成数组的元素。
Array.of()方法就是用来解决这个问题,不管Array.of()的参数的类型和个数,Array.of()方法总是将所有的参数当做新建数组的元素。
let items = Array.of(1,2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
let items = Array.of(1);
console.log(items.length); // 1
console.log(items[0]); // 1
let items = Array.of("1");
console.log(items.length); // 1
console.log(items[0]); // "1"
备注:Array.of()方法不通过Symbol.species属性确定返回值的类型,它使用当前构造函数(也就是of方法中的this值)来确定
正确的返回数据的类型。
2、Array.from()方法
JavaScript对象不支持直接将非数组对象转换为数组对象。
传统方法:
function makeArray(likeArray){
return Array.prototype.slice.call(likeArray);
}
新方法:
function makeArray(likeArray){
return Array.from(likeArray);
}
Array.from()方法可接受可迭代对象或类数组对象作为第一个参数,返回Array的实例。
备注:Array.from()方法也是通过this来确定返回的数组的类型的。
①、映射转换
可以提供一个映射函数作为Array.from()的第二个参数,这个函数用来转换类数组中的元素值。
function translate(){
return Array.from(arguments,value => value + 1);
}
可以给Array.from()传入第三个参数,表示映射函数的this值。
let helper = {
differ: 1,
add(value){
return value + this.differ;
}
};
function translate(){
return Array.from(arguments,helper.add,helper);
}
②、用Array.from()转换可迭代对象
即Array.from()方法可以将所有含有Symbol.iterator属性的对象转换为数组。
let numbers = {
*[Symbol.iterator](){
yield 1;
yield 2;
yield 3;
}
};
let numbers2 = Array.from(numbers,value => value + 1);
console.log(numbers2); // 2,3,4
备注:如果一个对象既是类数组又是可迭代的,Array.from()方法会根据迭代器来决定转换哪个值。
2、为所有数组添加的新方法
1、find()方法和findIndex()方法
传统方法:
indexOf()方法和lastIndexOf()方法:在数组中查找特定的值。
ES6方法:
find()方法和findIndex()方法
参数:一个回调函数,一个可选参数用来指定回调函数中this的值。
回调函数的参数与forEach()方法的相同。
如果给定的值满足定义的标准,回调函数返回true;一旦回调函数返回true,find()方法和findIndex()方法都会立即停止搜索数组剩下的部分。
区别:
find()方法返回查找到的值;
findIndex()方法返回查找到的值得索引。
let numbers = [25,30,35,40,45];
console.log(numbers.find(n => n > 33)); // 35
console.log(numbers.findIndex(n => n > 33)); // 2
2、fill()方法
用指定的值填充一个或多个数组元素;当传入一个值时,fill()方法会用这个值重写数组中的所有值。
参数:第一个参数表示要填充的值;第二个参数表示开始索引;第三个参数表示结束索引。包前不包后,后两个参数可选。
let numbers = [1,2,3,4];
numbers.fill(1,2);
console.log(numbers.toString()); // 1,2,1,1
numbers.fill(0,1,2);
console.log(numbers.toString()); // 1,0,0,2
备注:如果开始索引和结束索引为负值,那么这些值会与数组的长度相加得到最终位置。
3、copyWithin()方法
复制数组的部分值再填充到数组。
参数:第一个参数指定开始填充值得索引位置;第二个参数指定开始复制值得索引位置。
第三个参数指定被重写元素的数量。包前不包后。
let numbers = [1,2,3,4];
numbers.copyWithin(2,0,1);
console.log(numbers.toString()); // 1,2,1,4
备注:如果参数为负值,那么这些值会与数组的长度相加得到最终位置。
3、定型数组
定型数组是一种用于处理数值类型数据的专用数组。可以为JavaScript提供快速的按位运算。
在JavaScript中,数字是以64位浮点数格式存储的,并按需转换为32位整数,因此速度很慢。
所谓定型数组,就是将任何数字转换为一个包含数字比特的数组。
1、数值数据类型
定型数组支持存储和操作以下8种不同的数值类型:
①、有符号的8位整数int8
②、无符号的8位整数uint8
③、有符号的16位整数int16
④、无符号的16位整数uint16
⑤、有符号的32位整数int32
⑥、无符号的32位整数uint32
⑦、32位浮点数float32
⑧、64位浮点数float64
使用这些数据,需要创建一个数组缓冲区存储这些数据。
2、数组缓冲区
数组缓冲区是一段可以包含特定数量字节的内存地址。创建数组缓冲区不需指明内存块包含的数据类型。
调用构造函数时传入缓冲区的比特数量即可。
let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); // 10
let buffer2 = buffer.slice(4,6); // slice()方法包前不包后
console.log(buffer2.byteLength); // 2
备注:数组缓冲区包含的实际数量创建时就已经确定,可以修改缓冲区内的数据,但是不能改变缓冲区的尺寸大小。
3、通过视图操作数组缓冲区
视图是用来操作内存的接口。视图可以操作数组缓冲区或缓冲区的子集,并按照其中一种数值型数据类型来读取和写入数据。
DataView类型是一种通用的数组缓冲区视图,支持所有八种数值型数据类型。
let buffer = new ArrayBuffer(10);
let view = new DataView(buffer,5,2);
视图view操作缓冲区buffer位于索引5和索引6的字节。第二个参数和第三个参数可选。
①、获取视图信息
可通过以下只读属性获取视图的信息:
buffer:视图绑定的数组缓冲区
byteOffset:DataView构造函数的第二个参数,默认为0,只有传入参数时才有值。
byteLength:DataView构造函数的第三个参数,默认是缓冲区的长度byteLength。
let buffer = new ArrayBuffer(10);
let view1 = new DataView(buffer);
let view2 = new DataView(buffer,5,2);
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2
由于两个视图都是基于相同的数组缓冲区创建的,因此他们具有相同的buffer属性。
②、读取和写入数据
用于读取和写入int8和uint8类型数据的方法:
getInt8(byteOffset,littleEndian):读取位于byteOffset后的int8类型的数据。
setInt8(byteOffset,value,littleEndian):在byteOffset处写入int8类型的数据。
getUint8(byteOffset,littleEndian):读取位于byteOffset后的uint8类型的数据。
setUint8(byteOffset,value,littleEndian):在byteOffset处写入uint8类型的数据。
类似的方法还有:
getInt16(byteOffset,littleEndian)、setInt16(byteOffset,value,littleEndian)、
getUint16(byteOffset,littleEndian)、setUint16(byteOffset,value,littleEndian)、
getInt32(byteOffset,littleEndian)、setInt32(byteOffset,value,littleEndian)、
getUint32(byteOffset,littleEndian)、setUint32(byteOffset,value,littleEndian)
getFloat32(byteOffset,littleEndian)、setFloat32(byteOffset,value,littleEndian)、
getFloat64(byteOffset,littleEndian)、setFloat64(byteOffset,value,littleEndian)
get方法接收两个参数:读取数据时的字节偏移量;一个可选的布尔值,表示是否按照小端排序进行读取。
set方法接收三个参数:写入数据时的字节偏移量;写入的值;一个可选的布尔值,表示是否按照小端排序进行写入。
小端排序:指最低有效字节位于字节0的字节顺序。
let buffer = new ArrayBuffer(2);
let view = new DataView(buffer);
view.setInt8(0,5);
view.setInt8(1,-1);
console.log(view.getInt8(0); // 5
console.log(view.getInt8(1); // -1
视图是独立的,无论数据之前是通过何种方式存储的,你都可以在任意时刻读取或写入任意格式的数据。
let buffer = new ArrayBuffer(2);
let view = new DataView(buffer);
view.setInt8(0,5);
view.setInt8(1,-1);
console.log(view.getInt16(0); // 1535
console.log(view.getInt8(0); // 5
console.log(view.getInt8(1); // -1
③、定型数组是视图
定型数组实际上是用于数组缓冲区的特定类型的视图。
8个特定类型的视图对应于8中不同数值型数据类型。
ES6中的特定类型视图:
构造函数名称 元素尺寸(字节) 说明
Int8Array 1 8位二进制补码有符号整数
Uint8Array 1 8位无符号整数
Uint8ClampedArray 1 8位无符号整数(强制类型转换)
Int16Array 2 16位二进制补码有符号整数
Uint16Array 2 16位无符号整数
Int32Array 4 32位二进制补码有符号整数
Uint32Array 4 32位无符号整数
Float32Array 4 32位IEEE浮点数
Float64Array 8 64位IEEE浮点数
Uint8ClampedArray与Uint8Array大致相同,区别是当数组缓冲区中的值如果小于0或大于255,Uint8ClampedArray会强制分别将其转换为0或255
④、创建特定类型的视图
第一种创建定型数组的方法:
let buffer = new ArrayBuffer(10);
let view1 = new Int8Array(buffer);
let view2 = new Int8Array(buffer,5,2);
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2
第二种创建定型数组的方法:
let ints = new Int16Array(2);
let floats = new Float32Array(5);
console.log(ints.byteLength); // 4
console.log(ints.length); // 2
console.let(floats.byteLength); // 20
console.let(floats.length); // 5
备注:调用定型数组的构造函数时如果不传参数,会按照传入0来处理,因此缓冲区没有分配到任何比特,因此创建的定型数组不能用来保存数据。
第三种创建定型数组的方法:
调用构造函数时,将以下任意对象作为唯一的参数传入:
* 一个定型数组:该数组中的每个元素会作为新的元素被复制到新的定型数组中。
* 一个可迭代对象:对象的迭代器被调用,通过检索所有条目来选取插入到定型数组的元素,如果所有元素都是不符合用于该视图类型的无效类型,构造函数将会抛出一个错误。
* 一个数组或一个类数组对象:数组中的元素会被复制到一个新的定型数组中,如果所有类型都是不适合该视图类型的无效类型,构造函数将会抛出错误。
let ints1 = new Int16Array([25,50]);
let ints2 = new Float32Array(ints1);
console.log(ints1.buffer === ints2.buffer); // false
console.log(ints1.byteLength); // 4
console.log(ints1.length); // 2
console.log(ints1[0]); // 25
console.log(ints1[1]); // 50
console.let(ints2.byteLength); // 8
console.let(ints2.length); // 2
console.log(ints2[0]); // 25
console.log(ints2[1]); // 50
备注:元素大小指的是每种定型数组中每个元素表示的字节数。该值存储在每个构造函数和每个实例的BYTES_PER_ELEMENT属性中。
4、定型数组与普通数组的相似之处
可以修改length属性来改变普通数组的大小,但定型数组的length属性是一个不可写属性,因此不能修改定型数组的大小。
1、通用方法
以下方法均可用于定型数组:
copyWithin()、findIndex()、lastIndexof()、slice()、entries()、forEach()、map()、some()、fill()、indexOf()、
reduce()、sort()、filter()、join()、reduceRight()、values()、find()、keys()、reverse()
定型数组中的方法会额外检查数值类型是否安全,也会通过Symbol.species确认方法的返回值是定型数组而不是普通数组。
let ints = new Int16Array([25,50]),
mapped = ints.map(v=>v*2);
console.log(mapped.length); // 2
console.log(mapped[0]); // 50
console.log(mapped[1]); // 100
console.log(mapped instanceof Int16Array); // true
2、相同的迭代器
定型数组与普通数组有3个相同的迭代器:entries()、keys()、values()
可以把定型数组当做普通数组一样来使用展开运算符、for-of循环
let ints = new Int16Array([25,50]);
let intsArray = [...ints];
console.log(intsArray instanceof Array); // true
console.log(intsArray[0]); // 25
console.log(intsArray[1]); // 50
展开运算符能够将可迭代对象转换为普通数组,也可以将定型数组转换为普通数组。
3、of()方法和from()方法
所有定型数组都含有静态of()方法和from()方法,运行效果与Array.of()方法和Array.from()方法相似。
区别:定型数组的方法返回定型数组,普通数组的方法返回普通数组。
5、定型数组与普通数组的差别
定型数组与普通数组的最重要的区别:定型数组不是普通数组。
定型数组不继承自Array,通过Array.isArray()方法检查定型数组返回的是false
1、行为差异
①、定型数组始终保持相同的尺寸,其length属性不可变。
给定型数组中不存在的索引赋值会被忽略,但在普通数组中是可以的。
②、定型数组同样会检查数据类型的合法性,0不能被用于代替所有非法值。
所有修改定型数组的方法都会受到此限制。
let ints = new Int16Array(['hi']);
console.log(ints.length);
console.log(ints[0]); // 0
2、缺失的方法
以下方法在定型数组中不存在:
concat()、shift()、pop()、splice()、push()、unshift()
3、附加方法
set():将其他数组复制到已有的定型数组
参数:一个是数组(定型数组和普通数组都支持);一个是可选的偏移量,表示开始插入数据的位置,默认为0.
let ints = new Int16Array(4);
ints.set([25,50]);
ints.set([75,100],2);
console.log(ints.toString()); // 25,50,75,100
subarray():提取已有定型数组的一部分作为一个新的定型数组
参数:一个是可选的开始位置;一个是可选的结束位置(不包含);最后返回一个新的定型数组。
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