Proxy代理
我的简单理解就是,在对象和对象的属性之间设置一个代理,在对对象或者对象属性进行操作时,进行一个拦截,进而实现自己的操作。
1.在Proxy代理之前的拦截器
2.强大的Proxy
3.对于其他数据类型的拦截
一、在Proxy代理之前的拦截器
在没有Proxy之前我们可以通过Object.defineProperty来对对象设置拦截。
let obj = {}
Object.defineProperty(obj,"data",{
get(){
console.log("get")
},
set(){
console.log("set")
}
})
console.log(obj)
上面的代码通过Object.defineProperty设置了一个简单拦截,调用obj的data属性时,会触发get,而修改则会触发set。
如果你想知道代理有什么用,那么继续扩展这个例子你就会发现现在的所以响应式框架都是建立在代理的地基之上的。
<div id="box"> </div>
<script>
let box = document.querySelector("#box")
let obj = {}
Object.defineProperty(obj,"data",{
get(){
console.log("get")
return box.innerHTML
},
set(value){
console.log("set",value)
box.innerHTML = value
}
})
console.log(obj)
</script>
上面的代码,就可以实现对数据进行修改从而改变DOM,这就是vue2实现响应式拦截的底层原理。而Proxy代理是传统方法的升级,更加的便捷,迅速。所以到了vue3就采用的是proxy代理。
二、强大的Proxy
传统的Object.defineProperty,每次只能设置一个属性,如果想要对整个对象都添加拦截,则要逐行设置,而且Object.defineProperty无法实现对数组拦截,所以在vue3之前,为了实现数组的响应式,vue的作者把数组很多方法都重写了一遍。而proxy则可以一次代理整个对象,而且可代理多种数据类型。
<div id="box"> </div>
<script>
let box = document.querySelector("#box")
let obj = {}
let proxy = new Proxy(obj,{
get(target,key){
console.log("get",target[key])
return target[key]
},
set(target,key,value){
console.log("set",target,key,value)
}
})
上面的代码生成了一个obj的代理变量 proxy,其中包含两个函数,get,set,不一样的是,proxy的不会对原对象的变化产生反应,只有代理变量proxy变化时才会反应。
但是在proxy中是可以改变原变量的。我们拿Object.defineProperty中data属性的例子举例。
<div id="box"> </div>
<script>
let box = document.querySelector("#box")
let obj = {}
let proxy = new Proxy(obj,{
get(target,key){
console.log("get",target[key])
return target[key]
},
set(target,key,value){
console.log("set",target,key,value)
if(key === "data"){
box.innerHTML = value
}
target[key] = value
}
})
</script>
即修改原对象,又添加了data的响应式拦截。
除了get和set之外,proxy还有一个方法has(),不过用处很少,此处扩展一下。
let obj = {}
obj.name = "starry"
let proxy = new Proxy(obj,{
// has(){
// return false
// }
})
let obj = {}
obj.name = "starry"
let proxy = new Proxy(obj,{
has(){
return false
}
})
三、对于其他数据类型的拦截
- 对于Set的拦截
let s = new Set()
let proxy = new Proxy(s,{
get(){
console.log("get")
},
set(){
console.log("set")
}
})
看结果好像是成功拦截了,但是看最后的set的size方法其实还存在很多问题。
使用add直接报错,你可能会说这肯定啊,此时代理身上又没有这些函数,这个时候使用target把set身上自带的函数拿过来就OK了。
let s = new Set()
let proxy = new Proxy(s,{
get(target,key){
return target[key]
},
set(){
console.log("set")
}
})
结果是显而易见的报错,原因也很简单,Set方法调用时,比如add,它的指针是指向外层的,直接代理过来,指向proxy自然而然报错了。所以这个时候让代理返回的函数this指向指回Set就可以了!
let s = new Set()
let proxy = new Proxy(s,{
get(target,key){
let res = target[key]
if(res instanceof Function){
//bind call apply
return res.bind(target)
}
return res
},
set(){
console.log("set")
}
})
你成功了你知道吗!
- 对于Map的拦截
let s = new Map()
let proxy = new Proxy(s,{
get(target,key){
let res = target[key]
if(res instanceof Function){
return res.bind(target)
}
return res
},
set(){
console.log("set")
}
})
和Set基本一样,需要注意的也是对于方法需要注意this的指向。
最后总结,Proxy本质属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
这里回顾一下bind、call、apply,在 JavaScript 中,bind、call 和 apply 都是用于改变函数的执行上下文(即函数内部的 this 值),以及传递参数给函数的方法。它们之间的区别如下:
1.bind(): bind() 方法返回一个新的函数,该函数会将指定的对象绑定为调用函数时的上下文。bind() 方法不会立即执行函数,而是返回一个绑定了新上下文的函数。
示例:
javascript
const obj = { name: 'Alice' };
function greet() {
console.log(`Hello, ${this.name}!`);
}
const boundGreet = greet.bind(obj);
boundGreet(); // 输出:Hello, Alice!
2.call(): call() 方法直接调用函数,并将指定的对象作为调用函数时的上下文。此外,call() 方法允许传递一个参数列表给函数。
示例:
javascript
const obj = { name: 'Bob' };
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
greet.call(obj, 'Hola'); // 输出:Hola, Bob!
3.apply(): apply() 方法也是直接调用函数,并将指定的对象作为调用函数时的上下文。与 call() 不同的是,apply() 方法接受一个包含参数的数组或类数组对象作为参数。
示例:
javascript
const obj = { name: 'Carol' };
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
greet.apply(obj, ['Bonjour']); // 输出:Bonjour, Carol!
总结而言:
bind() 方法返回一个新函数,将指定对象作为上下文,并可以延迟执行;
call() 方法直接调用函数,并将指定对象作为上下文,还可以传递参数列表;
apply() 方法也直接调用函数,并将指定对象作为上下文,但是它接受一个参数数组。