概述
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 // 35proxy.time // 35proxy.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) // 1new fproxy(1, 2) // {value: 2}fproy.prototype === Object.prototype // truefproxy.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 // 100person.age = 'young' // 报错 The age is not an integeperson.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) // 6proxy.call(null, 5, 6) // 22proxy.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] === '_') returnreturn Object.getOwnPropertyDescriptor(target, key)}}var target = {_foo: 'bar', baz: 'tar'}var proxy = new Proxy(target, handler)Object.getOwnPropertyDescriptor(proxy, 'wat') // undefinedObject.getOwnPropertyDescriptor(proxy, '_foo') // undefinedObject.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 = 123proxy.foo // 123revoke()proxy.foo // TypeError: Revoked
Proxy.revocable 方法返回一个对象,该对象的proxy属性是 Proxy 实例, revoke 属性是一个函数,可以取消Proxy 实例。上面的代码种,当执行revoke 杉树之后,再访问Proxy 实例, 就会抛出一个错误。
