一、Proxy
Proxy(代理):在目标对象之前架设一层拦截,可以对外界的访问进行改写
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
target:要拦截的目标对象
handler:用来定制拦截行为。handler可拦截的行为有十几种 ,这里只介绍最常用的几种
1. get 捕获器
作用:拦截对象属性的读取
js
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function (target, key) {
if (key in target) {
return target[key];
}else{
return '无结果'
}
}
});
console.log(proxy.name) //张三
console.log(proxy.age) //无结果
在此之前,当访问一个不存在的属性时,返回的结果为undefined。但我们可以使用Proxy的get进行改写,当没有结果时,返回一个无结果
2. set 捕获器
作用:拦截对象的属性赋值操作
js
let person = new Proxy({}, {
set: function (target, key, value) {
/* 如果年龄不是整数 */
if (key === 'age' && !Number.isInteger(value)) {
throw new TypeError('The age is not an integer')
}
target[key] = value
return true // 表示成功
}
})
person.age = 100
console.log(person.age) // 100
person.age = 'young' // 抛出异常: Uncaught TypeError: The age is not an integer
在set中,我们可以在赋值之前进行表单校验等操作
3. has 捕获器
作用:拦截判断target对象中是否含有某个属性的操作
js
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
has: function (target, key) {
console.log('正在进行in操作')
return key in target;
}
});
'name' in proxy
4. deleteProperty 捕获器
作用:拦截删除target对象属性的操作
js
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
deleteProperty : function (target, key) {
if (key === 'name') {
throw new Error(`name属性不能被删除`);
}
return delete target[key]
}
});
delete proxy.name
二、Reflect
Reflect 是一个内置对象,它提供了一系列用于操作对象的方法,不要直接实例化(new)使用。
Reflect 将 Object 对象的一些明显属于语言内部的方法(in、delete),放到Reflect对象上(Reflect.get、Reflect.set)。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。
与Proxy搭配使用简单示例:
js
let obj = {
a: 1,
b: 2
};
let proxy = new Proxy(obj, {
//receiver为代理后的对象
get(target, key, receiver) {
console.log("监听的key", key);
//等同于target[key],
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log("触发set");
//等同于target[key] = value;
Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key) {
console.log("监听删除");
//等同于 delete target[key]
Reflect.deleteProperty(target, key);
//如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
return true;
},
has(target, key) {
console.log("监听has");
//等同于 key in target
return Reflect.has(target, key);
},
});
proxy.a; //监听的key a
proxy.a = 4; //触发set
delete proxy.b; //监听删除
"a" in proxy; //监听has
三、Set
1. 特点
-
类似于数组,但成员的值都是唯一的
-
会比较类型,5不等于 ' 5 '
-
null,undefined,NaN不会过滤
-
本身是一个构造函数,需使用new进行实例化
2. 使用情景
- 数组去重
js
[...new Set([2, 3, 5, 4, 5, 2, 2])] // [2 3 5 4]
- 字符串去重
js
[...new Set('ababbc')].join('') // "abc"
3. 属性和方法
add:增加
delete:删除
has:判断是否存在
clear:清除所有成员
size:返回成员总数
js
let s = new Set()
s.add(1).add(2).add(2) //{1,2}
s.delete(2) //{1}
s.has(1) //true
s.clear() //{}
s.size //0 注意size是属性,不是方法的调用
4. 遍历
js
let set = new Set([1, 2, 3]);
//使用forEach进行遍历
set.forEach((value) => console.log(value))
// 1
// 2
// 3
四、Map
1. 特点
-
类似于对象,但属性不限于字符串
-
只有引用地址一样,Map结构才将其视为同一个键
js
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
上面看起来是同一个键,但['a']指向不同的内存地址,所有取值为undefined。应将数组先赋值给变量
js
const map = new Map();
const a = ["a"]
map.set(a, 555);
map.get(a) // 555
2. 属性和方法
set:添加
get:获取key对应的键值
delete:删除
has:判断是否存在
clear:清除所有成员
size:返回成员总数
js
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
m.size //0
3. 遍历
可以通过forEach和for...of两种方式遍历Map数据结构,获取key与对应的value
js
//注意:value在前
map.forEach(function (value, key) {
console.log(key, value);
});
for (let o of map) {
console.log(o) //[key,value]
}
map.keys():返回键名的遍历器
map.values():返回键值的遍历器
五、WeakMap
1. 类似于Map,但键名只能是对象,不接受其他类型的值作为键名
js
const map = new WeakMap();
map.set(1, 2) // Uncaught TypeError: Invalid value used as weak map key
2. 键名所指向的对象,不计入垃圾回收机制
先看Map存储的数据结构,新建map.js
js
function usageSize() {
//获取当前进程的堆内存使用量
const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
global.gc(); //手动清内存,需配置启动参数 --expose-gc
console.log(usageSize()); // ≈ 3.15M
let arr = new Array(10 * 1024 * 1024);
const map = new Map();
map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 83.29M
arr = null;
global.gc();
console.log(usageSize()); // ≈ 83.29M
运行node --expose-gc map.js,得到打印结果,最后两次打印结果相似,说明即使arr为null,但Map中的{arr:1}并没有被内存回收。
如果arr想被垃圾回收,我们还需手动清除Map中的arr,修改map.js
js
//在arr = null前新增
map.delete(arr)
global.gc();
console.log(usageSize()); // ≈ 83.29M
//... arr = null;
运行node --expose-gc map.js,得到打印结果
js
3.15M
83.29M
83.29M
3.29M //arr占用的内存被释放
也可使用WeakMap来保存,新建weakMap.js
js
function usageSize() {
//获取当前进程的堆内存使用量
const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
global.gc(); //手动清内存,需配置启动参数 --expose-gc
console.log(usageSize()); // ≈ 3.15M
let arr = new Array(10 * 1024 * 1024);
const map = new WeakMap();
map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 83.29M
arr = null;
global.gc();
console.log(usageSize()); // ≈ 3.29M
运行node --expose-gc weakMap.js,得到打印结果,可以看到arr为null时,arr所占内存全部被释放。
正由于WeakMap的弱引用,WeakMap 的 key 是不可枚举的。因为key可能会随时被回收
3. 弱引用的只是键名,而不是键值
修改weakMap.js,将arr保存到value上
js
function usageSize() {
//获取当前进程的堆内存使用量
const used = process.memoryUsage().heapUsed;
return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}
global.gc(); //手动清内存,需配置启动参数 --expose-gc
console.log(usageSize()); // ≈ 3.15M
let arr = new Array(10 * 1024 * 1024);
const map = new WeakMap();
let key = {key:1}
//将arr保存到value上
map.set(key, arr);
global.gc();
console.log(usageSize()); // ≈ 83.29M
arr = null;
console.log(map.get(key)) // <10485760 empty items> 依旧能取到值
global.gc();
console.log(usageSize()); // ≈ 83.3M
运行node --expose-gc weakMap.js,根据打印结果可以看到arr为null时,map.get依旧能取到值,内存也没有被释放。说明弱引用的只是键名,而不是键值。
六、结尾
参考文章: