前言
在集合(Set)中,我们的关注点放在集合(Set)的每个值本身,集合(Set)以[值,值]的形式存储元素,而在字典中,是用[键,值]对的形式存储数据。字典也称作映射,符号表或者关联数组。
散列表(也叫HashTable类 或 HashMap类),是字典的一种散列表实现方式。JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键,因此ES2015 带来了Map类和Map类的弱化版本WeakMap类。
集合、散列表与字典都是用来存储唯一值(不重复的值)的数据结构。
字典(Dictionary类)
定义
在字典中,是用[键,值]对的形式存储数据。字典也称作映射,符号表或者关联数组。
实际中的字典
一个实际的字典(单词和它们的释义)
地址簿、通讯录
代码实现
字典在理想状态下,使用字符串作为键名,值可以是任何数据类型,所以需要一个方法把所有键名转换为字符串。使字典类更容易搜索或获取值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
|
在字典中,为了方便后续延伸字典类,字典中的值需要把原始的key和value都保存到字典中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
|
有了它们,便可以着手实现最终的字典类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
export class Dictionary { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; }
set(key, value) { if (key != null && value != null) { const tableKey = this.toStrFn(key); this.table[tableKey] = new ValuePair(key, value); return true; } return false; }
get(key) { const valuePair = this.table[this.toStrFn(key)]; return valuePair === null ? undefined : valuePair.value; }
hasKey(key) { return this.table[this.toStrFn(key)] != null; }
remove(key) { if (this.hasKey(key)) { delete this.table[this.toStrFn(key)]; return true; } return false; }
values() { return this.keyValues().map(valuePair => valuePair.value); }
keys() { return this.keyValues().map(valuePair => valuePair.key); }
keyValues() { return Object.values(this.table); }
forEach(callbackFn) { const valuePairs = this.keyValues(); for (let i = 0; i < valuePairs.length; i++) { const result = callbackFn(valuePairs[i].key, valuePairs[i].value); if (result === false) { break; } } }
isEmpty() { return this.size() === 0; }
size() { return Object.keys(this.table).length; }
clear() { this.table = {}; }
toString() { if (this.isEmpty()) { return ''; } const valuePairs = this.keyValues(); let objString = `${valuePairs[0].toString()}`; for (let i = 1; i < valuePairs.length; i++) { objString = `${objString},${valuePairs[i].toString()}`; } return objString; } }
|
体验Dictionary类
创建字典类实例,并传入三个键值对
1 2 3 4
| const dictionary = new Dictionary(); dictionary.set('Gandalf', 'gandalf@email.com'); dictionary.set('John', 'johnsnow@email.com'); dictionary.set('Tyrion', 'tyrion@email.com');
|
查找 Gandalf 是否在字典类实例中
1
| console.log(dictionary.hasKey('Gandalf'));
|
获取字典类实例的大小
1
| console.log(dictionary.size());
|
获取字典类实例的所有key
1
| console.log(dictionary.keys());
|
获取字典类实例的所有value
1
| console.log(dictionary.values());
|
获取字典类实例中的Tyrion
1
| console.log(dictionary.get('Tyrion'));
|
删除字典类实例中的John,并查看是否已经删除。
1 2
| dictionary.remove('John'); console.log(dictionary.keyValues());
|
forEach循环迭代字典类实例
1 2 3 4 5 6
| dictionary.forEach((k, v) => { console.log('forEach: ', `key: ${k}, value: ${v}`); });
|
散列表(又称哈希表、HashTable、HashMap)
定义
散列表(也叫HashTable类 或 HashMap类),是字典的一种散列表实现方式。JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键,因此ES2015 带来了Map类和Map类的弱化版本WeakMap类。
散列表在实际中应用的场景也很多:
- 因为它是字典的一种实现,所以可以用作关联数组
- JavaScript的对象(Object),本质上是键值对的集合(Hash结构),JavaScript 语言内部就是使用散列表来表示每个对象。
- 在数据库(如 MySQL、Microsoft SQL Server、 Oracle,等等)中进行索引,散列表可以用来保存键和对表中记录的引用
为什么要用散列表
散列表,是根据键值对而直接进行访问的数据结构。
也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射过程中使用的算法叫做散列算法,存放记录的数组叫做散列表。
散列表几个基本概念
装填因子
装填因子:a=n/m 其中n 为key个数,m为散列表的大小。
装填因子表示散列表的填充程度。
- 装填因子越大,填满的元素越多,空间利用率高,发生散列冲突的可能性更大了。
- 装填因子越小,填满的元素越少,发生散列冲突的可能性更小,但是空间利用率低了。
在冲突的可能性和空间的利用率之间需要寻找合适的平衡。
散列算法
散列算法的作用是尽可能快地在数据结构中找到一个值。散列算法是用于将一个数据转换为固定长度数值的函数。
笔记中用到的两个散列算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
loseloseHashCode = (key) => { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; }
djb2HashCode(key) { const tableKey = this.toStrFn(key); let hash = 5381; for (let i = 0; i < tableKey.length; i++) { hash = (hash * 33) + tableKey.charCodeAt(i); } return hash % 1013; }
|
散列冲突
多个元素通过散列算法计算得到的散列值相同,此时保存重复散列值的数据,会覆盖掉老的相同散列值的数据。使用一个数据结构来保存数据的目的显然不是丢失这些数据,而是通过某种方法将它们全部 保存起来。因此,常规处理散列冲突有几种方法:
后续会介绍前两种散列冲突解决方法
基础散列表
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
export class Dictionary { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; }
loseloseHashCode = (key) => { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; }
hashCode(key) { return this.loseloseHashCode(key); }
put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); this.table[position] = new ValuePair(key, value); return true; } return false; }
get(key) { const valuePair = this.table[this.hashCode(key)]; return valuePair === null ? undefined : valuePair.value; }
remove(key) { const hash = this.hashCode(key); const valuePair = this.table[hash]; if (valuePair !== null) { delete this.table[hash]; return true; } return false; }
getTable() { return this.table; }
isEmpty() { return this.size() === 0; }
size() { return Object.keys(this.table).length; }
clear() { this.table = {}; }
toString() { if (this.isEmpty()) { return ''; } const keys = Object.keys(this.table); let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`; for (let i = 1; i < keys.length; i++) { objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`; } return objString; } }
|
使用基础散列表实例
创建一个散列表,传入三个键值对
1 2 3 4 5 6 7 8 9 10 11 12 13
| const hash = new HashTable(); hash.put('Gandalf', 'gandalf@email.com'); hash.put('John', 'johnsnow@email.com'); hash.put('Tyrion', 'tyrion@email.com'); console.log(hash.hashCode('Gandalf') + ' - Gandalf'); console.log(hash.hashCode('John') + ' - John'); console.log(hash.hashCode('Tyrion') + ' - Tyrion');
|
测试散列表的部分方法
1 2 3 4 5
| console.log(hash.get('Gandalf')); console.log(hash.get('Loiane'));
hash.remove('Gandalf'); console.log(hash.get('Gandalf'));
|
测试散列表的散列冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
const hash1 = new HashTable(); hash.put('Ygritte', 'ygritte@email.com'); hash.put('Jonathan', 'jonathan@email.com'); hash.put('Jamie', 'jamie@email.com'); hash.put('Jack', 'jack@email.com'); hash.put('Jasmine', 'jasmine@email.com'); hash.put('Jake', 'jake@email.com'); hash.put('Nathan', 'nathan@email.com'); hash.put('Athelstan', 'athelstan@email.com'); hash.put('Sue', 'sue@email.com'); hash.put('Aethelwulf', 'aethelwulf@email.com'); hash.put('Sargeras', 'sargeras@email.com');
console.log(hashTable.toString())
|
使用分离链接法解决散列冲突
介绍
为散列表的每一个位置创建一个链表并将元素存储在里面。它是解决散列冲突的最简单的方法,但是在 散列表 实例之外还需要占用额外的存储空间。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
| import { LinkedList } from '/LinkedList/app.js';
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
export class HashTableSeparateChaining { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; }
loseloseHashCode = (key) => { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; }
hashCode(key) { return this.loseloseHashCode(key); }
put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if (this.table[position] === null) { this.table[position] = new LinkedList(); } this.table[position].push(new ValuePair(key, value)); return true; } return false; }
get(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList !== null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { return current.element.value; } current = current.next; } } return undefined;
}
remove(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList != null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { linkedList.remove(current.element); if (linkedList.isEmpty()) { delete this.table[position]; } return true; } current = current.next; } } return false; }
getTable() { return this.table; }
isEmpty() { return this.size() === 0; }
size() { return Object.keys(this.table).length; }
clear() { this.table = {}; }
toString() { if (this.isEmpty()) { return ''; } const keys = Object.keys(this.table); let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`; for (let i = 1; i < keys.length; i++) { objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`; } return objString; } }
|
使用 线性探查法解决散列冲突
介绍
线性探查之所以称作线性,是因为它处理冲突的方法是将元素直接存储到表中,而不是在单独的数据结构中。当想向表中某个位置添加一个新元素的时候,如果散列值的位置已经被占据了,就尝试寻找散列值的下一个位置,如果仍然被占据,则再往下找下一个位置,以此类推,直到找到空闲位置,填充新值。
使用线性探查移除元素会导致散列表出现异常,所以需要对移除的元素作处理,通常使用的方法为以下两种:
- 软删除
- 删除后检查并挪动删除副作用影响的键值对元素
线性探查(软删除法)
介绍
使用一个删除标记来标记键值对被删除了,而不是真的从散列表中删除它,但是会随着删除次数的增加,散列表冗余了许多无用的软删除标记,逐渐降低散列表的效率。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
export class HashTableLinearProbingLazy { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; }
loseloseHashCode = (key) => { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; }
djb2HashCode(key) { const tableKey = this.toStrFn(key); let hash = 5381; for (let i = 0; i < tableKey.length; i++) { hash = (hash * 33) + tableKey.charCodeAt(i); } return hash % 1013; }
hashCode(key) { return this.loseloseHashCode(key); }
put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if (this.table[position] == null || (this.table[position] != null && this.table[position].isDeleted)) { this.table[position] = new ValuePair(key, value); } else { let index = position + 1; while (this.table[index] !== null && !this.table[position].isDeleted) { index++; } this.table[index] = new ValuePair(key, value); } return true; } return false; }
get(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key && !this.table[position].isDeleted) { return this.table[position].value; } let index = position + 1; while (this.table[index] != null && (this.table[index].key !== key || this.table[index].isDeleted)) { if (this.table[index].key === key && this.table[index].isDeleted) { return undefined; } index++; } if ( this.table[index] != null && this.table[index].key === key && !this.table[index].isDeleted ) { return this.table[position].value; } } return undefined; }
remove(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key && !this.table[position].isDeleted) { this.table[position].isDeleted = true; return true; } let index = position + 1; while ( this.table[index] != null && (this.table[index].key !== key || this.table[index].isDeleted) ) { index++; } if ( this.table[index] != null && this.table[index].key === key && !this.table[index].isDeleted ) { this.table[position].isDeleted = true; return true; } } return false; }
verifyRemoveSideEffect(key, removedPosition) { const hash = this.hashCode(key); let index = removedPosition + 1; while (this.table[index] != null) { const posHash = this.hashCode(this.table[index].key); if (posHash <= hash || posHash <= removedPosition) { this.table[removedPosition] = this.table[index]; delete this.table[index]; removedPosition = index; } index++; } }
getTable() { return this.table; }
isEmpty() { return this.size() === 0; }
size() { return Object.keys(this.table).length; }
clear() { this.table = {}; }
toString() { if (this.isEmpty()) { return ''; } const keys = Object.keys(this.table); let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`; for (let i = 1; i < keys.length; i++) { objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`; } return objString; } }
|
线性探查(挪动删除副作用影响的键值对元素)
介绍
真正的把元素从散列表中删除,随后对它后续的所有元素进行判断是否需要移动到删除后空闲的位置,随着散列表的大小增加,存在跟数组类似的挪动元素成本。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
| import { LinkedList } from '/LinkedList/app.js';
export function defaultToString(item) { if (item === null) { return 'NULL'; } if (item === undefined) { return 'UNDEFINED'; } if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); }
export class ValuePair { constructor(key, value) { this.key = key; this.value = value; }
toString() { return `[#${this.key}: ${this.value}]`; } }
export class HashTableSeparateChaining { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; }
loseloseHashCode = (key) => { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; }
hashCode(key) { return this.loseloseHashCode(key); }
put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if (this.table[position] === null) { this.table[position] = new LinkedList(); } this.table[position].push(new ValuePair(key, value)); return true; } return false; }
get(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList !== null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { return current.element.value; } current = current.next; } } return undefined;
}
remove(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList != null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { linkedList.remove(current.element); if (linkedList.isEmpty()) { delete this.table[position]; } return true; } current = current.next; } } return false; }
getTable() { return this.table; }
isEmpty() { return this.size() === 0; }
size() { return Object.keys(this.table).length; }
clear() { this.table = {}; }
toString() { if (this.isEmpty()) { return ''; } const keys = Object.keys(this.table); let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`; for (let i = 1; i < keys.length; i++) { objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`; } return objString; } }
|
ES2015 Map类
ECMAScript 2015 新增了 Map 类。可以基于 ES2015 的 Map 类开发我们的 Dictionary 类。
1 2 3 4 5 6 7 8 9 10 11 12
| const map = new Map(); map.set('Gandalf', 'gandalf@email.com'); map.set('John', 'johnsnow@email.com'); map.set('Tyrion', 'tyrion@email.com'); console.log(map.has('Gandalf')); console.log(map.size); console.log(map.keys()); console.log(map.values()); "tyrion@email.com"} console.log(map.get('Tyrion')); map.delete('John'); map.clear();
|
跟先前开发的字典类,不同点在于ES2015 的 Map 类的 values 方法和 keys 方法都返回 Iterator(第 3 章提到过),而不是值或键构成的数组。
ES2015 WeakMap 类和 WeakSet 类
WeakSet 和 WeakMap 是 Set 和 Map 的弱化版本
主要区别在于:
- WeakSet 或 WeakMap 类没有 entries、keys 和 values 等方法;
- 只能用对象作为键。
1 2 3 4 5 6 7 8 9 10
| const map = new WeakMap(); const ob1 = { name: 'Gandalf' }; const ob2 = { name: 'John' }; const ob3 = { name: 'Tyrion' }; map.set(ob1, 'gandalf@email.com'); map.set(ob2, 'johnsnow@email.com'); map.set(ob3, 'tyrion@email.com'); console.log(map.has(ob1)); console.log(map.get(ob3)); map.delete(ob2);
|
由于WeakSet 或 WeakMap 类没有强引用的键,能够提升JavaScript垃圾回收的性能,并且因为没有迭代器方法,在不知道键的情况下,无法取出值。
(可以使用WeakMap 类封装 ES2015 类的私有属性)