ES6 Gist - Proxy

94 阅读2分钟

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
    - instanceof
    
    var 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