Proxy
Proxy 意为 代理,通俗来说,就是在 目标数据对象 外围设置一层 拦截。
什么是拦截?
举个栗子:老板有一个仓库(对象),刚开始大家可以任意在其中存放货物(获取、修改、存放值)。后来老板觉得自己的仓库每次被使用得太随意了,就请了一个管家(拦截对象),所有人存取货物都必须经过管家之手。老板说:我制定一个拦截方案,那就是每次存取货物,管家都要大声把物品的信息念出来(console.log 一下)。
据上所得三个关键词:
仓库
管家
拦截方案
get(target, key, receiver)
下面用一段简单的代码来表述下:
// 原对象
let obj = {
name:'steve',
age:20
}
// data 是我们要拦截的 原对象
// dataProxy 是我们新生成的 拦截对象
let dataProxy = new Proxy(obj, {
get(target, key, receiver) {
// target 就是我们的原对象,obj
console.log("target >>> ", target === obj);
// key 就是操作的健
console.log("key >>> ", key);
// receiver 就是 dataProxy,拦截对象
console.log("receiver >>> ", receiver);
console.log(`当前值为:${target[key]}`);
return target[key];
},
});
// 使用拦截器
dataProxy.name;
// 打印结果:
// -> 当前值为:steve
// -> "steve"
然后,相应的关系就出来了:
- 仓库 ---
obj
- 管家 ---
dataProxy
- 拦截方案 ---
set(),get()
使用 proxy 实现 节点创建
在上面,我们已经学会了如何使用 proxy 实现对对象的拦截,接下来,我们用一个例子来说明 proxy 在实际项目中可以用来干些什么。
// 首先创建我们的 proxy 代理对象
const DOMR = new Proxy(
{},
{
get(target, key) {
// 每次访问 DOMR 的属性,都返回一个函数,该函数可以创建节点
// attr 要创建的元素的属性对象
// children 要创建的元素的内容
return function (attr = {}, ...children) {
const el = document.createElement(key);
// 遍历 属性对象,为元素添加属性
for (let key of Object.keys(attr)) {
el.setAttribute(key, attr[key]);
}
for (let child of children) {
// 遍历 内容,为元素添加 内容
if (typeof child === "string") {
child = document.createTextNode(child);
}
el.appendChild(child);
}
// 返回创建的元素
return el;
};
},
}
);
// 只要我们调用 DOMER.xxx 就创建一个 xxx 节点
let div = DOMR.div(
{ id: "div1", class: "aaa" },
// 只要我们调用 DOMER.xxx 就创建一个 xxx 节点
DOMR.a({ href: "https://www.baidu.com", class: "hello" }, "这是一个连接")
);
// 打印出节点
console.log(div);
// window.onload,必须等到页面内包括图片的所有元素加载完毕后才能执行。
window.onload = function () {
// 添加节点到页面上
document.body.appendChild(div);
};
set(target, key, value, receiver)
用来拦截对象的设置过程
let person = new Proxy(
{},
{
// target 原对象 {}
// key 操作的键
// value 被设置的值
// receiver 就是 person,拦截对象
set(target, key, value, receiver) {
if (key === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("年龄必须为整数!");
}
if (value > 100) {
throw new RangeError("年龄不能大于100!");
}
}
target[key] = value;
},
}
);
person.age = 20;
apply(target, context, args)
拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
let obj = {name:'obj'}
function say() {
console.log("hello world");
}
let newFn = new Proxy(say, {
// target 目标函数
// context 目标函数的上下文 this
// args 函数参数
apply(target, object, args) {
return 'hello'
},
});
// target:say
// context:obj
// args:[1,2,3]
newFn.call(obj,1,2,3)
// 打印结果:hello
// 并没有打印出 hello world,说明,原函数内部代码并不会执行,如需执行我们需要在 apply 中调用 say()
其它方法
-
has(target, key):拦截
key in proxy
的操作,返回一个布尔值。 -
deleteProperty(target, key):拦截
delete proxy[key]
的操作,返回一个布尔值。 -
ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 -
getOwnPropertyDescriptor(target, key):拦截
Object.getOwnPropertyDescriptor(proxy, key)
,返回属性的描述对象。 -
defineProperty(target, key, propDesc):拦截
Object.defineProperty(proxy, key, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 -
preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 -
getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 -
isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 -
setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 -
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
proxy 与 Object.defineproperty 的区别
Proxy
提供了13种拦截方法,包括拦截 constructor
、apply
、deleteProperty
等等,而 Object.defineProperty
只有 get
和 set
。
Reflect
reflect:反射,一般用来将语言内部的一些方法,直接暴露到这个对象上,供人们使用。比如 Function 的 apply,Object 的 设置抽成 set 等等。
Reflect.apply(func, context, args)
效果跟 Function 的 apply 很相似,可以用任意对象作为上下文调用函数
function show(...args) {
console.log(this);
console.log(args);
}
Reflect.apply(show, null, [1, 2, 3, 4]);
// 打印结果:
// window
// [1,2,3,4]
Reflect.set(target, name, value, receiver)
设置
target
对象的name
属性等于value
。
var myObject = {
foo: 1,
};
Reflect.set(myObject, "foo", 2);
myObject.foo; // 2
Proxy 与 Reflect 结合使用案例
实现数据的双向绑定
let vm = {
list: [1, 2, 3, 4]
}
let vmProxy = new Proxy(vm.list, {
set (target, prop, value) {
console.log(`Setting: ${value}`);
Reflect.set(target, prop, value);
return true;
}
})
vmProxy[0] = 3
// 打印结果:[3, 2, 3, 4]
console.log(vm.list)
如果有同学想要系统学习,请移步 ECMAScript 6 - Proxy。
参考文献
https://segmentfault.com/a/1190000015483195
https://www.jianshu.com/p/77eaaf34e732
https://juejin.cn/post/6844904088119853063
https://juejin.cn/post/6844904090116292616#heading-6
https://es6.ruanyifeng.com/#docs/proxy
https://my.oschina.net/u/4333555/blog/4329035
https://blog.csdn.net/XuM222222/article/details/98846376
https://blog.csdn.net/weixin_43574780/article/details/108042951
网友评论