一、Proxy介绍
Proxy
:用于创建一个对象的代理,从而实现基本操作的拦截和自定义
二、Proxy使用
Proxy
为 构造函数,用来生成 Proxy
实例
var proxy = new Proxy(target, handler)
target
表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler
通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时的代理行为
参数 handler 属性
关于handler
拦截属性,有如下:
- get(target,propKey,receiver):拦截对象属性的读取
- set(target,propKey,value,receiver):拦截对象属性的设置
- has(target,propKey):拦截
propKey in proxy
的操作,返回一个布尔值 - deleteProperty(target,propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值 - ownKeys(target):拦截
Object.keys(proxy)
、for...in
等循环,返回一个数组 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
,返回一个布尔值 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
优先重点关注 get set deleteProperty 这三个操作
Reflect介绍
ES6`中操作对象而提供的新 `API`,若需要在`Proxy`内部调用对象的默认行为,建议使用`Reflect
基本特点:
- 只要
Proxy
对象具有的代理方法,Reflect
对象全部具有,以静态方法的形式存在 - 修改某些
Object
方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
) - 让
Object
操作都变成函数行为
get()
get(target, propKey, receiver)
接受三个参数,依次为目标对象、属性名和 proxy
实例本身
let person = {
name: "Guest"
};
let proxy = new Proxy(person, {
get: function(target, propKey, receiver) {
return Reflect.get(target, propKey, receiver)
// or
// return target[propKey]
}
});
proxy.name // "Guest"
get
能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
set()
set(target, propKey, value, receiver)
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy
实例本身
现定义一个对象 规定 年龄输入整数时才被赋值,访问无效属性时控制台提醒 code ads
const obj = { name: "张三", age: 18 };
const proxy = new Proxy(obj, {
get(target, prop) {
if (prop in target) {
return Reflect.get(target, prop);
} else {
console.error("字段不存在")
return undefined;
}
},
set(target, propKey, value, receiver) {
if (propKey === "age") {
if (typeof value === "number") {
return Reflect.set(target, propKey, value, receiver);
// or
// target[propKey] = value
// return true
} else {
console.error("年龄只能输入正整数");
return false;
}
} else {
return false;
}
}
});
proxy.age = 20;
console.log(proxy.age); // 20
proxy.age = "22";
console.log(proxy.age); // 20
console.log(proxy.test); // undefined
提醒:严格模式下,set
代理如果没有返回true
,就会报错
deleteProperty()
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
Reflect.deleteProperty(target,key)
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`无法删除私有属性`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 无法删除私有属性
取消代理
Proxy.revocable(target, handler);
三、Reflect使操作变得更加合理
一招制敌 细读下面例子
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // ???
把admin的原型指向userProxy,再访问admin.name属性
admin自身不具备name属性,顺着原型链往上找name,user对name的访问做了返回值,返回当前对象的name属性,也就是admin对象上面的name 。这样看 admin.name 值就是 admin._name
。
有个关键点就在于userProxy 代理的是user对象,所以 userProxy 中 get的第一个参数 target
的值实际上是user,细品一下,这里的this 依旧是指向的是user,而不是admin。
若需要在Proxy
内部调用对象的默认行为,建议使用Reflect
//html
<div>
普通对象操作:
<span id="common"></span>
</div>
<div>
Reflect函数操作:
<span id="reflect"></span>
</div>
//js
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop];
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
document.getElementById('common').innerHTML = admin.name
let user2 = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy2 = new Proxy(user2, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
let admin2 = {
__proto__: userProxy2,
_name: "Admin"
}
document.getElementById('reflect').innerHTML = admin2.name
//运行结果
普通对象操作: Guest
Reflect函数操作: Admin
四、使用场景
Proxy
其功能非常类似于设计模式中的代理模式,常用功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
使用 Proxy
保障数据类型的准确性
let data = { num: 0 };
data = new Proxy(data, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("属性只能是number类型");
}
return Reflect.set(target, key, value, proxy);
}
});
data.num = "foo"
// Error: 属性只能是number类型
data.num = 1
// 赋值成功
声明一个私有的 apiKey
,便于 api
这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey
let api = {
_apiKey: 'kafakakafaka',
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, receiver) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可访问.`);
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可修改`);
}
return Reflect.set(target, key, value, receiver);
}
});
console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都抛出错误
还能通过使用Proxy
实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
observable
函数返回一个原始对象的 Proxy
代理,拦截赋值操作,触发充当观察者的各个函数
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
观察者函数都放进Set
集合,当修改obj
的值,在会set
函数中拦截,自动执行Set
所有的观察者
作者:Morakes 链接:https://juejin.cn/post/7182084369454989349 来源:稀土掘金