概述
Proxy 用于修改某些操作的默认行为,等用于在语言做出修改,属于一种‘元编程’,即对编程语言进行编程。
Proxy 可以让外界对对象的访问进行过滤和改写。
var obj = new Proxy({}, {
get: function(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set: function(target, key, receiver) {
return Reflect.set(target, key, value, receiver)
}
})
Proxy 实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。
Es6提供Proxy 构造函数,用来生成Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的是handler 参数的写法。
new Proxy() 标识生成一个Proxy 实例,target 参数标识所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
// 拦截读取属性行为
var proxy = new Proxy({}, {
get: function(target, property) {
return 35
}
})
proxy.name // 35
proxy.time // 35
proxy.title // 35
访问任何属性都得到35。要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作。
如果handler没有设置任何拦截,那等同于直接通向原对象。
var target = {}
var handler = {}
var proxy = new Proxy(target, handler);
proxy.a = "b"
target.a // 'b'
一个技巧是将Proxy 对象,设置到 object.proxy 属性,从而可以在object 对象上调用。
var object = {proxy: new Proxy(taret, handler)}
Proxy实例也可以作为其它对象的原型对象
var proxy = new Proxy({}, {
get: function(target, property) {
return 35
}
})
let obj = Object.create(proxy)
obj.time // 35
同一个拦截器函数,可以设置拦截多个操作
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype
}
return 'Hello, ' + name
},
apply: function(target, thisBinding, args) {
retrun args[0]
}
construct: function(target, args) {
return {value: args[1]}
}
}
var fproxy = new Proxy(function(x, y) {
return x + y
}, handler)
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproy.prototype === Object.prototype // true
fproxy.foo === 'Hello, foo' // true
Proxy 支持的拦截操作: 13种
get(target, propKey, receiver)
拦截对象属性的读取
set(target, propKey, value, receiver)
拦截对象属性的设置
has(target, propKey)
拦截propKey in proxy的操作, 返回一个布尔值。
deletePropery(target, propkey)
拦截delete proxy[propLKey] 的操作,返回一个布尔值。
ownkeys(target)
getOwnPropertyDescriptor(target, propkey)
difineProperty(target, propKey, propDesc)
preventExtensions(target)
getPrototypeOf(target)
isExtensible(target)
setPrototypeOf(target, proto)
apply(target, object, args)
拦截Proxy 实例作为函数的调用,比如 proxy(…args) proxy.call(object, …args) proxy.apply(…)
construct(target, args)
拦截Proxy实例作为构造函数调用的操作,比如: new proxy(…args)
get()
var person = { name: '张三' }
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property]
} else {
console.log(`${property}属性不存在`)
}
}
})
proxy.name // 张三
proxy.age // age属性不存在
get() 可以继承
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET', propertyKey)
return target[propertyKey]
}
})
let obj = Object.create(proto)
obj.foo // 'GET foo'
利用 Proxy, 可将 读取属性的操作,转变为执行某个函数,从而实现属性的链式操作。
var pipe = (function() {
return function(value) {
var funStack = []
var oproxy = new Proxy({}, {
get: function(popeObject, fnName) {
if (fnName === 'get') {
return funStack.reduce(function(val, fn) {
retrun fn(val)
}, value)
}
funcStack.push(window[fnName])
return oproxy
}
})
return oproxy
}
}())
利用get拦截,实现一个生成各种DOM节点的通用函数dom
set()
set方法用来拦截某个属性的复制操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实例本身(可选)。
假定Person对像的age 属性是一个不大于200的整数
let validator = {
set: function(obj, prop, vlaue) {
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')
}
obj[prop] = value
}
}
}
let person = new Proxy({}, validator)
person.age = 100 // 100
person.age = 'young' // 报错 The age is not an intege
person.age = 300 // 报错 The age seems invalid
set 的第4个参数receiver, 指的是原始的操作行为所在的那个对象,一般情况下proxy实例本身
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver
}
}
const proxy = new Proxy({}, handler)
proxy.foo = 'bar'
proxy.foo === proxy // true
注意: 如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
apply()
apply方法拦截函数的调用、call 和 apply 操作。
apply方法接受三个参数,分别是目标对象、目标对象的上下文对象 和 目标对象的参数数组。
var target = function() { return 'i am the taret'}
var handler = {
apply: function() {
return 'i am the proxy'
}
}
let p = new Proxy(target, handler)
p() // i am the proxy
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()
has 方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in运算符。
has方法可以接受两个参数,分别时目标对象、需查询的属性名。
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
construct()
construct方法用于拦截new命令。
接受两个参数
target: 目标对象
args: 构造函数的参数对象
newTarget 创造实例对象时, new 命令作用的构造函数
var p = new Proxy(function() {}, {
construct: function(target, args) {
console.log('called: '+ args.join(', '))
return {value: args[0] * 10}
}
})
(new p(1)).value // 'called: 1'
construct 方法返回的必须时一个对象,否则会报错
var p = new Proxy(function() {}, {
contruct: function(target, argumentsList) {
return 1
}
})
new p() // 报错
deleteProperty()
用于拦截delete操作,如果这个方法抛出错误或者返回false,档期那属性就无法被delete命令删除。
var handler = {
deleteProperty(target, key) {
invariant(key, 'delete')
delete target[key]
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
// erro: Invalid attempt to delete private "_prop" property
注意: 目标对象自身的不可配置的属性,不能被deleteProperty方法删除,否则报错
defineProperty()
defineProperty 方法拦截了Object.defineProperty操作。
var handler = {
defineProperty(target, key, descriptor) {
return false
}
}
var target = {}
var proxy = new Proxy(target, handler)
proxy.foo = 'bar' // 不会生效
上例, defineProperty方法返回false,导致添加新属性总是无效。
注意: 如果目标对象不可扩展,的defineProperty不能增加目标对象上不存在的属性,否则会报错。
如果目标对象的某个属性不可写或不可配置,则defineProperty方法不得改变这两个设置。
getOwnPropertyDescriptor()
getOwnPropertyDescriptor 方法拦截 Object.getOwnPropertyDescriptot(), 返回一个属性描述对象或undefined.
var handler = {
getOwnPropertyDescriptor(target, key) {
if (key[0] === '_') return
return Object.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', writabel: true, enumerable: true, configurable: true }
getPrototypeOf()
主要用来拦截获取对象原型。如以下操作:
- Object.prototype.proto
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
isExtensible()
isExtensible 方法拦截Object.isExtensible操作
var p = new Proxy({}, {
isExtensible: function(target) {
console.lo('called')
return true
}
})
Object.isExtensible(p)
// 'called'
// true
ownKeys()
ownkeys 方法用来拦截对象自身属性的读取操作。
let target = {a: 1, b: 2, c; 3}
let handler = {
ownKeys(target) {
return ['a']
}
}
let proxy = new Proxy(target, handler);
Object.keys(proxy)
preventExtensions()
preventExtensions 方法拦截Object.preventExtensions()。
该方法必须返回一个布尔值,否则会被自动转为布尔值。
限制: 只有目标对象不可扩展时(Object.isExtensible(proxy))为false, proxy.preventExtensions 才能返回true,否则会报错。
var proxy = new Proxy({}, {
preventExtensions: function(target) {
return false
}
})
Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
setPrototypeOf()
setPrototypeOf 方法主要用来拦截Object.setPrototypeOf 方法
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
Proxy.revocable()
Proxy.revocable 方法返回一个可取消的 Proxy 实例
var target = {}
var handler = {}
let {proxy, revoke} = Proxy.revocalbe(target, handler)
proxy.foo = 123
proxy.foo // 123
revoke()
proxy.foo // TypeError: Revoked
Proxy.revocable 方法返回一个对象,该对象的proxy属性是 Proxy 实例, revoke 属性是一个函数,可以取消Proxy 实例。上面的代码种,当执行revoke 杉树之后,再访问Proxy 实例, 就会抛出一个错误。