Proxy实践
概述
Proxy是ES6推出的一个类,用于给对象架设一层拦截器,但凡要访问
或修改
对象上的值或属性,都必须经过这层拦截器, Proxy也叫代理器
, 它代理了对对象的操作。
Proxy
Proxy 用于创建一个对象的代理,从而实现操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
const p = new Proxy(target, handler)
参数详见:
target
:要使用 Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
的行为。
基础示例
在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37
,下面以此展示 get
handler 的使用场景。
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
console.log('c' in p, p.c); // false, 37
代理转发示例
代理会将所有应用到它的操作转发到这个源 target 对象上
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 37 操作已经被正确地转发
详见MDN文档
Proxy使用示例
<script>
const obj = {
name: "花花",
age: 18,
address: "陕西省西安市",
};
// 创建一个代理的对象进行属性的:增,读,删,等操作
const ObjProxy = new Proxy(obj, {
//set与get中传入的值说明
//1.target:原对象
//2.key:当前属性名
//3.newValue:修改的新值
//4.receiver:代理的这个对象
set: function (target, key, newValue, receiver) {
// 第一种写法:监听到某个属性名新值就赋值给原对象的某个属性名
// target[key] = newValue;
// 第二种写法:也是vue3源码的写法,利用Reflect.set来实现监听新值
Reflect.set(target, key, newValue);
console.log(`监听:监听到${target[key]}的值已经被修改成了`, newValue);
},
get: function (target, key, receiver) {
console.log(`监听:${key}属性被读取了`);
// 第一种写法
// return target[key];
// 第二种写法
return Reflect.get(target, key, receiver);
},
});
//对这个代理对象进行值修改
ObjProxy.name = "小红";
//读取原对象看看是否修改成功
console.log(obj);
</script>
Object.defineProperty和proxy的区别
两者常被用于数据拦截,其区别如下:
Object.defineProperty 是一个老方法,兼容性好,Proxy是新方法,有更好的性能和功能,但不兼容IE,也没有polyfill;
Object.defineProperty 是通过修改原对象上的属性来实现数据拦截, 而Proxy是在原对象上加一层拦截,并不会修改原对象;
Object.defineProperty 功能单一,如:无法监听数组变化等;Proxy功能强大,是对整个对象进行拦截;
Object.defineProperty的问题有三个:
- 不能监听数组变化,如:数组的 push、pop、shift、unshift、splice、sort、reverse等方法都不能触发set,Vue是对其变异方法进行了重写。
- 必须遍历对象的每个属性,并对其设置 set、get 方法;
- 当一个对象有深层嵌套时,必须逐层遍历所有属性,直到每个对象都调用到Object.defineProperty为止
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
...
})
})
Proxy优势
1.0> 支持数组,不需要对数组方法进行重载;
Proxy代理整个对象,省略了使用 Object.keys() 逐个遍历所有属性的过程;
Proxy只是代理原对象,并未修改原对象;因此在Proxy中可调用原对象本身的方法和属性;
let arr = [1,2,3]
let proxy = new Proxy(arr, {
get(target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver){
console.log('set', key, value)
return Reflect.get(target, key, value, receiver)
}
})
proxy.push(4)
// 打印内容
4 get push
4 get length
8 set 3 4
8 set length 4
2.0> 嵌套支持:Get里面递归调用proxy并返回
let obj = { a:[1, 2, 3], b:1 }
let handler = {
get(target, key, receiver) {
console.log('get', key)
if(typeof target[key] === 'object' && target[key] !== null)
return new Proxy(target[key], handler)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
proxy.b = 8
proxy.a.push(4)
Reflect是一个内置对象,提供拦截js的方法;即,通过Reflect可以直接调用某个对象上的属性或方法,同时 Reflect 具有功能强大的方法。
Polyfill 是一块代码,用来为旧浏览器提供它没有原生支持的较新的功能。