写作不易,未经作者允许禁止以任何形式转载!
如果觉得文章不错,欢迎关注、点赞和分享!
持续分享技术博文,关注微信公众号 👉🏻 前端LeBron
掘金原文
WeakMap
前置知识[深入浅出]JavaScript GC 垃圾回收机制
什么是WeakMap?
WeakMap是key / value的组合,key只接受对象,不接受基本类型,value可以为任意类型。
方法
- set(key, value)
在WeakMap中设置一组关联对象,返回WeakMap对象
- get(key)
返回key的关联对象,不存在时返回undefined
- has(key)
根据是否有key关联对象,放回一个Boolean值
- delete(key)
移除key的关联对象,之后执行has(key)方法返回false
和Map有什么区别?
- Map的key / value,都可以是任意类型
- WeakMap的key只能是对象,value可以是任意类型
const name = "LeBron";
const person = {
name: "LeBron",
age: 21,
};
let wk = new WeakMap();
wk.set(person, "nice");
console.log(wk.get(person)); // nice
wk.set(name, 1); // TypeError: Invalid value used as weak map key
let map = new Map();
map.set(name, "JS");
map.set(person, "nice");
console.log(map.get(name)); // JS
console.log(map.get(person)); // nice
- Map的key / value 是可遍历的,因为它的 key / value 存放在一个数组中。
- WeakMap不存在存放 key / value 的数组,所以不可遍历。
const name = "LeBron";
const person = {
name: "LeBron",
age: 21,
};
let wk = new WeakMap();
wk.set(name, "JS");
wk.set(person, "nice");
console.log(wk.keys()); // TypeError: wk.keys is not a function
console.log(wk.values()); // TypeError: wk.values is not a function
console.log(wk.entries()); // TypeError: wk.entries is not a function
let map = new Map();
map.set(person, "nice");
console.log(map.keys()); // [Map Iterator] { 'LeBron', { name: 'LeBron', age: 21 } }
console.log(map.values()); // [Map Iterator] { 'JS', 'nice' }
console.log(map.entries()); // [Map Entries] {
// [ 'LeBron', 'JS' ],
// [ { name: 'LeBron', age: 21 }, 'nice' ]
// }
- Map对键进行强引用,即使键被标记为null了,由于Map的key / value数组还在引用,key不会被GC。
- WeakMap对key进行弱引用,在key被标记为null了以后。由于是弱引用,也不存在key / value数组引用,不影响key的GC。
- 以下程序需要手动GC 启动方法:
node --expose-gc xxx
Map
function memmorySizeLogger() {
global.gc();
const used = process.memoryUsage().heapUsed;
console.log((used / 1024 / 1024).toFixed(2) + "M");
}
memmorySizeLogger(); // 1.79M
let person = {
name: "LeBron",
age: 21,
tmp: new Array(5 * 1024 * 1024),
};
memmorySizeLogger(); // 41.96M
let map = new Map();
memmorySizeLogger(); // 41.96M
map.set(person, "nice");
memmorySizeLogger(); // 41.96M
person = null;
memmorySizeLogger(); // 41.96M person的内存没有被回收
如果想在这种情况下正常GC,标记为null前需先执行map.delete(person)
WeakMap
function memmorySizeLogger() {
global.gc();
const used = process.memoryUsage().heapUsed;
console.log((used / 1024 / 1024).toFixed(2) + "M");
}
memmorySizeLogger(); // 1.79M
let person = {
name: "LeBron",
age: 21,
tmp: new Array(5 * 1024 * 1024),
};
memmorySizeLogger(); // 41.96M
let wk = new WeakMap();
memmorySizeLogger(); // 41.96M
wk.set(person, "nice");
memmorySizeLogger(); // 41.96M
person = null;
memmorySizeLogger(); // 1.96M person的内存被回收
Why WeakMap?
在JS里的Map API共用两个数组(key、value),设置的key、value都会加到这两个数组的末尾,并对key产生引用。当从map取值时,需要遍历所有的key,然后通过索引从value数组中取出相应index的值。
缺点一:
赋值、搜索都是O(n)复杂度
缺点二:
使用Map容易出现内存泄漏,因为数组一直引用着每个key和value,导致无法正常GC。
WeakMap对key进行弱引用,不影响正常GC
- key被GC后失效
如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap
- 如果需要遍历 / 迭代,则需要使用Map
应用场景
保存DOM节点数据
let domData = new WeakMap();
let dom = document.getElementById("xxx");
const anyDomData = getDomData(dom);
domData.set(dom, anyDomData);
console.log(domData.get(dom));
dom.parentNode.removeChild(dom);
dom = null;
缓存相关数据
let cache = new WeakMap();
class HandleCache {
get(key) {
if (cache.has(key)) {
return cache.get(key);
} else {
return undefined;
}
}
set(key, value) {
cache.set(key, value)
}
delete(key){
cache.delete(key)
}
}
封装私有属性
let privateData = new WeakMap();
class Person{
constructor(name, age){
privateData.set(this,{name, age});
}
getData(){
return privateData.get(this);
}
}
WeakSet
前置知识[深入浅出]JavaScript GC 垃圾回收机制
什么是WeakSet
WeakSet对象是一些对象值的集合,并且其中的每个对象只能出现一次,在WeakSet集合中是唯一的
方法
- add(value)
在该WeakSet对象中添加一个新的元素value
- delete(value)
在该WeakSet对象中删除value这个元素后,has方法会返回false。
- has(value)
返回一个Boolean值,表示给定的value值是否存在这个WeakSet中
和Set有什么区别
- Set的value可以是任何值,WeakSet的值只能是对象
const name = "LeBron";
const age = 21;
const person = {
name: "LeBron",
age: 21,
};
const ws = new WeakSet();
const set = new Set();
set.add(name);
set.add(age);
set.add(person);
ws.add(person);
ws.add(name); // TypeError: Invalid value used in weak set
ws.add(age); // TypeError: Invalid value used in weak set
- Set是可遍历的,WeakSet不可遍历
- Set存在一个数组存放value的值,引用原对象,故可遍历
- WeakSet不存这样的数组,故不可遍历
const name = "LeBron";
const age = 21;
const person = {
name: "LeBron",
age: 21,
};
const ws = new WeakSet();
const set = new Set();
set.add(name);
set.add(age);
set.add(person);
console.log(set.values()); // { 'LeBron', 21, { name: 'LeBron', age: 21 } }
ws.add(person);
ws.add(name);
ws.add(age);
console.log(set.values()); // TypeError: ws.values is not a function
- Set影响GC,而WeakSet不影响
- 以下程序需要手动GC 启动方法:
node --expose-gc xxx
Set存在values数组,在原value指向null后,values数组仍对value的值存在强引用,影响正常GC
function memmorySizeLogger() {
global.gc();
const used = process.memoryUsage().heapUsed;
console.log((used / 1024 / 1024).toFixed(2) + "M");
}
memmorySizeLogger(); // 1.79M
let person = {
name: "LeBron",
age: 21,
tmp: new Array(5 * 1024 * 1024),
};
memmorySizeLogger(); // 41.96M
const set = new Set();
set.add(person);
memmorySizeLogger(); // 41.96M
person = null;
memmorySizeLogger(); // 41.96M
WeakSet不存在这样的数组,故不影响正常GC
function memmorySizeLogger() {
global.gc();
const used = process.memoryUsage().heapUsed;
console.log((used / 1024 / 1024).toFixed(2) + "M");
}
memmorySizeLogger(); // 1.79M
let person = {
name: "LeBron",
age: 21,
tmp: new Array(5 * 1024 * 1024),
};
memmorySizeLogger(); // 41.96M
const ws = new WeakSet();
ws.add(person);
memmorySizeLogger(); // 41.96M
person = null;
memmorySizeLogger(); // 1.96M
应用场景
检测循环引用
递归调用自身的函数需要一种通过跟踪哪些对象已被处理,来应对循环数据结构的方法
// 对 传入的subject对象 内部存储的所有内容执行回调
function execRecursively(fn, subject, _refs = null){
if(!_refs)
_refs = new WeakSet();
// 避免无限递归
if(_refs.has(subject))
return;
fn(subject);
if("object" === typeof subject){
_refs.add(subject);
for(let key in subject)
execRecursively(fn, subject[key], _refs);
}
}
const foo = {
foo: "Foo",
bar: {
bar: "Bar"
}
};
foo.bar.baz = foo; // 循环引用!
execRecursively(obj => console.log(obj), foo);
Reflect
Reflect译为反射,是一个内置的新的全局对象,它提供拦截JavaScript操作的方法。这些方法与Proxy handler的方法相同。Reflect不是一个函数对象,是静态的类似工具函数,类似Math,因此它是不可构造的
Reflect的静态方法
具体用法参考:Reflect MDN文档
Reflect.apply()
Reflect.construct()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.get()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.has()
Reflect.isExtensible()
Reflect.ownKeys()
Reflect.preventExtensions()
Reflect.set()
Reflect.setPrototypeOf()
这些方法与Proxy handler的方法的命名相同,其中的一些方法与Object的方法相同,尽管二者之间存在着某些细微的差别
有什么不同?
Reflect的静态方法进行相应操作会返回一个Boolean值
- 操作成功返回true
- 操作失败返回false
将常规的命令式操作转换成了函数式操作,编程方式增加了元编程。
- 例如delete、赋值、判断等
- 命令式操作失败一般会报错,而Reflect不会,返回一个Boolean值判断是否成功。
- 内置对象中已经存在了一些反射API,Reflect将他们聚合起来并进行了优化
什么是元编程?
- 元编程即对编程语言进行编程
- 例如Proxy对象可以进行代理,拦截get、set操作
- 而在程序中获取的是你编程后的值。
Reflect就是一种反射,调用的是处理过后的各内置对象上的方法
- 所以各内置对象的方法改变后,Reflect调用的方法也是改变了的
- 类似于封装了一层
Reflect的优点
- 优化命名空间
你会发现JS的内置反射方法散落在各处,Reflect将他们很好地组织了起来。
- 增强代码的健壮性
使用Reflect进行操作不容易抛出异常、线程阻塞,使代码更健壮地运行。
- 为什么不直接挂在Object上?
反射的对象不仅针对于Object,还可能针对函数
- 例如apply,调用Object.apply(myFunc)还是挺奇怪的
用一个单一的对象保存内置方法能够保证JavaScript代码其他对象的纯净性
- 这样要优于直接反射挂载到构造函数或者原形上
- 更优于直接使用全局变量,这样JS关键字将越来越多。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。