likes
comments
collection
share

一文了解你所不知道的Proxy💯前言🐼 这几天准备开始看Vue3相关的源码。针对这个Proxy而言的话,自己理解的不

作者站长头像
站长
· 阅读数 27

前言🐼

这几天准备开始看Vue3相关的源码。针对这个Proxy而言的话,自己理解的不是特别清楚,故今天来彻底且系统的去弄清楚。

Proxy🐕

概念:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法:const p = new Proxy(target, handler)

  1. target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。简而言之就是需要劫持的对象。
  2. handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。(也就是定义一大堆的捕获器,去捕获对于对象的操作行为)。

先上代码:

//定义对象分别有name、age两个属性
let target = {
    name:"xxxx",
    age:20
}
//定义相关捕获器
const handler = {
    set(target,key,value){
        console.log("触发了set方法,key:"+key);
          target[key] = value;
          return true;
    },
    get(target,key){
        console.log("触发了get方法,key:"+key)
        return target[key];
    }
}
//创建Proxy对象
const proxy = new Proxy(target,handler);
proxy.name="1111"   // 打印 “触发了set方法,key:name”
proxy.address="China"  //打印 “触发了set方法,key:address”
console.log(proxy.name); // 打印 “触发了get方法,key:name”

如上述例子所示,我们在初始化Proxy之后,利用Proxy对象进行访问相关属性,便能通过相关捕获器来捕获对象的各类操作。以便去做一些其他的事情。例如我们上述例子中可以在里面去进行年龄的相关校验。

需要注意的是set捕获器应当返回一个布尔值,- 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。

当然,对于我们的Proxy也可以去代理数组对象。它的使用方式也如上述例子相同。

let target = ["红楼梦","水浒传","三国演义"]
const handler = {
    set(target,key,value){
        console.log("触发了set方法,key:"+key)
          target[key] = value;
          return true;
    },
    get(target,key){
        console.log("触发了get方法,key:"+key)
        return target[key];
    }
}
const proxy = new Proxy(target,handler);
//打印 “触发了get方法,key:0”
console.log(proxy[0]); 
//打印 “触发了set方法,key:0”
proxy[0] = "西游记"; 
//打印
   // 触发了get方法,key:push  
   // 触发了get方法,key:length  
   // 触发了set方法,key:3  
   // 触发了set方法,key:length
proxy.push("西游记") 

这里有人可能会有相对应的疑惑:为啥getset都会触发两次呢? 你仔细看实际会发现,它实际上是push内部做了一点事情,被全部捕获出来了。除了第一次触发的get方法,其他的都是对于数组的length属性的访问。

Proxy其实除了setget两个对于属性访问的捕获器之外,还有其他类型的捕获器,这里去MDN上查看,所有针对对象的操作,都大差不差有相对应捕获器来支持,此处不做太多详细说明。

另外还有一个点需要探讨,就是Proxy对象中的this指向问题。

一般来说,我们利用Proxy对象去进行代理时,我们会在他的捕获器中去做一些相关操作,例如我们熟知的Vuesetget中进行了相对于的依赖收集和派发更新。所以在Proxy以及对象中也会有对于相关this的使用场景。那么,在Proxy中this指向是怎么样的呢?

废话不多说,直接上代码。

let target = {
    name:"xxxx",
    age:20
}
const handler = {
    set(target,key,value){
        console.log("触发了set方法,key:"+key)
        console.log(this);
          target[key] = value;
          return true;
    },
    get(target,key){
        console.log("触发了get方法,key:"+key)
        return target[key];
    }
}
const proxy = new Proxy(target,handler);
proxy.name="1111"
proxy.address="China"
console.log(proxy.name);

在上述例子中,我们在set捕获器内打印this。 它的打印结果是:{ set: [Function: set], get: [Function: get] }。那也就是说,在Proxy捕获器内,this他的指向为当前这个handler捕获器对象。

好!!!!我们再看下对象内的this指向:

let target = {
    name:"xxxx",
    age:20,
    show(){
        console.log(this === proxy)   //true
    }
}
const handler = {}
const proxy = new Proxy(target,handler);
proxy.show()

如图所示,我们的对象内this指向为Proxy

好🙉!!!! 那么问题来了。 既然proxy代理的对象,对象内部this指向会指向proxy对象。那么请看下面例子。

let map = new WeakMap();
let target = {
    name:"xxxx",
    age:20,
    saveValue(){
        map.set(this,{name:this.name,age:this.age});
    }
}
const handler = {}
const proxy = new Proxy(target,handler);
proxy.saveValue();
console.log(map.get(target))   //undefined
console.log(map.get(proxy))  //{ name: 'xxxx', age: 20 }

这里详细解释一下:首先,map.get(target) 返回的是undefined。此例子通过saveValue将元素属性以this为键,name以及age为值保存到WeakMap中。但是由于this指向为proxy,所以在外部利用map.get(target)获取的为undefined,而map.get(proxy)获取我们存进去的值。

正因为如此,Proxy对于原生属性代理也是一样!!

const handler = {}
const proxy = new Proxy(new Date(),handler);
console.log(proxy.getDate())   //TypeError: this is not a Date object.

getDate方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。this绑定原始对象,就可以解决这个问题。 像这样子:

const handler = {get(target,key){
    if (key === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, key);
}}
const proxy = new Proxy(new Date(),handler);
console.log(proxy.getDate()) 

对于我们的捕获器而言,相信看过上面的MDN相关文档,会发现它还有着另外一个参数 receiver,那么这个参数是干嘛的呢?

let target = {
    name:"xxxx",
    age:20,
}
const handler = {
    set(target,key,value){
          target[key] = value;
          return true;
    },
    get(target,key,receiver){
        console.log(receiver === this);  //fasle
        console.log(receiver === target) //fasle
        console.log(receiver === proxy)  //true
        return target[key];
    }
}
const proxy = new Proxy(target,handler);
proxy.age;

该例子,我们在get函数中设置了三个等式,我们在上述代码中可以看出,这个receiver也是我们的proxy对象,但是这种说法并不太准确。为啥子呢?请看下面例子。

const parent = {
    get value() {
      return 'xiafighting';
    },
  };
  const handler = {
    get(target, key, receiver) {
      console.log(receiver === proxy);    //false
      console.log(receiver === obj);    //true
      return target[key];
    },
  }
  const proxy = new Proxy(parent,handler);
  const obj = {
    name: 'xxxx',
  };
  Object.setPrototypeOf(obj, proxy);
  console.log(obj.value)  //xiafighting

解释一下:这里我们创建了一个parent对象与他的代理对象。最后将创建好的obj对象原型链设置成为代理对象。我们可以看到结果receiver === proxy结果为false,而receiver === obj结果为true,由此可以看出receiver其实就是为了正确的在捕获器中传递上下文(简单来说就是谁调this指向谁)。它不仅仅指向proxy对象。但是也不要和this搞混淆,捕获器中的this,是指向捕获器本身的。这里可以看上面的例子。

好啦,整个Proxy就已经完毕啦!!

总结

  1. proxy可以用来进行对象代理,它里面定义了一大堆捕获器进行捕获对象操作。
  2. proxy所代理的对象中,this指向proxy对象。
  3. proxy捕获器中,this指向该捕获器对象,而捕获器中receiver参数能更好的帮助我们指向正确的上下文对象。

好啦,讲到这里就结束啦。如果你觉得我讲解的有问题,欢迎提点诺!!!!! 最后如果觉得可以的话,给我留一个❤️❤️❤️❤️❤️!!!

转载自:https://juejin.cn/post/7425603008404733963
评论
请登录