Proxy的理解
Proxy是es6的新特性,
用于创建一个对象的代理,从而实现基本的拦截、自定义(属性查找、赋值、枚举、函数调用....)。
Proxy可以直接监听整个对象而非属性,可以监听数组的变化,多达13种拦截方法。
vue3数据响应式的原理,采用的就是,Proxy代理
语法:let p = new Proxy(target, handler)
target:目标对象(可以是任意类型的对象,包括:数组、函数、甚至是另一个代理)
handler:具体的操作,其实就是一个对象,其属性是:当执行一个操作时定义代理的行为的函数
就是说里面写的是各种拦截函数,不同的拦截方法拦截的是不同的操作
监听方法如下,
get(target, property, receiver),用于拦截某个属性的读取操作,
target:目标对象,
property:属性名,
receiver:proxy实例本身(此参数可选)
set(target, property, value, receiver),用于拦截某个属性的赋值操作,
target:目标对象,
property:属性名,
value:属性值,
receiver:proxy实例本身(此参数可选)
举个栗子,
let obj = { name: 'White', age: 18, sex: '女' }
// 给 obj 设置一个代理
let p1 = new Proxy(obj, {
get(target, property) {
// 一些骚操作...
// 最后,返回你想要的值即可(你想返回什么就返回什么)
return target[property]
},
set(target, property, value){
// 一些骚操作...
target[property] = value
}
})
p1.age = 123456
// 读取 obj 的 age属性,注意定义代理后,得用代理来调用属性、方法
console.log('p1.age=', p1.age)
console.log('p1=', p1)
对Proxy的结构做一下解释
用,上面 obj对象 的代理,做例子,说一下吧
下面这就是 proxy代理 的样子(console.log('p1=', p1)的打印如下)
Proxy(Object) {name: 'White', age: 123456, sex: '女'}
[[Handler]]: Object
get: ƒ get(target, property)
set: ƒ set(target, property, value)
[[Prototype]]: Object
[[Target]]: Object
age: 123456
name: "White"
sex: "女"
[[Prototype]]: Object
[[IsRevoked]]: false
[[Handler]]:是一个内部属性,指向一个对象,其中包含了钩子函数(handler functions),用来拦截对代理对象的各种操作,
这些钩子函数包括:get用于拦截对属性的读取操作,set用于拦截对属性的设置操作,以及其他诸如 apply、construct 等。
补充一下:这个get、set,就是我们在 p1代理中 自己定义的。
[[Target]]:是一个内部属性,指向被代理的目标对象,
也就是说,Proxy代理的是这个目标对象,通过代理可以对目标对象进行各种操作的拦截。
[[IsRevoked]]:是一个内部属性,表示该代理是否被撤销,当代理被撤销时,它将失去对目标对象的代理能力,`[[IsRevoked]]`的值也会发生变化
[[IsRevoked]]有两种结果:
false:表示代理对象尚未被撤销,它仍然有效,可以继续拦截对目标对象的操作。
true:表示代理对象已经被撤销,它会失去对目标对象的代理能力,无法再拦截对目标对象的操作,也无法再访问目标对象的属性和方法。
实际使用中,通常不需要直接操作 `[[IsRevoked]]` 属性,
因为代理对象的撤销通常是由系统自动处理的。当代理对象不再被引用时,它会被自动垃圾回收,从而触发撤销操作。
----------------------------- 具体解释一下 -----------------------------
首先
- 为什么Proxy会取代Object.defineProperty
1、Object.defineProperty,只能劫持对象的属性, 不能监听数组,也不能对es6新产生的Map、Set这些数据解构做出监听, 也不能监听新增、删除操作等等。 2、Proxy可以直接监听整个对象而非属性,可以监听数组的变化,具有多达13种拦截方法
- 什么是Proxy
Proxy是es6的新特性,用于创建一个对象的代理, 从而实现基本操作的拦截、自定义(如:属性查找、赋值、枚举、函数调用等等) Proxy可以直接监听整个对象而非属性,可以监听数组的变化。具有多达13种拦截方法 【语法】: let p = new Proxy(target, handler) * 参数1,target:目标对象(可以是任何类型的对象,包括原生数组、函数、甚至是另一个代理) * 参数2,handler:具体的操作,其实就是一个对象,其属性是,当执行一个操作时定义代理的行为的函数, 就是说里面写各种拦截的函数,不同的拦截方法拦截的是不同的操作。 get、set的拦截方法如下: 1、get(target,property,receiver),用于拦截某个属性的'读取'操作, 参数1,target:目标对象 参数2,property:属性名 参数3,receiver:proxy实例本身(可选) 2、set(target, property, value, receiver),用于拦截某个属性的'赋值'操作, 参数1,target:目标对象 参数2,property:属性名 参数3,value:属性值 参数4,receiver:proxy实例本身(可选) '举个例子' let obj1 = { name: 'jack', age: 18, sex: '女', } // 给obj设置一个代理 let p1 = new Proxy(obj1, { get(target, property) { console.log('get--target->', target) console.log('get--property->', property) console.log('get--target[property]=', target[property]) // 定义你要返回的值(你想返回什么就返回什么) return target[property] }, set(target, property, value) { console.log('set--target->', target) console.log('set--property->', property) console.log('set--target[property]=', target[property]) console.log('set--value=', value) // 123456 target[property] = value } }) // 这里会走代理的set哦 p1.age = 123456 // 读取obj1的age属性看看,注意定义代理后,得用代理来调用属性、方法 console.log('p1.age=', p1.age) console.log('------------------------------------------------------') console.log('obj1=', obj1) // obj1= {name: 'jack', age: 123456, sex: '女'} console.log('p1=', p1) // p1= Proxy(Object) {name: 'jack', age: 123456, sex: '女'}
小例子 - 1
'要求':obj1有个age属性,此属性是不大于200的整数,利用Proxy保证age的值符合要求
用 Number.isInteger(aaa) 判断'变量aaa'是否为整数
Number.isInteger(26) // true
Number.isInteger(26.0) // true
Number.isInteger(26.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false
const obj1 = { name: 'jack', age: 18, sex: '女', }
const proxy1 = new Proxy(obj1, {
get(target, property) {
return target[property] // 定义你要返回的值(你想返回什么就return什么)
},
set(target, property, value) {
// 对age属性继进行约束
if (property === 'age') {
// 判断是不是整数
if (!Number.isInteger(value)) new TypeError('the age is not a integer')
// 判断不大于200
if (value > 200) new RangeError('the age seems invalid')
}
// 对其他属性就直接赋值;对满足条件的age属性也直接赋值
target[property] = value
}
})
proxy1.age = 300 // 浏览器会抛出错误,因为 age > 200
小例子 - 2
'要求':
事先给对象设置的内部属性,属性名的第一个字符用下划线开头,表示这些属性不能被外部使用,
结合get和set方法,就可以防止这些内部属性被外部读写。
const obj2 = { name: 'bbbbbb', _age: 20, sex: '男', }
// 定义工具函数,Proxy中要使用
function invariant(property, action) {
if (property[0] === '_') {
throw new Error(`不能对${property}属性进行${action}操作`)
}
}
let proxy2 = new Proxy(obj2, {
get(target, property) {
invariant(property, 'get')
return target[property]
},
set(target, property, value) {
invariant(property, 'set')
target[property] = value
return true
}
})
console.log('proxy2._age=', proxy2._age) // 浏览器会抛出错误(Error: 不能对_age属性进行get操作)
proxy2._age = 123 // 浏览器会抛出错误(Error: 不能对_age属性进行set操作)
set方法第四个参数 receiver
这里涉及到了`原型链`,所以说一下
const obj1 = {}
const obj2 = { foo: 'bar' }
Object.setPrototypeOf(obj1, obj2)
const { foo } = obj1
foo // "bar"
将,obj1的原型对象设置为obj2,那么,在obj1找不到属性,就按照原型链到它(obj1)的原型对象(obj2)上去找
let obj3 = { a: 1, b: 2, c: 3, d: '哒哒哒' }
let proxy3 = new Proxy(obj3, {
set(target, property, value, receiver) {
console.log('3-receiver=', receiver)
target[property] = receiver
}
})
proxy3.a = 11 // 进行赋值操作,从而触发proxy的set方法
console.log(proxy3.a === proxy3) // true
---------- 扩展深入一下 ----------
let proxy4 = new Proxy(obj3, {
set(target, property, value, receiver) {
console.log('target=', target) // target= obj3
console.log('property='. property) // value= undefined
console.log('value=', value) // value= bar
console.log('在这里看一下<原始的操作行为所在的对象>==', receiver) // 打印的是{ a: '1', b: '2' }
target[property] = receiver
}
})
const myObj = { a: '1', b: '2' }
Object.setPrototypeOf(myObj, proxy4) // 将myObj的原型对象设置为proxy4
myObj.foo = 'bar'
console.log('myObj.foo === myObj:', myObj.foo === myObj) // myObj.foo === myObj: true
console.log('myObj.foo=', myObj.foo) // myObj.foo= {a: '1', b: '2'}