Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改。Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。行为类似于set,get函数
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
get方法用于拦截某个属性的读取操作
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
demo1: recevier指向
var p = new Proxy({a:1}, {
get: function(target, prop, receiver) {
return 10;
}
});
p.a; // 10
var b = {};
b.__proto__ = p;
b.a; // 10
通过p.a访问对象,则recevier === p,通过b.a访问对象 recevier === b;
set方法用来拦截某个属性的赋值操作
假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的属性值符合要求。
var p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
demo
var validator = {
set: function(target, property, value, receiver) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于age以外的属性,直接保存
target[property] = value;
}
};
var person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
apply方法拦截函数的调用、call和apply操作.
var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
has方法可以隐藏某些属性,不被in操作符发现
var p = new Proxy(target, {
has: function(target, prop) {
}
});
demo
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。
如果原对象不可配置或者禁止扩展,这时has拦截会报错。
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
"a" in p; // TypeError is thrown
construct方法用于拦截new命令。如果construct方法返回的不是对象,就会抛出错误。
var p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
}
});
function test(){
return 123;
}
var p = new Proxy(test, {
construct: function(target, args, newTarget) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
new p(1).value
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
var p = new Proxy(target, {
deleteProperty: function(target, property) {
}
});
demo
``
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
return true;
}
}
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(
Invalid attempt to ${action} private "${key}" property`);
}
}
var target = { prop: 'foo' } var proxy = new Proxy(target, handler) delete proxy.prop // Error: Invalid attempt to delete private "_prop" property ``` 上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。
defineProperty方法拦截了Object.defineProperty操作。
var p = new Proxy(target, {
defineProperty: function(target, property, descriptor) {
}
});
var p = new Proxy({}, {
defineProperty: function(target, prop, descriptor) {
console.log("called: " + prop);
return false;
}
});
var desc = { configurable: true, enumerable: true, value: 10 };
Object.defineProperty(p, "a", desc); // "called: a"
上面代码中,defineProperty方法返回false,导致添加新属性会抛出错误。
getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor,返回一个属性描述对象或者undefined。
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
var handler = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return
}
return Reflect.getOwnPropertyDescriptor(target, key);
}
}
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }
getPrototypeOf方法主要用来拦截Object.getPrototypeOf()运算符,以及其他一些操作。
var obj = {};
var p = new Proxy(obj, {
getPrototypeOf(target) {
return Array.prototype;
}
});
console.log(
Object.getPrototypeOf(p) === Array.prototype, // true
Reflect.getPrototypeOf(p) === Array.prototype, // true
p.__proto__ === Array.prototype, // true
Array.prototype.isPrototypeOf(p), // true
p instanceof Array // true
);
setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。
var p = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
}
});
var handler = {
setPrototypeOf (target, proto) {
throw new Error('Changing the prototype is forbidden');
}
}
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
上面代码中,只要修改target的原型对象,就会报错。
isExtensible方法拦截Object.isExtensible操作。
var p = new Proxy({}, {
isExtensible: function(target) {
console.log("called");
return true;
}
});
Object.isExtensible(p)
// "called"
// true
这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。
Object.isExtensible(proxy) === Object.isExtensible(target)
var p = new Proxy({}, {
isExtensible: function(target) {
return false;
}
});
Object.isExtensible(p); // 报错
preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值。
这个方法有一个限制,只有当Object.isExtensible(proxy)为false(即不可扩展)时,proxy.preventExtensions才能返回true,否则会报错。
var p = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});
Object.preventExtensions(p); // 报错
上面代码中,proxy.preventExtensions方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。
为了防止出现这个问题,通常要在proxy.preventExtensions方法里面,调用一次Object.preventExtensions。
var p = new Proxy({}, {
preventExtensions: function(target) {
console.log("called");
Object.preventExtensions(target);
return true;
}
});
Object.preventExtensions(p)
// "called"
// true
ownKeys方法用来拦截Object.keys()操作。
let target = {};
let handler = {
ownKeys(target) {
return ['hello', 'world'];
}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
// [ 'hello', 'world' ]
Proxy.revocable方法返回一个可取消的Proxy实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。
修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});