proxy
-
可以定制被包装对象的基本操作,譬如属性访问(get)、赋值(set)、函数调用(apply)、构造函数(construct) 等,简单的用途是函数调用的次数
-
用 Proxy 构造函数创建代理对象 (get 陷阱函数traps)
const target = {} const handler = { get: function(target,prop){ console.log(`proxy get ${prop}`) return target[prop] } } const proxy = new Proxy(target,handler) console.log(proxy.name) -
例子
function createObservable(obj,onChange){ return new Proxy(obj,{ set: function(target,prop,value){ target[prop] = value onChange(prop,value) return true } }) } const person = { name: 'Dong', age: 25 } const observablePerson = createObservable(person,(prop,value)=>{ console.log(`person ${prop} changed`) }) observablePerson.age = 26 const target = {} const handler = { set: function(target,prop,value){ if(typeof value==='number' && value>0){ target[prop] = value }else{ console.warn(`Invalid value for ${prop}`) } return true } } const proxy = new Proxy(target,handler) proxy.count = 5 proxy.count = -1 // err -
陷阱函数(traps),用于拦截和定制操作的代理对象方法
- get 属性拦截
- set 属性赋值
- apply 函数调用拦截
- construct 对象实例化拦截
- has 拦截 for in
- deleteProperty 拦截属性删除
-
代理对象与普通对象的区别在于代理对象可以在操作发生时执行拦截和定制
-
取消代理对象,Proxy.revocable
const target = {} const handler = { get: function(target,prop){ return target[prop] } } const {proxy,revoke} = Proxy.revocable(target,handler) console.log(proxy.name) // ok revoke() console.log(proxy.name) // err, 不能再调用get -
性能考虑
- 代理对象的拦截和定制逻辑可能导致性能下降,特别是频繁调用的场景,但是代理本身可能影响
-
Set
const dom = new Proxy({},{ get(target,property){ return function(attrs={},...children){ const el = document.createElement(property) for(let prop of Object.keys(attrs)){ el.setAttribute(prop, attrs[prop]) } for(let child of children){ if(typeof child === 'string'){ child = document.createTextNode(child) } el.appendChild(el) } return el } } }) const el = dom.div({},'Hello, Myname is ', dom.a({href:'//example.com'},'Silen')) document.body.appendChild(el) -
Apply
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,1,2) // 6 proxy.apply(null,[1,2]) // 6 Reflect.apply(proxy,null,[1,2]) // 6 -
Has
Has 的拦截只对 in 运算符生效,对 for...in 循环不生效
var handler = { has(target,key){ // } } var proxy = new Proxy({},handler) -
Consturct
var handler = { construt(target,args,newTarget){ // target 目标对象,必须是一个函数 // args 构造函数的参数数组 // newTarget 创建实例对象时,new命令作用的构造函数 // 必须返回一个对象 // this 指向 handler } } -
deleteproperty
拦截 delete
var handler = { deleteProperty(target,key){ delete target[key] return true } } var proxy = new Proxy({},handler) -
defineProperty
拦截 Object.defineProperty
var handler = { defineProperty(target,key,descriptor){ return false } } var proxy = new Proxy({},hander) -
getOwnPropertyDescriptor
拦截 Object.getOwnPropertyDescriptor
var handler = { getOwnPropertyDescriptor(target,key){ if(key[0]==='_'){ return } return Object.getOwnPropertyDescriptor(target,key) } } var proxy = new Proxy({},hander) -
getPrototypeOf
拦截获取对象原型
- Object.prototype.__proto__ - Object.prototype.isPrototypeOf - Object.getPrototypeOf - Reflect.getPrototypeOf - instanceofvar proto = {} var handler = { getPrototypeOf(target){ return proto // 必须返回对象 或者 null } } -
isExtensible
拦截 Object.isExtensible
-
ownkeys
拦截自身属性的读取操作
不包括:目标对象不存在的属性,属性名为Symbol值,不可遍历属性
- Object.getOwnPropertyName
- Object.getOwnPropertySymbols
- Object.keys
- for ... in
-
preventExtensions
拦截Object.preventExtensions
-
setPrototypeOf
拦截 Object.setPrototypeOf,用于防止改写原型对象
-
Proxy.revocable
返回一个可以取消的 Proxy 实例
使用场景:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回权限
let target = {} let hander = {} let {proxy,revoke} = Proxy.revocable(target,handler) proxy.foo = 123 revoke() proxy.foo // err -
This 指向问题
-
Proxy 代理之后,目标对象的this由调用对象决定,handler 里面的 this 始终指向 handler
const target = { m:function(){ console.log(this===proxy,this===target) } } const hander = { get: function(target,key,receiver){ console.log(this===handler) return target[key] } } const proxy = new Proxy(target,handler) target.m() // false,true proxy.m() // true, true,false
-