本章涉及:
了解ES6新增的数据结构 Set、WeakSet、Map、WeakMap
了解JS中的强应用和弱应用
了解entries结构
在ES6以前,JavaScript中就只有两种数据结构:数组
和 对象
。 准确的来说,对象
还不是一种数据结构,它的底层是hashTable实现的(不知道对不对,听别人说的)。
ES6 中 新增了4
中数据结构:Set
、WeakSet
、Map
和 WeakMap
。接下来,就依次的认识一下。
根据MDN作为依据
Set
Set
对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set
对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
Set
中没有索引值
set中值的唯一性
唯一性,就表示基本数据类型的值要相等,引用数据类型的地址要相等;
但是呢?还是有几个特别要注意的几个值:
- 针对
0
的处理
// +0 和 -0
console.log(+0 === -0) // true
const set = new Set()
set.add(+0)
set.add(-0)
console.log(set) // Set(1) {0}
- 针对
NaN
的处理
console.log(NaN === NaN) // false
const set = new Set()
set.add(NaN)
set.add(NaN)
console.log(set) // Set(1) {NaN}
在Set中也视为相等。
Set常见的方法和属性
constructor
: 接受无参或者一个迭代器的参数,创建一个Set实例。
Set.prototype.size
: 返回set中的元素个数。
Set.prototype.add(value)
: 在Set
对象尾部添加一个元素。返回该Set
对象。
Set.prototype.clear()
: 移除Set
对象内的所有元素,没有返回值。
Set.prototype.delete(value)
: 从set中删除和这个值相等的元素,返回boolean类型
Set.prototype.has(value)
: 判断set中是否存在某个元素,返回boolean类型
遍历
entries
: 保持跟Map返回的格式一样, key与value的值相同
forEach
: 遍历Set中的每个元素
keys
: 与values()
方法相同,返回一个新的迭代器对象。
values
: 与上面一样
代码演示
const arr = [4, 3, 2, 1, 5]
// constructor
const set = new Set(arr)
// size
console.log(set.size) // 5
// add
console.log(set.add(6)) // Set(6) { 4, 3, 2, 1, 5, 6 }
// delete
console.log(set.delete(1)) // true
// has
console.log(set.has(1)) // false
// clear
console.log(set.clear()) // undefined
遍历的话,就不敲了。
WeakSet
WeakSet
对象允许你将弱保持对象存储在一个集合中,也是内部不能重复。
上面概念中提到了一个 弱保持,怎么理解呢?
请看最下面的JavaScript中的强引用和弱引用。
WeakSet和Set的区别
- WeakSet中只能存放对象类型,不能存放基本数据类型;
- WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
- 不可以被枚举。
WeakSet的常见方法
WeakSet.prototype.add(value)
:添加某个元素,返回WeakSet对象本身;WeakSet.prototype.delete(value)
:从WeakSet中删除和这个值相等的元素,返回boolean类型;WeakSet.prototype.has(value)
:判断WeakSet中是否存在某个元素,返回boolean类型;
不能被遍历。
使用场景,很少很少(代码量少,可能没遇到过)。
Map
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。
在map以前,只要想着键值对的形式,肯定想到对象。对,不可反对。
那么Map
和对象
之间有什么区别呢?
对象的key值只能是字符串,以及后面新增的symbol类型。但是,Map支持所有类型。
对象处理对象作为key的场景
let obj1 = { name: 'copyer' }
let obj2 = {name: 'copyer'}
const a = {
[obj1]: 'aaa',
[obj2]: 'bbb'
}
console.log(a) // { '[object Object]': 'bbb' }
看出来,对象会先被转化为字符串[object Object]
, 字符串作为key值。
Map中的属性和方法
constructor
: 接受无参或者entries结构的参数,创建一个Set实例。(理解entries结构,看下面的理解entries结构)。
Map.prototype.size
: 返回 Map
对象中的键值对数量。
Map.prototype.clear()
: 移除 Map
对象中所有的键值对。
Map.prototype.delete(key)
:移除 Map
对象中指定的键值对,如果键值对存在,返回 true
,否则返回 false
。
Map.prototype.get(key)
: 返回与 key
关联的值,若不存在关联的值,则返回 undefined
。
Map.prototype.has(key)
: 返回一个布尔值,用来表明 Map
对象中是否存在与 key
关联的值。
Map.prototype.set(key, value)
: 在 Map
对象中设置与指定的键 key
关联的值 value
,并返回 Map
对象。
遍历
for...of..
keys
values
entries
forEach
代码演示
const map = new Map([["name", "copyer"]]);
// size
console.log(map.size); // 1
// set
map.set(1, "我是数字");
// get
console.log(map.get(1)); // 我是数字
// has
console.log(map.has(1)); // true
// delete
console.log(map.delete(1)); // true
// clear
// console.log(map.clear());
// forEach
map.forEach((value, key) => {
console.log(value, key) // copyer name
})
// for...of...
for (const [key, value] of map) {
console.log(key, value) // name copyer
}
Map
和对象
的比较
借鉴于MDN的对比
Map | Object | |
---|---|---|
意外的键 | Map 默认情况不包含任何键。只包含显式插入的键。 |
一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。 |
键的类型 | 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 |
一个 Object 的键必须是一个 String 或是 Symbol 。 |
键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 |
虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。 |
Size | Map 的键值对个数可以轻易地通过size 属性获取。 |
Object 的键值对个数只能手动计算. |
迭代 | Map 是可迭代的的,所以可以直接被迭代。 |
Object 没有实现 可迭代协议,所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象。 |
性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
序列化和解析 | 没有元素的序列化和解析的支持。 | 原生的由Object 到JSON的序列化支持,使用 JSON.stringify() 。原生的由 JSON 到Object 的解析支持,使用 JSON.parse() 。 |
其实通过上面的比较,map
大部分是优先于对象
的,但是在开发过程中,还是基本上都是使用对象(可能是开发的级别不够)
Map 和 数组的转化
const kvArray = [["key1", "value1"], ["key2", "value2"]];
// 使用常规的 Map 构造函数可以将一个二维键值对数组转换成一个 Map 对象
const myMap = new Map(kvArray);
myMap.get("key1"); // 返回值为 "value1"
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
console.log(Array.from(myMap)); // 输出和 kvArray 相同的数组
// 更简洁的方法来做如上同样的事情,使用展开运算符
console.log([...myMap]);
// 或者在键或者值的迭代器上使用 Array.from,进而得到只含有键或者值的数组
console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]
WeakMap
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
WeakMap和Map的区别
- WeakMap的key值只能是对象,不能其他类型;
- WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
- 不可以被枚举。
WeakMap常用的方法
constructor
: 接受无参或者entries结构的参数,创建一个Set实例。(理解entries结构,看下面的理解entries结构)。
WeakMap.prototype.delete(key)
:移除 WeakMap
对象中指定的键值对,如果键值对存在,返回 true
,否则返回 false
。
WeakMap.prototype.get(key)
: 返回与 key
关联的值,若不存在关联的值,则返回 undefined
。
WeakMap.prototype.has(key)
: 返回一个布尔值,用来表明 WeakMap
对象中是否存在与 key
关联的值。
WeakMap.prototype.set(key, value)
: 在 WeakMap
对象中设置与指定的键 key
关联的值 value
,并返回 Map
对象。
使用场景
在Vue3响应式拦截中,是使用了WeakMap。
理解JavaScript中的强引用和弱引用
JavaScript中的对象,只要没有被引用,就会被垃圾回收机制回收。
强引用
先看代码
let obj = { name: "copyer", age: 23 };
const newObj = obj;
obj = null;
console.log(obj); // null
console.log(newObj); // { name: 'copyer', age: 23 }
上面的代码应该很简单吧。简单一个对象obj,把obj的引用赋值给另外一个变量newObj, 然后清空obj,打印两者。那么内存的表现形式呢?为什么obj为null, newObj还有值的存在呢?
- 创建Obj对象,在heap中生成一个内存地址 0x100, stack中的obj的值:0x100
- 把引用赋值给newObj,那么它的值也是0x100
- 当执行
obj = null
, 只是断开了obj 与 heap内存的连接,并没有断开 newObj的连接,所以obj为null,但是newObj是有值的。
当内存中开辟的地址,只要又被其他所指向,就不会被垃圾回收机制所回收。这就是强引用。(强势的一面)
弱引用
从字面上来说,就是出于弱势的一面,即使指向某一个引用,但是垃圾回收机制看不起你,你太弱了,强行把内存收回掉。
WeakSet 和 WeakMap就是 弱引用。
WeakMap实例:
let obj = { name: "copyer", age: 23 };
let weakMap = new WeakMap([[obj, obj]])
console.log(weakMap.get(obj)) // { name: "copyer", age: 23 }
// 如果
obj = null
console.log(weakMap.get(obj)) // undefined
定义一个obj对象,在内存中会开辟一块地址,在WeakMap的传参中,是引用了obj内存中所指向的地址。
但是呢,obj = null
时, 断开了连接,按理说,还有其他的地方在引用,是不会被回收的。但是如果是弱引用,那么内存中的地址一样的会被回收,那么WeakMap参数引用
就没有指向了,所以是undefined。
总结
强引用:只要有引用指向,就不会被回收。
弱引用:即使有引用指向,也是会被回收的。(WeakSet 和 WeakMap是JS弱引用的体现)
理解entries结构
其实何为entries结构
, 调用一下 Object.entries()
方法就知道了。
const obj = {
name: 'copyer',
age: 23,
height: 180
}
console.log(Object.entries(obj)) // [ ['name', 'copyer'], ['age', 23], ['height', 180] ]
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组。(像一个二维数组)
其格式:
[ [key, value], [key, value] ... ]