Proxy API
对应的Proxy
对象是ES2015就已引入的一个原生对象,用于定义基本操做的自定义行为(如属性查找、赋值、枚举、函数调用等)。javascript
从字面意思来理解,Proxy
对象是目标对象的一个代理器,任何对目标对象的操做(实例化,添加/删除/修改属性等等),都必须经过该代理器。所以咱们能够把来自外界的全部操做进行拦截和过滤或者修改等操做。vue
基于Proxy
的这些特性,经常使用于:java
Proxy语法:react
const p = new Proxy(target, handler)
复制代码
例以下面一个很简单的用法:git
let foo = {
a: 1,
b: 2
}
let handler = {
get:(obj,key)=>{
console.log('get')
return key in obj ? obj[key] : undefined
}
}
let p = new Proxy(foo,handler)
console.log(p.a) // 1
复制代码
上面代码中p就是foo的代理对象,对p对象的相关操做都会同步到foo对象上。github
同时Proxy也提供了另外一种生成代理对象的方法Proxy.revocable()
:api
const { proxy,revoke } = Proxy.revocable(target, handler)
复制代码
该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}
,其中:数组
new Proxy(target, handler)
建立的代理对象没什么不一样,只是它能够被撤销掉。例如:bash
let foo = {
a: 1,
b: 2
}
let handler = {
get:(obj,key)=>{
console.log('get')
return key in obj ? obj[key] : undefined
}
}
let { proxy,revoke } = Proxy.revocable(foo,handler)
console.log(proxy.a) // 1
revoke()
console.log(proxy.a) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
复制代码
须要注意的是,一旦某个代理对象被撤销,它将变得几乎彻底不可调用,在它身上执行任何的可代理操做都会抛出 TypeError 异常。app
上面代码中,咱们只使用了get操做的handler,即当尝试获取对象的某个属性时会进入这个方法,除此以外Proxy共有接近14个handler也能够称做为钩子,它们分别是:
handler.getPrototypeOf():
在读取代理对象的原型时触发该操做,好比在执行 Object.getPrototypeOf(proxy) 时。
handler.setPrototypeOf():
在设置代理对象的原型时触发该操做,好比在执行 Object.setPrototypeOf(proxy, null) 时。
handler.isExtensible():
在判断一个代理对象是不是可扩展时触发该操做,好比在执行 Object.isExtensible(proxy) 时。
handler.preventExtensions():
在让一个代理对象不可扩展时触发该操做,好比在执行 Object.preventExtensions(proxy) 时。
handler.getOwnPropertyDescriptor():
在获取代理对象某个属性的属性描述时触发该操做,好比在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.defineProperty():
在定义代理对象某个属性时的属性描述时触发该操做,好比在执行 Object.defineProperty(proxy, "foo", {}) 时。
handler.has():
在判断代理对象是否拥有某个属性时触发该操做,好比在执行 "foo" in proxy 时。
handler.get():
在读取代理对象的某个属性时触发该操做,好比在执行 proxy.foo 时。
handler.set():
在给代理对象的某个属性赋值时触发该操做,好比在执行 proxy.foo = 1 时。
handler.deleteProperty():
在删除代理对象的某个属性时触发该操做,即便用 delete 运算符,好比在执行 delete proxy.foo 时。
handler.ownKeys():
当执行Object.getOwnPropertyNames(proxy) 和Object.getOwnPropertySymbols(proxy)时触发。
handler.apply():
当代理对象是一个function函数时,调用apply()方法时触发,好比proxy.apply()。
handler.construct():
当代理对象是一个function函数时,经过new关键字实例化时触发,好比new proxy()。
复制代码
结合这些handler,咱们能够实现一些针对对象的限制操做,例如:
let foo = {
a:1,
b:2
}
let handler = {
set:(obj,key,value,receiver)=>{
console.log('set')
if (key == 'a') throw new Error('can not change property:'+key)
obj[key] = value
return true
},
deleteProperty:(obj,key)=>{
console.log('delete')
if (key == 'a') throw new Error('can not delete property:'+key)
delete obj[key]
return true
}
}
let p = new Proxy(foo,handler)
p.a = 3 // Uncaught Error
delete p.a // Uncaught Error
复制代码
其中,set方法的receiver一般是 Proxy 本即 p,可是当有一段代码执行 obj.name = "jen", obj 不是一个 proxy,且自身不含 name 属性,可是它的原型链上有一个 proxy,那么,那个 proxy 的handler里的set方法会被调用,而此时obj 会做为 receiver 这个参数传进来。
let foo = {
a:1,
b:2
}
let handler = {
set:(obj,key,value)=>{
console.log('set')
if (typeof(value) !== 'number') throw new Error('can not change property:'+key)
obj[key] = value
return true
}
}
let p = new Proxy(foo,handler)
p.a = 'hello' // Uncaught Error
复制代码
Vue3中的响应式对象:
import {ref,reactive} from 'vue'
...
setup(){
const name = ref('test')
const state = reactive({
list: []
})
return {
name,
state
}
}
...
复制代码
在Vue3中,composition-api提供了一种建立响应式对象的方法reactive,其内部就是利用了Proxy API来实现的,特别是借助handler的set方法,能够实现双向数据绑定相关的逻辑,这对于Vue2.x中的Object.defineProperty()
是很大的改变。
Object.defineProperty()
只能单一的监听已有属性的修改或者变化,没法检测到对象属性的新增或删除,而Proxy则能够轻松实现。
Object.defineProperty()
没法监听属性值是数组类型的变化,而Proxy则能够轻松实现。
例如监听数组的变化:
let arr = [1]
let handler = {
set:(obj,key,value)=>{
console.log('set')
return Reflect.set(obj, key, value);
}
}
let p = new Proxy(arr,handler)
p.push(2)
复制代码
Reflect.set()
用于修改数组的值,参考Reflect,可是对于多层对象嵌套问题,须要通过必定的处理:
let foo = {
a:1,
b:2
}
let handler = {
set:(obj,key,value)=>{
console.log('set')
// 双向绑定相关逻辑
obj[key] = value
return true
}
}
let p = new Proxy(foo,handler)
p.a = 3
复制代码
上面代码中,对于简单的对象foo是彻底没问题的,可是若是foo是一个复杂对象,里面嵌套的不少对象,那么当去尝试修改里层对象的值时,set方法就不会触发,为了解决这种场景,在Vue3中,采用了递归的方式来解决这个问题:
let foo = {a:{c:3,d:{e:4}},b:2}
const isObject = (val)=>{
return val !== null && typeof val === 'object'
}
const createProxy = (target)=>{
let p = new Proxy(target,{
get:(obj,key)=>{
let res = obj[key] ? obj[key] : undefined
// 判断类型,避免死循环
if (isObject(res)) {
return createProxy(res)
} else {
return res
}
},
set: (obj, key, value)=> {
console.log('set')
obj[key] = value;
}
})
return p
}
let result = createProxy(foo)
result.a.d.e = 6 // 打印出set
复制代码
当尝试去修改一个多层嵌套的对象的属性时,会触发该属性的上一级对象的get方法,利用这个就能够对每一个层级的对象添加Proxy代理,这样就实现了多层嵌套对象的属性修改问题。
固然,上面这段代码只是Vue3中reactive的一个缩影,更多的细节能够浏览相关源码来了解。
就目前来看,Porxy API相关内容是从ES2015才引入的标准,而且业界相关的polyfill也不是很完善,因此使用此API相关的框架要慎重的考虑兼容性问题。