# 6. 集合引用类型
# Object 类型
Object 是 ES 中最常用的类型之一,可以使用 Object() 构造函数创建,也可以使用简写:字面量写法。推荐使用对象的字面量语法。
let person = new Object(); // 等价于 let person = {}
peson.name = "zuo";
person.age = 29;
// 简写 对象字面量写法
let person = {
name : "zuo",
age: 20
}
// 访问对象属性 person.name 或 person["name"]
// [] 里面可以是变量。可以含空格 ES6 对象的属性可以使用变量
第 8 章 对象、类与面向对象编程会更加全面、深入的介绍 Object 类型。
# Array 类型
EMCAScript 中的数组 Array 是有序列表,和其他语言不同的是,它的每一项都可以保存任何数据类型。创建数组有 4 种方法:
- 使用 Arra() 构造函数创建 let arr = new Array(1, 2, 3) 或 new Array(100)
- 使用数组字面量 let arr = [1, 2, 3]
- Arrray.from() 将类似数组的对象转为数组,类似数组有字符串、set集合,可迭代(for...of)的对象等。
- Arrray.of() 将一组参数转换为数组。
// 创建一个数组
let colors = new Array();
// 也可以 let colors = Array(20); 创建长度为 20 的元素的数组
// 构造函数中可以赋值
let colors = new Array("red", "green", "yellow");
// 省略new的写法
let colors = Array(3); // 等价于 new Array(3);
let names = Array("zuo");
//字面量写法
let colors = ["red", "green", "yellow"];
let names = [];
let values = [1,2,];
// 类似最后加,的写法不要有,IE8及之前的版本会创建3个元素,1,2,undefined,而其他浏览器创建两个
let values = [,,,,,]; // 同上
// 数组索引从 0 开始,0 到 arr.length -1, arr[0] ~ arr[arr.length - 1]
let colors = ["red", "green", "yellow"];
colors[99] = "black";
alert(colors.length); // 数组长度会增长到100,中间填充undefined
// 手动修改colors.length = 104, 长度也会改变,中间填充undefined
Array.from 与 Array.of 是 ES6 新增的创建数组静态方法。这部分内容和 ES6 入门 有重复
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
// Array.from 第二个参数等价于在 转数组后,再执行数组的 map 操作
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']
// from ES6 入门
// Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于\uFFFF的 Unicode 字符,算作两个字符的问题
let message = "ab😊de";
message.length // 6
function countSymbols(string) {
return Array.from(string).length; // ["a", "b", "😊", "d", "e"]
}
countSymbols(message) // 5
// 更精简的写法
[...message].length // 5
// Array.of
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
// 类似于
function ArrayOf(){
return [].slice.call(arguments);
}
# 检测是否为数组
判断是否为数组一般有三种方法
- xx instanceof Array ,它有一个缺点。当网页含有多个 frame 框架,会有两个不同版本的 Array 构造函数,会出现问题。
- Object.prototype.toString.call(xx) 转换为 String 来判断数据类型。
- Array.isArray(xx) ES6 新增,它不用管数组是在哪个执行上下文,建议使用。
// 判断是否为数组,在进行后续操作
if (value instanceof Array) {
// 对数组执行某些操作
}
if (Object.prototype.toString.call(value) === '[Object Array]') {
//
}
Array.isArray(value) // 解决多个框架不同版本的Array构造函数问题
# Array 迭代器方法(遍历方法)
ES6 中,Array新增 3 个用于检索(遍历)数组内容的方法。keys() 是对数组索引(数组下标)的遍历、values() 是对键值的遍历,entries() 是对键值对的遍历。注意 keys()、values()、entries() 返回的是数组元素的迭代器(Array Iterator)。可以使用 for...of 遍历,或者先使用 Array.from 将他们转为真正的数组,再遍历。
[1,2,3,4].keys()
// Array Iterator {}
// __proto__: Array Iterator
// next: ƒ next()
// Symbol(Symbol.toStringTag): "Array Iterator"
// __proto__: Object
let arr = ['a', 'b']
const arrKeys = Array.from(arr.keys()) // [0, 1]
const arrValues = Array.from(arr.values()) // ["a", "b"]
const arrEntries = Array.from(arr.entries()) // [[0, "a"], [1, "b"]]
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
# Array 复制和填充方法
ES6 新增,Array.fill() 与 Array.copyWithin()
fill(value[, startIndex[, endIndex]])
填充 value 的内容从数组的 startIndex 开始 endIndex,如果没有传索引开始或结束,默认为全部。copyWithin(insertIndex[, startIndex[, endIndex]])
拷贝当前数组中 startIndex 到 endIndex 的内容,填充到当前数组 insertIndex 索引开始的位置,覆盖原数组内容。
这两个方法都会修改原数组内容,参数中对于索引过低、过高或反向的传参,均会忽略,不执行任何操作。
const zeroes = [0, 0, 0, 0, 0];
// Fill the entire array with 5
zeroes.fill(5);
alert(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(0); // reset
// Fill all indices >=3 with 6
zeroes.fill(6, 3);
alert(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(0); // reset
// Fill all indices >= 1 and < 3 with 7
zeroes.fill(7, 1, 3);
alert(zeroes); // [0, 7, 7, 0, 0];
zeroes.fill(0); // reset
// Fill all indices >=1 and < 4 with 8
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
alert(zeroes); // [0, 8, 8, 8, 0];
注意:如果 fill 填充的是引用类型,那么他的没一个元素都是同一个地址,修改一个,会修改所有。参考:Array.prototype.fill()填充引用类型值的问题 - dev-zuo 技术日常 (opens new window)
let ints,
reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// Copy the contents of ints beginning at index 0 to the values beginning at index 5.
// Stops when it reaches the end of the array either in the source
// indices or the destination indices.
ints.copyWithin(5);
alert(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
reset();
// Copy the contents of ints beginning at index 5 to the values beginning at index 0.
ints.copyWithin(0, 5);
alert(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
reset();
// Copy the contents of ints beginning at index 0 and ending at index 3 to values
// beginning at index 4.
ints.copyWithin(4, 0, 3);
alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// The JS engine will perform a full copy of the range of values before inserting,
// so there is no danger of overwrite during the copy.
ints.copyWithin(2, 0, 6);
alert(ints); // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// Support for negative indexing behaves identically to fill() in that negative
// indices are calculated relative to the end of the array
ints.copyWithin(-4, -7, -3);
alert(ints); // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]
# Array 转换方法
所有对象都有 toLocalString()、toString()、valueOf() 方法。数组的 valueOf() 返回数组本身。toString() 方法返回各个元素以逗号拼接的字符串。toLocalString() 会调用数组每个值的 toLocalString() 方法。
let colors = ["red", "blue", "green"];
colors.toString(); // "red,blue,green"
colors.valueOf()); // [red, blue, green] valueOf() 返回数组本身
// join()
alert(colors.join(",")); // red,blue,green
alert(colors.join("||")); // red||blue||green
TIP
如果数组中的某一项是 null 或 undefined,则在 join()、toLocalString()、toString() 和 valueOf() 返回的结果中会以空字符串表示。
# Array 栈方法
pop() 与 push(), 后进先出(LIFO,Last-In-First-Out)
let colors = [];
let count = colors.push("red","green"); // colors.push("a") 再末尾添加元素,返回数组长度
let item = colors.pop(); // 取出最后一项, 返回其值
alert(item); // "green"
alert(colors.length); // 1
# Array 队列方法
shift() 和 push(),先进先出(FIFO,First-In-First-Out),shift() 与 pop() 类似,但每次移除的是数组的首个元素。unshift()可以在数组前端添加任意个元素。
let colors = [];
let count = colors.push("red","green"); // 2
let item = colors.shift(); // 取出最前面一项, 返回其值
alert(item); // "red"
alert(colors.length); // 1
count = colors.unshift('a', 'b') // 返回数组长度 3
colors // ["a", "b", "green"]
# Array 排序方法
将数组反转可以使用 reverse(),排序使用sort(). 操作前需要注意,会直接改变原数组。
WARNING
sort() 在不传参数时,默认的情况下,会将数组的每一项转换为字符串在按从小到大的情况排序,排序数字时要特别注意
let values = [1, 2, 3, 4, 5];
values.reverse();
values // [5, 4, 3, 2, 1]
// sort() 在不传参数的情况下,会将数组的每一项转换为字符串在按从小到大的情况排序
let vals = [0, 1, 5, 10, 15];
vals.sort();
alert(vals); // 0, 1, 10, 15, 5 转换为字符串时 "5" > "10"
// 鉴于上面的bug,数组的排序sort可以自定义排序方式。需要传入对应的规则函数参数就
// value1 < value2 return -1 [value1, value2] 如果return 1; [value2,value1]
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
// 可以简写问 return value1 - value2
}
vals.sort(compare); // 0,1,5,10,15
// 如果逆序,将 compare里-1和1互换 或 return value2 - value1
// 使用 ES6 箭头函数可以简写为
vals.sort((x,y) => x - y)
实际应用:如果有一个数组,元素都是对象。需要根据对象的 date 属性,按先后顺序排序。就可以用到自定义排序的功能
# Array 操作方法(3个)
- concat() 合并多个数组, 字符串生成新数组, 不会影响原数组。注意:会将元素的没一项都扁平化。如果参数数 array 也会被展开。他是基于 Symbol.isConcatSpreadable 来决定是否扁平化数组,默认为 true,如果是 false,则不扁平化数组。
let colors = ["red", "green"];
let colors2 = colors.concat(); // 直接创建了一个副本。
// 如果单纯的复制数组 colors2 = colors 那如果colors值变化,colors2也会变。
let colors3 = colors.concat("blue", ["yellow", "black"]);
alert(colors3); // red,green,blue,yellow,black
let newColors = ["a", "b"]
newColors[Symbol.isConcatSpreadable] = false
colors.concat("c", newColors) // ["a", "b", "green", "c", ["a", "b"]]
- slice() 取数组的某个一部分, 生成新数组,不影响原数组
let colors= ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(); // 创建副本,同colors.concat();
let colors3 = colors.slice(1);
let colors4 = colors.slice(1, 4);
alert(colors3); // green,blue,yellow,purple
alert(colors4); // green,blue,yellow
// slice里面的数如果是负数,等价与加上其数据长度的值
// 包含5个元素的数组 slice(-2,-1) => 等价于 slice(3,4);
- splice() 删除、插入、替换数组元素,根据参数个数及值来执行对应的操作
let colors = ["red", "green", "blue"];
// 删除数组元素
let removed = colors.splice(0,1); // 从第0个元素开始移除1个元素
alert(removed); // red
alert(colors); // green,blue
// 插入数组元素
removed = colors.splice(1, 0, "yellow", "orange"); // 从位置1开始插入两项,删除0项
alert(colors); // green,yellow,orange,blue
alert(removed); // 空
// 替换数组元素
removed = colors.splice(1, 1, "red", "purple"); // 从位置1开始删除一项,再插入两项
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow
# Array 位置方法
- ES5 方法 indexOf(val[, startIndex]), lastIndexOf(val[, startIndex]),判断元素在数组中的位置, 有则返回索引 index,没有则返回 -1。他们比较时使用的是严格相等(===)
- ES6 方法 includes(val[, startIndex]) 返回 true 或 false,可以正确的判断 indexOf 不能判断的 NaN
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.indexOf(2) // 判断元素2在数组中的位置 1
numbers.indexOf("2") // 对于不存在的返回 -1
numbers.lastIndexOf(2) // 7
[1, 2, NaN].includes(NaN) // true
[1, 2, NaN].indexOf(NaN) // -1
- find()、findIndex() 第四版新增,翻译为断言函数。用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;}
) // 10
// 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;}
) // 2
// 这两个方法都可以接受第二个参数,用来绑定回调函数的 this 对象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
// 这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足,indexOf方法无法识别数组的NaN成员,但是 findIndex 方法可以借助 Object.is 方法做到。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
# Array 迭代方法
ES5定义了5个迭代方法,每个方法都接收两个参数,运行函数及作用域对象(this),IE9+支持
- 检测数组里的元素是否满足条件。every()、some() 返回Boolean值
- every() 对数组的每一项运行给定函数,函数对每一项都返回true,则返回true
- some() 对数组的每一项运行给定函数, 如果函数对数组的任一项返回的true,return true
- 只做遍历,不返回任何值
- forEach() 对数组的每一项运行给定函数, 不返回任何值,只做函数操作 类似于 for () { do something }
- 返回数组
- filter() 对数组的每一项运行给定函数, 返回该函数会返回true的项组成的数组
- map() 对数组的每一项运行给定函数, 返回每次函数调用结果组成的数组
let numbers = [1,2,3,4,5];
let isGreaterThan2 = function (item, index, array) {
return (item > 2)
};
let everyResult = numbers.every(isGreaterThan2);
let someResult = numbers.some(isGreaterThan2);
let filterResult = numbers.filter(isGreaterThan2);
alert(everyResult); // false 是否所有值都大于2
alert(someResult); // true 是否有一个值大于2
alert(filterResult); // [3,4,5] 返回所有大于2的项
let mapResult = numbers.map(function (item, index, array) {
return (item * 2);
});
alert(mapResult); // [2,3,6,8,10] 返回每个元素执行完*2后的数组
numbers.foreach(function(item, index, array) {
cosnole.log(item) // 依次打印数组的值
})
# Array 归并方法
ES5新增了两个缩小数组的方法 reduce() 和 reduceRight(), 会迭代数组的所有项,然后构建一个最终返回的值。reduce 从数组的第一项开始,逐个遍历到最后。reduceRight 则从数组的最后一项开始,向前遍历到第一项。
- 该方法接收两个参数:一个在每一项上调用的函数,和(可选的)作为归并基础的初始值。
- 第一个参数的函数,接收4个参数:前一个值;当前值;项的索引;数组对象;这个函数返回的任何值,都会作为第一个参数传个下一项。第一次迭代发生在数组的第二项上,第一个参数是第一项,第二个参数为数组的第二项。
// 一参的情况
let values = [1,2,3,4,5];
let sum = values.reduce(function(prev, cur, index, array) {
console.log(prev, cur, index)
return prev + cur;
});
// prev, cur, index
// 1 2 1
// 3 3 2
// 6 4 3
// 10 5 4
alert(sum); // 15
// 二参的情况,reduce二参为初识值,然后函数第一个参数为初识值,第二个参数为数组第一个元素,再依次遍历
let numbers = [15.5, 2.3, 1.1, 4.7];
// 四舍五入相加
numbers.reduce(function(prev, cur, index) {
console.log(prev, cur, index)
return prev + Math.round(cur);
}, 0)
// 0 15.5 0
// 16 2.3 1
// 18 1.1 2
// 19 4.7 3
// 计算数组中每个元素出现的次数
// MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let countedNames = names.reduce(function (allNames, name) {
if (name in allNames) {
allNames[name]++;
}
else {
allNames[name] = 1;
}
return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
# 定型数组(typed array)
typed array 是 ES 新增的结构,目的是提升向原生库传输数据的效率。JS 中并没有 "TypedArray" 类型,它指的是一种特殊的包含数值类型的数组。
typed array 历史
在 WebGL 的早起版本中,JS 数组与原生数组之间不匹配,出现了性能问题。JS 数组中的数值存在内存中使用的是双精度浮点格式。图形驱动程序 API 通常不需要以双精度浮点格式传递数值。每次 WebGL 与 JS 运行时之间传递数据时,WebGL 都要先把数组里的数值转换为合适的格式再操作,消耗了很多时间。
Mozilla 为了解决这个问题,实现了 CanvasFloatArray
,是 C 语言风格的浮点值数组。JS 可以使用这个类型分配、读取、写入数组。它可以直接传给底层图形驱动程序 API,也可以直接从底层获取到,最终 CanvasFloatArray
变成了 Float32Array
,也就是今天定型数组中可用的第一个 "类型"。
ArrayBuffer,Float32Array 实际上是一种 "视图",它允许 JS 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。ArrayBuffer 是一个普通的构造函数,可用于在内存中分配特定数量的字节空间。
// 在内存中分配 16 字节
const buf = new ArrayBuffer(16) // ArrayBuffer(16) {}
buf.byteLength // 16
let buf2 = buf.slice(4, 12) // 复制 4 ~ 12 索引内容
buf2.byteLength // 8
ArrayBuffer 创建后就不能再调整大小,可以使用 slice() 复制全部或部分内容到另一个新的实例中。它在某种程度上类似于 C++ 的 malloc(),在 JS 中它会有一些差异,比如分配失败时会直接报错,分配成功后会将所有的二进制位初始化为 0,分配的堆内存可以被当成垃圾回收,不用手动释放。
TIP
不能直接读写 ArrayBuffer 实例,需要通过视图对 ArrayBuffer 读写。视图有不同的类型,但引用的都是 ArrayBuffer 中存储的二进制数据。
# DataView 视图
DateView 是第一种允许读写 ArrayBuffer 的视图是 DataView。这个视图专门为文件 I/O 和 网络 I/O 设计。支持对缓冲数据的高度控制,相比其他类型的视图性能也差一些。
const buf = new ArrayBuffer(16);
// DataView default to use the entire ArrayBuffer
// DataView 默认使用整个 ArrayBuffer
const fullDataView = new DataView(buf);
alert(fullDataView.byteOffset); // 0
alert(fullDataView.byteLength); // 16
alert(fullDataView.buffer === buf); // true
// Constructor takes an optional byte offset and byte length
// byteOffset=0 begins the view at the start of the buffer 视图从缓冲起点开始
// byteLength=8 restricts the view to the first 8 bytes 限制视图为前 8 个字节
// 构造函数接收一个可选的字节偏移量和字节长度
const firstHalfDataView = new DataView(buf, 0, 8);
alert(firstHalfDataView.byteOffset); // 0
alert(firstHalfDataView.byteLength); // 8
alert(firstHalfDataView.buffer === buf); // true
// DataView will use the remainder of the buffer unless specified
// byteOffset=8 begins the view at the 9th byte of the buffer 视图从第 9 个字节开始
// byteLength default is the remainder of the buffer 未指定,默认为剩余缓冲
// 如果不指定,则 DataView 会使用后面的缓存区
const secondHalfDataView = new DataView(buf, 8);
alert(secondHalfDataView.byteOffset); // 8
alert(secondHalfDataView.byteLength); // 8
alert(secondHalfDataView.buffer === buf); // true
DataView 要读写 ArrayBuffer 还需要使用 ElementType 来实现 JS 的 Number 类型到缓冲区二进制格式的转换。例如
const buf = new ArrayBuffer(2)
const view = new DataView(buf)
// 第一个字符和第二个字符二进制位都是 0
view.getInt8(0) // 0
view.getInt8(1) // 0
// 整个缓冲区 为 0
view.getInt16(0) // 0
// 将整个缓冲区都设置为 1
view.setUint8(0, 255) // 11111111
view.setUint8(1, 0xFF) // 11111111
// 获取缓冲区内容
view.getInt16(0) // -1 // 11111111 被看成是负数的补码。
ElementType | 字节 | 说明 | 对应的 C 类型 | 值范围 |
---|---|---|---|---|
Int8 | 1 | 8 位有符号整数 | signed char | -128~127 |
Uint8 | 1 | 8 位无符号整数 | unsigned char | 0~255 |
Int16 | 2 | 16 位有符号整数 | short | -32768~32767 |
Uint16 | 2 | 16 位无符号整数 | unsigned short | 0~65535 |
Int32 | 4 | 32 位有符号整数 | int | 略 |
Uint32 | 4 | 32 位无符号整数 | unsigned int | 略 |
Float32 | 4 | 32 位 IEEE-754 浮点数 | float | 略 |
Float64 | 8 | 64 位 IEEE-754 浮点数 | double | 略 |
字节序,指的是一种字节顺序约定。DataView 支持两种约定:
- 大端字节序,也叫网络字节序,第一个字节:最高有效位。最后一个字节:最低有效位。
- 小端字节序,第一个字节:最低有效位,最后一个字节:最高有效位。
DataView 默认是大端字节序,接收一个可选的二参,设置为 true,则开启小端字节序
const buf = new ArrayBuffer(2)
const view = new DataView(buf)
view.setUint8(0, 0x80)
view.setUint8(1, 0x01)
// 16 进制 0x80 0x01
// 2 进制 1000 0000 0000 0001
// 0x80 是高字节,0x01 是低字节
// 按大端字节序取 Uint16 => 0x8001
view.getUint16(0) // 32769 = 2 ** 15 + 1
// 按小端字节序取 Uint16 => 0x0180
view.getUint16(0, true) // 384 = 2 ** 8 + 2 ** 7
// 按大端字节序写入 Uint16
view.setUint16(0, 0x0004)
view.getUint8(0) // 0
view.getUint8(1) // 4
// 按小端字节序写入 Uint16
view.setUint16(0, 0x0002, true)
view.getUint8(0) // 2
view.getUint8(1) // 0
边界情形,如果 index 超出缓冲区内容,会直接报错:RangeError
# 定型数组
定型数组是另一种形式的 ArrayBuffer 视图,它特定于一种 ElementType,且遵循原生的字节序,API更多,性能更高。创建定型数组的方法:
- 读取已有的缓冲,
new <ElementType>Array(new ArrayBuffer(字节数))
- 使用自有缓冲,
new <ElementType>Array(数组长度)
- 填充可迭代结构
new ElementType>Array(可迭代数组)
- 填充基于任意类型的定型数组
new ElementType>Array(定型数组)
<ElementType>.from()
<ElementType>.of()
// Creates a buffer of 12 bytes
const buf = new ArrayBuffer(12);
// Creates an Int32Array that references this buffer
// 1. 读取已有的缓冲
const ints = new Int32Array(buf);
// The typed array recognizes it needs 4 bytes per element,
// and therefore will have a length of 3
alert(ints.length); // 3
// Creates an Int32Array of length 6
// 2. 使用自有缓冲
const ints2 = new Int32Array(6);
// Each number uses 4 bytes, so the ArrayBuffer is 24 bytes
alert(ints2.length); // 6
// Like DataView, typed arrays have a reference to their associated buffer
alert(ints2.buffer.byteLength); // 24
// Creates an Int32Array containing [2, 4, 6, 8]
// 3. 填充可迭代结构
const ints3 = new Int32Array([2, 4, 6, 8]);
alert(ints3.length); // 4
alert(ints3.buffer.byteLength); // 16
alert(ints3[2]); // 6
// Creates an Int16Array with values copies from ints3
// 4. 填充基于任意类型的定型数组
const ints4 = new Int16Array(ints3);
// The new typed array allocates its own buffer, and each value
// is converted to its new representation at the same index
alert(ints4.length); // 4
alert(ints4.buffer.byteLength); // 8
alert(ints4[2]); // 6
// Creates an Int16Array from a normal array
// 5. <ElementType>.from()
const ints5 = Int16Array.from([3, 5, 7, 9]);
alert(ints5.length); // 4
alert(ints5.buffer.byteLength); // 8
alert(ints5[2]); // 7
// Creates a Float32Array from arguments
// 6. <ElementType>.of()
const floats = Float32Array.of(3.14, 2.718, 1.618);
alert(floats.length); // 3
alert(floats.buffer.byteLength); // 12
alert(floats[2]); // 1.6180000305175781
定型数组的构造函数和实例都有一个 BYTES_PER_ELEMENT,表示每个元素所占的字节数
alert(Int16Array.BYTES_PER_ELEMENT); // 2
alert(Int32Array.BYTES_PER_ELEMENT); // 4
const ints = new Int32Array(1),
floats = new Float64Array(1);
alert(ints.BYTES_PER_ELEMENT); // 4
alert(floats.BYTES_PER_ELEMENT); // 8
定型数组行为 ,定型数组与普通数组很相似。定型数组支持以下操作符、属性、和方法
[]
forEach()
、map()
、some()
、every()
、filter()
、keys()
、values()
、entries()
、fill()
、slice()
、reduce()
等
const ints = new Int16Array([1, 2, 3]);
const doubleints = ints.map(x => 2 * x);
alert(doubleints instanceof Int16Array); // true
[...ints] // [1, 2, 3]
合并、复制和修改定型数组,定型数组使用数组缓冲来存储数据,而数组缓冲无法调整大小。因此像 concat()、push() 等方法不适用于定型数组。它提供了两个新方法,用于快速向外或向内复制数据:set()
和 subarray()
// Create an int16 array of length 8
const container = new Int16Array(8);
// Copy in typed array into first four values
// Offset default to an index of 0
container.set(Int8Array.of(1, 2, 3, 4));
alert(container); // [1,2,3,4,0,0,0,0]
// Copy in normal array into last four values
// Offset of 4 means begin inserting at the index 4
container.set([5,6,7,8], 4);
alert(container); // [1,2,3,4,5,6,7,8]
// An overflow will throw an error
container.set([5,6,7,8], 7);
// RangeError
subarray() 执行和 set() 相反的操作
const source = Int16Array.of(2, 4, 6, 8);
// Copies the entire array into a new array of the same type
const fullCopy = source.subarray();
alert(fullCopy); // [2, 4, 6, 8]
// Copy the array from index 2 on
const halfCopy = source.subarray(2);
alert(halfCopy); // [6, 8]
// Copy the array from index 1 up until 3
const partialCopy = source.subarray(1, 3);
alert(partialCopy); // [4, 6]
上溢和下溢(underflow与overflow)
// Signed ints array of length 2
// Each index holds a 2's complement signed integer which can
// range from -128 (-1 * 2^7) to 127 (2^7 – 1)
const ints = new Int8Array(2);
// Unsigned ints array of length 2
// Each index holds an unsigned integer which can range from
// 0 to 255 (2^7 – 1)
const unsignedInts = new Uint8Array(2);
// Overflow bits will not spill into adjacent indices.
// The index only takes the least significant 8 bits
// 上溢不会影响相邻的索引,只会取最低有效位上的 8 位
unsignedInts[1] = 256; // 0x100
alert(unsignedInts); // [0, 0] 取 0x00
unsignedInts[1] = 511; // 0x1FF
alert(unsignedInts); // [0, 255] 取 0xFF
// Underflow bits will be converted to their unsigned equivalent.
// 0xFF is -1 as a 2's complement int (truncated to 8 bits),
// but is 255 as an unsigned int
// 下溢的位会被转换为其无符号的等价值 0xff -1的补码
unsignedInts[1] = -1 // 0xFF (truncated to 8 bits)
alert(unsignedInts); // [0, 255]
// Overflow in 2's complement occurs transparently.
// 0x80 is 128 in unsigned int but -128 in 2's complement int
ints[1] = 128; // 0x80
alert(ints); // [0, -128]
// Underflow in 2's complement occurs transparently.
// 0xFF is 255 in unsigned int but -1 in 2's complement int
ints[1] = 255; // 0xFF
alert(ints); // [0, -1]
除了 8 种元素类型,还有一种 "夹板" 数组类型:Uint8ClampedArray,不允许任何方向溢出,超过 255,即取 255,小于 0 取 0。它完全是 HTML5 Canvas 元素的历史遗存。除非真的做 canvas 开发相关,否则不要使用它。
const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256]);
alert(clampedInts); // [0, 0, 255, 255]
# Map、WeakMap、Set、WeakSet
ES6 新增了 Map、WeakMap、Set、WeakSet 4 种引用类型。由于 ES6 入门有对应的内容,笔记重复。这里就不讲基础语法了。基础语法参见:Set和Map数据结构 | ES6入门笔记 (opens new window)
# Map、WeakMap
Map 类似于 Object,是键值对的形式。Object 只能使用数值、字符串或符号作为键名,Map 可以使用任何 JS 数据类型作为键,包括对象 undefined 等
选择 Object 还是 Map? 选择 Object 还是 Map 只是个人偏好问题,影响不多。对于在乎内存和性能的开发者来说,对象和映射之间确实存在明显差异
- 内存占用,给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键值对。
- 插入性能,Map一般会稍快,如果涉及大量的插入操作,选择 Map
- 查找速度,基本差不多,但 Object 会有一些优化,选择 Object 可能会好点。
- 删除性能,Map 的 delete() 操作比插入、查找更快。如果涉及大量的删除操作,选择 Map
WeakMap 与 Map 的区别:
- WeakMap 只接受对象作为键名(null除外),不接受其他类型的值作为键名。
- WeakMap 中键值对任何时候都可能被销毁。如果键对象被销毁或设置为 null,这个键值对就会在垃圾回收期间被清理。
- WeakMap 不可迭代,没有clear() 方法。因为键值对随时可能被销毁。当 dom 节点作为键时,dom 节点销毁后,垃圾回收就可以立即释放其内存,减少内存开销。
# Set、WeakSet
Set 类似于数组,但成员都是唯一的,注意引用类型由于是地址,相同对象,不同的地址,也算是唯一,可以 转字符串之后再存入。
Set 与 WeakSet 的区别和 Map 与 WeakMap的区别基本一致。
# 迭代与扩展操作
即 for...of 和 ...,有四种原生集合类型内置了迭代器(Iterator): Array,所有定型数组,Map,Set。基本和数组的迭代、扩展一致,代码略!