欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JS学习笔记 - Proxy & Reflect

程序员文章站 2024-02-20 19:32:46
...

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写

定义Proxy

ES6提供了Proxy构造函数,用来生成Proxy实例

var proxy = new Proxy(target,handler)
var target = {}
var proxy = new Proxy(target,{
    get(target,props,receiver){
        return Reflect.get(target,props,receiver)
    }
})

捕获器不变式

使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。比如,如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError

可撤销代理

有时候可能需要中断代理对象与目标对象之间的联系,此时可以使用 Proxy.revocable() 方法返回一个可取消的Proxy实例,内部包含了生成的Proxy实例和用于取消该实例的revoke()方法

var target = { foo:123 }
var { proxy, revoke } = Proxy.revocable(target,{})
proxy.foo //123
revoke()
proxy.foo // TypeError: Revoked

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问

this问题

在Proxy代理的情况下,目标对象内部的this会指向Proxy代理

const target = { 
 thisValEqualsProxy() { 
 return this === proxy; 
 } 
} 
const proxy = new Proxy(target, {}); 
console.log(target.thisValEqualsProxy()); // false 
console.log(proxy.thisValEqualsProxy()); // true

Reflect

所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器行为都像 get()那么简单。实际上,开发者并不需要手动重建原始行为,而是可以通过调用全局 Reflect 对象上(封装了原始行为)的同名方法来轻松重建

处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为

代理 覆写的特性 默认特性
get(target, props, receiver) 读取属性值 Reflect.get()
set(target, props, value, receiver) 写入属性值 Reflect.set()
has(target, props) in操作 Reflect.has()
deleteProperty(target, prop) delete操作符 Reflect.deleteProperty()
getPrototypeOf(target) Object.getPrototypeOf() Reflect.getPrototypeOf()
setPropertyOf(target, proto) Object.setPropertyOf() Reflect.setPrototypeOf()
isExtensible(target) Object.isExtensible() Reflect.isExtensible()
preventExtensions(target) Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor(target, prop) Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty(target, prop, desc) Object.defineProperty() Reflect.defineProperty()
ownKeys(target) Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() for...in Reflect.ownKeys()
apply(target, thisArg, args) 实例作为函数调用 Reflect.apply()
construct(target, args) 使用new实例化Proxy Reflect.construct()

应用

跟踪属性访问

通过捕获get、set、has等操作,可以知道对象属性什么时候被操作。把实现响应捕获器的某个代理对象放到应用中,可以监控这个对象何时在何处被访问过

const user = { 
    name: 'Jake' 
}; 
const proxy = new Proxy(user, { 
    get(target, property, receiver) { 
        console.log(`Getting ${property}`); 
        return Reflect.get(...arguments); 
    }, 
    set(target, property, value, receiver) { 
        console.log(`Setting ${property}=${value}`); 
        return Reflect.set(...arguments); 
    } 
}); 
proxy.name; // Getting name 
proxy.age = 27; // Setting age=27

隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标上的属性也轻而易举

const hiddenProps = {'city','money'}
const userInfo = {
    name: 'LiMing',
    age: 13,
    money: 2000,
    city: 'Shenyang',
    country: 'China'
}
const proxy = new Proxy(userInfo,{
    get(target, props, receiver){
        if(hiddenProps.includes(props)){
            return undefined
        }else{
            Reflect.get(target, props, receiver)
        }
    },
    has(target, prop){
    	if(hiddenProps.includes(prop)){
            return false
        }else{
            Reflect.has(target, prop)
        }
    }
})
proxy.name // Liming
proxy.city // undefined
proxy.age // 13

'name' in proxy // false
'age' in proxy // true

属性验证

可以根据所赋的值决定是否允许还是拒绝赋值

const target = { 
    onlyNumbersGoHere: 0 
}; 
const proxy = new Proxy(target, { 
    set(target, property, value) { 
        if (typeof value !== 'number') { 
            return false; 
        } else { 
            return Reflect.set(...arguments); 
        } 
    } 
}); 
proxy.onlyNumbersGoHere = 1; 
console.log(proxy.onlyNumbersGoHere); // 1 
proxy.onlyNumbersGoHere = '2'; 
console.log(proxy.onlyNumbersGoHere); // 1

函数与构造函数参数验证

跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查

例:使函数只接收某种类型的值

function getA(a,b){
    console.log(a)
    console.log(b)
}
var proxy = new Proxy(getA,{
    apply(target,thisArg,args){
        for(let item of args){
            if(typeof item !== 'number'){
                throw new TypeError(`Non-number arg ${item}`)
            }
        }
        return Reflect.apply(...arguments)
    }
})
proxy.apply(null,[123,341,'aaa',false,123])
// Uncaught TypeError: Non-number arg aaa

例:要求实例化对象时必须给构造函数传参

class User{
    constructor(id) { 
        this.id_ = id; 
    } 
} 
const proxy = new Proxy(User, { 
    construct(target, argumentsList, newTarget) { 
        if (argumentsList[0] === undefined) { 
            throw 'User cannot be instantiated without id'; 
        } else { 
            return Reflect.construct(...arguments); 
        } 
    } 
}); 
new proxy(1); 
new proxy(); 
// Error: User cannot be instantiated without id

Proxy实现观察者模式

  1. 首先定义了一个observe 用来存储 要触发的函数
  2. 然后定义了一个observable 函数 对 对象的设值做了一层代理,拦截赋值操作, Reflect.set()用来完成默认的设值行为, 然后触发函数
  3. 每当对象调用对象内部的的set方法时,就会遍历触发我们添加进callbacks里的回调函数
var funcQueue = new Set()
var observer = fn => funcQueue.add(fn)
var observable = object => new Proxy(object,{set})
function set(target,prop,value,receiver){
    let result = Reflect.set(...arguments)
    funcQueue.forEach(func=>func())
    return result
}
function response(){
    console.log(data)
}
observer(response)

var data = { name:'Lihua', age:13 }
var person = observable(data)

person.name = 'Liming'
person.age = 20