title: JavaScript高级程序设计第四版学习–第六章
date: 2021-5-18 22:29:20
author: Xilong88
tags: JavaScript
本章内容
对象
数组与定型数组
Map 、WeakMap 、Set 以及WeakSet 类型
可能出现的面试题:
1.数组的操作的程序题
2.了解过定型数组吗?
3.了解过映射吗?了解过弱映射吗?两者差别
4.了解过集合吗?了解过弱集合吗?两者差别
5.集合和映射的差别?
6.集合映射的程序题
7.对象和映射的内存,性能问题
总结:
主要内容是简单介绍对象,介绍数组和定型数组,介绍映射和集合,这些知识都比较常用,感觉不仅仅工作中会用到,做算法也会用到很多,所以要对操作很熟悉。
知识点:
1.创建对象:
(1)通过new + Object构造函数
let person = new Object();
person.name = "Nicholas";
person.age = 29;
(2)通过对象字面量
let person = {
name: "Nicholas",
age: 29
};
//属性名可以是字符串或数值,数值会自动转化为字符串
//通过字面量方法构造不会调用Object的构造函数
(3)用对象当参数时,一般把必选参数用命名参数,对象参数用来做为可选参数的封装集合。
属性访问可以用 对象名.属性名,也可以对象名[属性名],假如属性名称有空格,只能用后者
2.Array
创建数组:
(1)通过new+构造函数:
let colors = new Array();
(2)通过构造函数加数组大小:
let colors = new Array(20);
(3)通过构造函数加字面量元素:
let colors = new Array("red", "blue", "green");
(4)以上的new 省略效果一样
(5) 直接用数组字面量:
let colors = ["red", "blue", "green"]; // 创建一个包含3个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含2个元素的数组
(6)Array.from()和Array.of()可以创建数组:
// 字符串会被拆分为单字符数组
console.log(Array.from("Matt")); // ["M", "a", "t", "t"]
// 可以使用from()将集合和映射转换为一个新数组
const m = new Map().set(1, 2)
.set(3, 4);
const s = new Set().add(1)
.add(2)
.add(3)
.add(4);
console.log(Array.from(m)); // [[1, 2], [3, 4]]
console.log(Array.from(s)); // [1, 2, 3, 4]
// Array.from()对现有数组执行浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1);
console.log(a1); // [1, 2, 3, 4]
alert(a1 === a2); // false
// 可以使用任何可迭代对象
const iter = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
yield 4;
}
};
console.log(Array.from(iter)); // [1, 2, 3, 4]
// arguments对象可以被轻松地转换为数组
function getArgsArray() {
return Array.from(arguments);
}
console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4]
// from()也能转换带有必要属性的自定义对象
const arrayLikeObject = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4
};
console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]
form的第二个参数是一个方法,用于处理每一个迭代元素:
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
console.log(a2); // [1, 4, 9, 16]
console.log(a3); // [1, 4, 9, 16]
原本说第三参数可以改变映射函数this的指向,但是我尝试了不行,网上也没查到资料,这里后面再研究研究。
Array.of() 可以把一组参数转换为数组。这个方法用于替代在ES6之
前常用的Array.prototype.slice.call(arguments) ,一种异常笨
拙的将arguments 对象转换为数组的写法:
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined)); // [undefined]
数组里可以存在空位,如:
const options = [,,,,,]; // 创建包含5个元素的数组
console.log(options.length); // 5
console.log(options); // [,,,,,]
ES6新增方法普遍将这些空位当成存在的元素,只不过值
为undefined
const options = [1,,,,5];
for (const option of options) {
console.log(option === undefined);
}
// false
// true
// true
// true
// false
const a = Array.from([,,,]); // 使用ES6的Array.from()创建的包含3个空位的数组
for (const val of a) {
alert(val === undefined);
}
// true
// true
// true
alert(Array.of(...[,,,])); // [undefined, undefined, undefined]
for (const [index, value] of options.entries()) {
alert(value);
}
// 1
// undefined
// undefined
// undefined
map方法会跳过空位,join会把空位看成空字符串
const options = [1,,,,5];
// map()会跳过空位置
console.log(options.map(() => 6)); // [6, undefined, undefined, undefined, 6]
// join()视空位置为空字符串
console.log(options.join('-')); // "1----5"
由于行为不一致和存在性能隐患,因此实践中要避免使用
数组空位。如果确实需要空位,则可以显式地用undefined 值代
替。
以下方式可以在数组的最后添加元素:
colors[colors.length] = xxx;
数组最多可以包含4 294 967 295个元素
越界会报undefined
通过修改length的大小可以删除数组后的元素
Array.isArray()方法检测一个对象是不是数组,因为不同执行上下文中的
value instanceof Array
结果可能不同,isArray其实检测了对象原型的构造函数中有没有Array字符串。
keys() 、values() 和entries()
const a = ["foo", "bar", "baz", "qux"];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys); // [0, 1, 2, 3]
console.log(aValues); // ["foo", "bar", "baz", "qux"]
console.log(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]
fill方法可以复制:
const zeroes = [0, 0, 0, 0, 0];
// 用5填充整个数组
zeroes.fill(5);
console.log(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(0); // 重置
// 用6填充索引大于等于3的元素
zeroes.fill(6, 3);
console.log(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(0); // 重置
// 用7填充索引大于等于1且小于3的元素
zeroes.fill(7, 1, 3);
console.log(zeroes); // [0, 7, 7, 0, 0];
zeroes.fill(0); // 重置
// 用8填充索引大于等于1且小于4的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
console.log(zeroes); // [0, 8, 8, 8, 0];
fill() 静默忽略超出数组边界、零长度及方向相反的索引范围:
const zeroes = [0, 0, 0, 0, 0];
// 索引过低,忽略
zeroes.fill(1, -10, -6);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引过高,忽略
zeroes.fill(1, 10, 15);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引反向,忽略
zeroes.fill(2, 4, 2);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes); // [0, 0, 0, 4, 4]
copyWithin()
let ints,
reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 从ints中复制索引0开始的内容,插入到索引5开始的位置
// 在源索引或目标索引到达数组边界时停止
ints.copyWithin(5);
console.log(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
reset();
// 从ints中复制索引5开始的内容,插入到索引0开始的位置
ints.copyWithin(0, 5);
console.log(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
reset();
// 从ints中复制索引0开始到索引3结束的内容
// 插入到索引4开始的位置
ints.copyWithin(4, 0, 3);
alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// JavaScript引擎在插值前会完整复制范围内的值
// 因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6);
alert(ints); // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// 支持负索引值,与fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3);
alert(ints); // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]
copyWithin() 静默忽略超出数组边界、零长度及方向相反的索引范
围:
let ints,
reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引过低,忽略
ints.copyWithin(1, -15, -12);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引反向,忽略
ints.copyWithin(2, 4, 2);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
alert(ints); // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];
转换方法:
toLocaleString() 、toString() 和
valueOf() 方法。
let colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组
alert(colors.toString()); // red,blue,green
alert(colors.valueOf()); // red,blue,green
alert(colors); // red,blue,green
toLocaleString()
为了得到最终的字符串,会调用数组每个值的toLocaleString() 方
法,而不是toString() 方法
let person1 = {
toLocaleString() {
return "Nikolaos";
},
toString() {
return "Nicholas";
}
};
let person2 = {
toLocaleString() {
return "Grigorios";
},
toString() {
return "Greg";
}
};
let people = [person1, person2];
alert(people); // Nicholas,Greg
alert(people.toString()); // Nicholas,Greg
alert(people.toLocaleString()); // Nikolaos,Grigorios
join() 方法接收一个参数,即字符串分隔符,返回包含所有项的
字符串。来看下面的例子:
let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
栈方法:
push返回改变后元素个数
pop返回弹出的元素
队列方法:
shift,push,unshift,pop
shift会返回弹出的元素
unshift可以从队首插入元素,返回元素个数
排序:
reverse可以反向排列元素
sort不带参数按照字符的编码顺序排列
如:5在15后面,因为5的编码大于1的编码
sort可以传入一个函数,对数组元素进行比较,如果返回值为负表示a在b前,正表示a在b后,
0表示相等。
let values = [0, 1, 5, 10, 15];
values.sort((a, b) => b - a);
alert(values); // 15,10,5,1,0
sort和reverse返回数组的引用
let colors = ["red", "green", "blue"];
let newColors = ["black", "brown"];
let moreNewColors = {
[Symbol.isConcatSpreadable]: true,
length: 2,
0: "pink",
1: "cyan"
};
newColors[Symbol.isConcatSpreadable] = false;
// 强制不打平数组
let colors2 = colors.concat("yellow", newColors);
// 强制打平类数组对象
let colors3 = colors.concat(moreNewColors);
console.log(colors); // ["red", "green", "blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", ["black", "brown"]]
console.log(colors3); // ["red", "green", "blue", "pink", "cyan"]
所谓打平,就是展开,单个填入,不是整体填入
slice方法:
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
alert(colors2); // green,blue,yellow,purple
alert(colors3); // green,blue,yellow
传入负数会加上length,越界自动转化为最近边界
返回切下来的值,原数组不变
splice()
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,只有一个元素的数组
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置1插入两个元素
alert(colors); // green,yellow,orange,blue
alert(removed); // 空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,只有一个元素的数组
返回被删除的元素
indexOf()
、lastIndexOf() 和includes()
比较是用 === 比较,
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
alert(numbers.indexOf(4)); // 3
alert(numbers.lastIndexOf(4)); // 5
alert(numbers.includes(4)); // true
alert(numbers.indexOf(4, 4)); // 5
alert(numbers.lastIndexOf(4, 4)); // 3
alert(numbers.includes(4, 7)); // false
let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];
let morePeople = [person];
alert(people.indexOf(person)); // -1
alert(morePeople.indexOf(person)); // 0
alert(people.includes(person)); // false
alert(morePeople.includes(person)); // true
第二个参数是从第几位开始找,下标
find和findIndex,传入断言,断言函数接收3个参数:元素、索引和数组本身:
const people = [
{
name: "Matt",
age: 27
},
{
name: "Nicholas",
age: 29
}
];
alert(people.find((element, index, array) => element.age < 28));
// {name: "Matt", age: 27}
alert(people.findIndex((element, index, array) => element.age < 28));
// 0
找到匹配项后,这两个方法都不再继续搜索。
迭代:
every() :对数组每一项都运行传入的函数,如果对每一项函数都
返回true ,则这个方法返回true 。filter() :对数组每一项都运行传入的函数,函数返回true 的项
会组成数组之后返回。forEach() :对数组每一项都运行传入的函数,没有返回值。
map() :对数组每一项都运行传入的函数,返回由每次函数调用的
结果构成的数组。some() :对数组每一项都运行传入的函数,如果有一项函数返回
true ,则这个方法返回true 。这些方法都不改变调用它们的数组。
同样传入断言
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
alert(everyResult); // false
let someResult = numbers.some((item, index, array) => item > 2);
alert(someResult); // true
map会对每个元素进行操作,返回操作后的数组,不改变原数组
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
alert(mapResult); // 2,4,6,8,10,8,6,4,2
forEach和map类似,但是不返回,改变了原数组
reduce(),reduceRight()
四个参数:
上一个归并值、当前项、当前项的索引和数组本身
let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array) => prev + cur);
alert(sum); // 15
//只是从右边开始而已
let values = [1, 2, 3, 4, 5];
let sum = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); // 15
上一个归并值就是说return的值
3.定型数组,typed array
发明是为了解决WebGL和JavaScript的数据的传递,因为JavaScript的数据格式与其他语言有些不同,单独转换浪费算力,所以就发明了定型数组
ArrayBuffer
const buf = new ArrayBuffer(16); // 在内存中分配16字节
alert(buf.byteLength); // 16
ArrayBuffer 一经创建就不能再调整大小。不过,可以使用slice()
复制其全部或部分到一个新实例中:
const buf1 = new ArrayBuffer(16);
const buf2 = buf1.slice(4, 12);
alert(buf2.byteLength); // 8
ArrayBuffer 某种程度上类似于C++的malloc() ,但也有几个明显的
区别。
malloc() 在分配失败时会返回一个null 指针。ArrayBuffer 在
分配失败时会抛出错误。
malloc() 可以利用虚拟内存,因此最大可分配尺寸只受可寻址系
统内存限制。ArrayBuffer 分配的内存不能超过
Number.MAX_SAFE_INTEGER ( )字节。
malloc() 调用成功不会初始化实际的地址。声明ArrayBuffer 则
会将所有二进制位初始化为0。
通过malloc() 分配的堆内存除非调用free() 或程序退出,否则系
统不能再使用。而通过声明ArrayBuffer 分配的堆内存可以被当
成垃圾回收,不用手动释放。
DataView
const buf = new ArrayBuffer(16);
// DataView默认使用整个ArrayBuffer
const fullDataView = new DataView(buf);
alert(fullDataView.byteOffset); // 0
alert(fullDataView.byteLength); // 16
alert(fullDataView.buffer === buf); // true
// 构造函数接收一个可选的字节偏移量和字节长度
// byteOffset=0表示视图从缓冲起点开始
// byteLength=8限制视图为前8个字节
const firstHalfDataView = new DataView(buf, 0, 8);
alert(firstHalfDataView.byteOffset); // 0
alert(firstHalfDataView.byteLength); // 8
alert(firstHalfDataView.buffer === buf); // true
// 如果不指定,则DataView会使用剩余的缓冲
// byteOffset=8表示视图从缓冲的第9个字节开始
// byteLength未指定,默认为剩余缓冲
const secondHalfDataView = new DataView(buf, 8);
alert(secondHalfDataView.byteOffset); // 8
alert(secondHalfDataView.byteLength); // 8
alert(secondHalfDataView.buffer === buf); // true
相当于DataView就是用来读这块空间的
ElementType用来设置以什么格式去读空间和写空间
// 在内存中分配两个字节并声明一个DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 说明整个缓冲确实所有二进制位都是0
// 检查第一个和第二个字符
alert(view.getInt8(0)); // 0
alert(view.getInt8(1)); // 0
// 检查整个缓冲
alert(view.getInt16(0)); // 0
// 将整个缓冲都设置为1
// 255的二进制表示是11111111(2^8 - 1)
view.setUint8(0, 255);
// DataView会自动将数据转换为特定的ElementType
// 255的十六进制表示是0xFF
view.setUint8(1, 0xFF);
// 现在,缓冲里都是1了
// 如果把它当成二补数的有符号整数,则应该是-1
alert(view.getInt16(0)); // -1
字节序,就是DataView按什么顺序去读空间,有大端字节和小端字节两种顺序。
// 在内存中分配两个字节并声明一个DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 填充缓冲,让第一位和最后一位都是1
view.setUint8(0, 0x80); // 设置最左边的位等于1
view.setUint8(1, 0x01); // 设置最右边的位等于1
// 缓冲内容(为方便阅读,人为加了空格)
// 0x8 0x0 0x0 0x1
// 1000 0000 0000 0001
// 按大端字节序读取Uint16
// 0x80是高字节,0x01是低字节
// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
alert(view.getUint16(0)); // 32769
// 按小端字节序读取Uint16
// 0x01是高字节,0x80是低字节
// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
alert(view.getUint16(0, true)); // 384
// 按大端字节序写入Uint16
view.setUint16(0, 0x0004);
// 缓冲内容(为方便阅读,人为加了空格)
// 0x0 0x0 0x0 0x4
// 0000 0000 0000 0100
alert(view.getUint8(0)); // 0
alert(view.getUint8(1)); // 4
// 按小端字节序写入Uint16
view.setUint16(0, 0x0002, true);
// 缓冲内容(为方便阅读,人为加了空格)
// 0x0 0x2 0x0 0x0
// 0000 0010 0000 0000
alert(view.getUint8(0)); // 2
alert(view.getUint8(1)); // 0
边界情形
DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就
会抛出RangeError
const buf = new ArrayBuffer(6);
const view = new DataView(buf);
// 尝试读取部分超出缓冲范围的值
view.getInt32(4);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(8);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(-1);
// RangeError
// 尝试写入超出缓冲范围的值
view.setInt32(4, 123);
// RangeError
DataView 在写入缓冲里会尽最大努力把一个值转换为适当的类
型,后备为0。如果无法转换,则抛出错误:
const buf = new ArrayBuffer(1);
const view = new DataView(buf);
view.setInt8(0, 1.5);
alert(view.getInt8(0)); // 1
view.setInt8(0, [4]);
alert(view.getInt8(0)); // 4
view.setInt8(0, 'f');
alert(view.getInt8(0)); // 0
view.setInt8(0, Symbol());
// TypeError
定型数组就是和DataView类似的对ArrayBuffer的一种视图,内容很多,但是感觉不常用,以后要用的时候再细致学习。
4.Map 映射
创建映射:
const m = new Map();
使用:
// 使用嵌套数组初始化映射
const m1 = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
alert(m1.size); // 3
// 使用自定义迭代器初始化映射
const m2 = new Map({
[Symbol.iterator]: function*() {
yield ["key1", "val1"];
yield ["key2", "val2"];
yield ["key3", "val3"];
}
});
alert(m2.size); // 3
// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]);
alert(m3.has(undefined)); // true
alert(m3.get(undefined)); // undefined
set,get,has,delete,clear,进行操作
const m = new Map();
alert(m.has("firstName")); // false
alert(m.get("firstName")); // undefined
alert(m.size); // 0
m.set("firstName", "Matt")
.set("lastName", "Frisbie");
alert(m.has("firstName")); // true
alert(m.get("firstName")); // Matt
alert(m.size); // 2
m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // true
alert(m.size); // 1
m.clear(); // 清除这个映射实例中的所有键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // false
alert(m.size); // 0
set() 方法返回映射实例,因此可以把多个操作连缀起来,包括初始化
声明:
const m = new Map().set("key1", "val1");
m.set("key2", "val2")
.set("key3", "val3");
alert(m.size); // 3
NaN作为键,可以能有以下错误:
const m = new Map();
const a = 0/"", // NaN
b = 0/"", // NaN
pz = +0,
nz = -0;
alert(a === b); // false
alert(pz === nz); // true
m.set(a, "foo");
m.set(pz, "bar");
alert(m.get(b)); // foo
alert(m.get(nz)); // bar
map里的顺序和set顺序一样,迭代会用这种顺序迭代。
const m = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
alert(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
for (let pair of m[Symbol.iterator]()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。
当然,
const m1 = new Map([
["key1", "val1"]
]);
// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) {
key = "newKey";
alert(key); // newKey
alert(m1.get("key1")); // val1
}
const keyObj = {id: 1};
const m = new Map([
[keyObj, "val1"]
]);
// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) {
key.id = "newKey";
alert(key); // {id: "newKey"}
alert(m.get(keyObj)); // val1
}
alert(keyObj); // {id: "newKey"}
选择Object还是Map
内存占用:不同浏览器的情况不同,但给定固定大小的内存,Map 大约可
以比Object 多存储50%的键/值对。
插入性能:不过插入Map
在所有浏览器中一般会稍微快一点儿
如果代码涉及大量插入操
作,那么显然Map 的性能更佳
查找速度:从大型Object 和Map 中查找键/值对的性能差异极
小,但如果只包含少量键/值对,则Object 有时候速度更快。如果代码涉及大量查找操作,那么某些情况
下可能选择Object 更好一些
删除性能:而对大多数浏览器引擎来说,Map 的delete() 操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择Map 。
5.WeakMap,弱映射
创建:
const wm = new WeakMap();
弱映射中的键只能是Object 或者继承自Object 的类型,尝试使用非对
象设置键会抛出TypeError 。值的类型没有限制。
const key1 = {id: 1},
key2 = {id: 2},
key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
[key1, "val1"],
[key2, "val2"],
[key3, "val3"]
]);
alert(wm.get(key1)); // val1
alert(wm.get(key2)); // val2
alert(wm.get(key3)); // val3
// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([
[key1, "val1"],
["BADKEY", "val2"],
[key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof wm2;
// ReferenceError: wm2 is not defined
// 原始值可以先包装成对象再用作键
const stringKey = new String("key1");
const wm3 = new WeakMap([
stringKey, "val1"
]);
alert(wm3.get(stringKey)); // "val1"
操作和map差不多,但是所谓弱映射,就是说键是对象,对象没有被引用的时候,这个映射就会被回收
可以用一个代码块来保存这个引用,让映射不被回收
const wm = new WeakMap();
const container = {
key: {}
};
wm.set(container.key, "val");
function removeReference() {
container.key = null;
}
弱映射不可迭
弱映射的应用:
DOM节点元数据:
const m = new Map();
const loginButton = document.querySelector('#login');
// 给这个节点关联一些元数据
m.set(loginButton, {disabled: true});
dom节点删除后,映射可以自动被回收
6.Set ,是一种加强Map
创建:
const m = new Set();
// 使用数组初始化集合
const s1 = new Set(["val1", "val2", "val3"]);
alert(s1.size); // 3
// 使用自定义迭代器初始化集合
const s2 = new Set({
[Symbol.iterator]: function*() {
yield "val1";
yield "val2";
yield "val3";
}
});
alert(s2.size); // 3
可以使用add() 增加值,使用has() 查询,通过size 取
得元素数量,以及使用delete() 和clear() 删除元素:
const s = new Set();
alert(s.has("Matt")); // false
alert(s.size); // 0
s.add("Matt")
.add("Frisbie");
alert(s.has("Matt")); // true
alert(s.size); // 2
s.delete("Matt");
alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // true
alert(s.size); // 1
s.clear(); // 销毁集合实例中的所有值
alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // false
alert(s.size); // 0
add() 返回集合的实例
const s = new Set().add("val1");
s.add("val2")
.add("val3");
alert(s.size); // 3
相当于Set是没有键的Map,相当于键是元素本身。
7.WeakSet 若集合
弱集合中的值只能是Object 或者继承自Object 的类型,尝试使用非对
象设置值会抛出TypeError 。
和弱映射很类似,基本就是说,当传入的对象的引用切断后,会被回收。
和弱映射有相似作用,具体语法和集合很像。