Proxy 代理器
主要用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,可以对外界的访问进行过滤和改写。
- proxy结构
- proxy事例
proxy结构
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
new Proxy()表示生成一个Proxy实例,
target参数表示所要拦截的目标对象,
handler参数也是一个对象,用来定制拦截行为,可以定义一系列的拦截函数,做过滤。
let target = {
name: "张三"
};
let handler = {
get: function (target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
}
let proxy = new Proxy(target, handler);
console.log(proxy.name)// "张三"
proxy.age // 抛出一个错误
handler支持的拦截操作:
函数 | 参数 | 作用 |
---|---|---|
get | target, propKey, receiver | 拦截对象属性的读取,比如proxy.foo和proxy[‘foo’],最后一个参数receiver是一个对象 |
set | target, propKey, value, receiver | 拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值 |
has | target, propKey | 拦截propKey in proxy的操作,返回一个布尔值 |
deleteProperty | target, propKey | 拦截delete proxy[propKey]的操作,返回一个布尔值。 |
ownKeys(target) | target, propKey | 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性 |
getOwnPropertyDescriptor | target, propKey | 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。 |
defineProperty | target, propKey, propDesc | 拦截Object.defineProperty(proxy, propKey, 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),返回一个布尔值。 |
如果目标对象是函数,那么还有两种额外操作可以拦截。
函数 | 参数 | 作用 |
---|---|---|
apply | target, object, args | 拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…). |
construct | target, args | 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。 |
GET拦截
get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子,其次get方法可以继承。
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET '+propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.xxx // "GET xxx"
利用get拦截,实现一个生成各种DOM节点的通用函数dom。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script language="javascript">
const dom = new Proxy({}, {
get(target, property) {
return function(attrs = {}, ...children) {
const el = document.createElement(property);
console.log(el);
for (let prop of Object.keys(attrs)) {
el.setAttribute(prop, attrs[prop]);
console.log(prop,attrs[prop]);
}
for (let child of children) {
if (typeof child === 'string') {
console.log(child);
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
}
});
const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
console.log(document.body.appendChild(el));
</script>
</body>
</html>
如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错.
const targetExtends = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});
const handlerExtends = {
get(target, propKey) {
return 'abc';
}
};
const proxyExtends = new Proxy(targetExtends, handlerExtends);
console.log(proxyExtends.foo)
set方法
用来拦截某个属性的赋值操作。
let validator = {
set: function(obj, prop, value) {
if (prop === '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以外的属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age) // 100
person.age = 'young' // 报错
// person.age = 300 // 报错
结合get和set方法,就可以做到防止这些内部属性被外部读写
var handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = {};
var proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
如果目标对象自身的某个属性,不可写也不可配置,那么set不得改变这个属性的值,只能返回同样的值,否则报错。