Proxy
proxy能够代理整个对象, 在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
和defineProperties的对比
1 :defineProperties是针对属性的劫持。而proxy是针对于对象的,不需要递归进行劫持。性能更好。
2: proxy可以代理数组,而不需要重写数组的方法(hack)。length变化也可以监控到。而defineProperties不行
3: proxy代理支持13种代理拦截方法,而defineProperties没有那么多。
4: proxy返回的是一个新对象,而defineProperties只能遍历操作源对象
5: proxy兼容性不好,需要polyfill。
基本操作
get
const arr = [111,222,44,33];
const proxyArr = new Proxy(arr, {
get(target, key, _proxy) {
console.log(target === arr); // true
console.log(target === proxyArr); // false 返回新对象
console.log(_proxy === proxyArr); // true
console.log(_proxy === arr); // false
let index = key;
if (key < 0) {
index = target.length + Number(key);
}
return target[index];
}
})
console.log(proxyArr[-4]) // 111
如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错
set
const good = {
name : 'car',
price: 100
}
const proxyGood = new Proxy(good, {
set(target, key, value, _proxy) {
if (Number(value) > target.price) {
Reflect.set(target, key, value, _proxy);
} else {
throw new RangeError('price is invalid');
}
}
})
proxyGood.price = 500; // { name:'car', price:500 }
proxyGood.price = 300; // RangeError('price is invalid')
注意,如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
apply
var twice = {
apply (target, ctx, args) {
console.log(arguments) // [target, ctx, args]
console.log(args) // target函数执行时参数列表
console.log(ctx) // 上下文this
console.log(target === sum) // true
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
console.log(proxy(1, 2)); // 6
console.log(proxy.call(null, 5, 6)) // 22
console.log(proxy.apply(null, [7, 8])); // 30
console.log(proxy.bind({}, 5, 5)()); // 20
console.log(Reflect.apply(proxy, null, [6, 6])); // 24
has
has 只对 in操作符生效, 但不隐藏属性,所以for in 、keys等操作仍然可以遍历到属性
const stu1 = {name: 'kimi', score: 80};
const stu2 = {name: 'boom', score: 59};
const handle = {
has(target, key) {
if (key === 'score' && target[key] < 60) return false;
return true;
}
}
const proxy1 = new Proxy(stu1, handle);
const proxy2 = new Proxy(stu2, handle);
console.log('score' in proxy1) // true
console.log('score' in proxy2) // false
for (let k in proxy2) {
console.log(k) // name score
console.log(proxy2[k]) // boom 59
}
console.log(Object.keys(proxy2)); // [ 'name', 'score' ]
如果原对象不可配置或者禁止扩展,这时has拦截会报错。如果某个属性不可配置(或者目标对象不可扩展),则has方法就不得“隐藏”目标对象的该属性。
construct
const p = new Proxy(Array, {
construct(target, args, _proxy) {
console.log(_proxy === p); // true
return { val : new target(...args) } // 必须返回一个对象,如果不是会报错
}
})
const a = new p(222,33);
console.log(Object.getPrototypeOf(a));
deleteProperty
const a = {age: 27}
const b = {age: 35}
function checkAge(age) {
if (Number(age) > 30) throw new RangeError('age is too old!')
}
const handle = {
deleteProperty(target, key) {
checkAge(target[key]);
Reflect.deleteProperty(target, key);
return true
}
}
const p1 = new Proxy(a, handle)
const p2 = new Proxy(b, handle)
console.log(delete p1.age); // true
console.log(delete p2.age); // new RangeError('age is too old!')
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
ownKeys
const p = new Proxy({'name':'kimi'}, {
ownKeys(target) {
// ownKeys方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
return ['a', 'name']
}
})
for(let k in p) {
// 拦截后只返回['a', 'name'],但是原来的对象中只有name属性所以只返回name没有a
console.log(k); // name
}
console.log(Object.keys(p)); // ['name'] 与for in 同理
console.log(Reflect.ownKeys(p)); // ['a', 'name']
console.log(Object.getOwnPropertyNames(p)); // ['a', 'name']
注意,使用Object.keys方法和for in时,有三类属性会被ownKeys方法自动过滤,不会返回。
- 目标对象上不存在的属性
- 属性名为 Symbol 值
- 不可遍历(enumerable)的属性
let target = {
a: 1,
b: 2,
c: 3,
[Symbol.for('secret')]: '4',
};
Object.defineProperty(target, 'key', {
enumerable: false,
configurable: true,
writable: true,
value: 'static'
});
let handler = {
ownKeys(target) {
return ['a', 'd', Symbol.for('secret'), 'key'];
}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy) // ['a']
for(let k in proxy) {
console.log(k); // a
}
proxy中还可以拦截 defineProperty、getOwnPropertyDescriptor、getPrototypeOf
、isExtensible、preventExtensions、setPrototypeOf等方法。
Reflect
Reflect的作用是将Object对象的一些明显属于语言内部的方法,放到Reflect对象上。比如Object.defineProperty。这种属于整个语言的方法,并且修改某些Object方法的返回结果,让其变得更合理。方法和proxy一一对应。
Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和
网友评论