Proxy的使用和Vue3响应式原理

学过Vue2的哥哥姐姐应该知道,Vue2的响应式原理是用了对象属性描述符Object.defineProperty来监听对象进行存取操作,,这样做有个缺点就是无法监听到新增和删除的对象以及数组的length,所以我们知道数据描述符的初衷并不是为了去监听一个完整的对象

一.proxy简单用法

  • 在ES6中新增了一个Proxy类,如果我们希望一个对象的相关操作,我们可以创建一个代理对象,之后对对象的所有操作,都通过代理对象来完成。下面是一个小例子:

const obj = {
     name:'lina',
     age:18
}

const objProxy = new Proxy(obj,{
    get: function(target,key){
        console.log(`监听到访问了${key}值`);
        return target[key]
    },
    set:function(target,key,newValue){
        console.log(`监听到设置${key}值为${newValue}`);
        target[key] = newValue
    }

})
console.log(objProxy.name);
objProxy.age = 20

注意:我们操作的应该代理的对象objProxy,而不是obj

  • Proxy捕获器,主要是用来对对象进行操作,下面是常用的捕获器:

  • apply() 和construct是用来监听函数对象调用和new操作符调用的,例如:

function foo(arge){
    console.log("foo函数被调用",arge, this.end);
    return 'foo'
}
const fooProxy = new Proxy(foo,{
    apply:function(target,thisArg,otherArgs){
        console.log(thisArg,otherArgs);
        console.log("函数的apply监听");
        return target.apply(thisArg,otherArgs)
    },
    construct(target,argArray,newTarget){
        console.log(target,argArray,newTarget);
        return new target()
    }
})
fooProxy(123)
  • Reflact的作用

是ES6新增的一个API,它是一个对象,字面的意思就是反射

  1. 主要提供了很多操作js对象的方法,和Object中操作对象的方法类似

  1. 是因为在早期的ECMA规范中没有考虑到对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面,ES6新增了Reflact,让我们这些操作集中到了Reflact对象上,更加规范

  1. 常用方法和上面类似

  1. Reflect - JavaScript | MDN (mozilla.org) 也可以去MDN进行查阅

  1. 基本使用,如以下代码:

const obj = {
     name:'lina',
     age:18
}

const objProxy = new Proxy(obj,{
    get: function(target,key){
        console.log(`监听到访问了${key}值`);
        return Reflect.get(target,key)
    },
    set:function(target,key,newValue){
        console.log(`监听到设置${key}值为${newValue}`);
        return Reflect.set(target,key,newValue)
    },
    deleteProperty: function(target,key){
        return Reflect.defineProperty(target,key)
    },
    has:function(target,key){
        return Reflect.has(target,key)
    }

})

另外还有一个参数 receiver,这个参数的作用就是如果源对象中存在getter和setter和改变this的指向问题

二.Vue3响应式原理

  • 使用reactive函数将数据对象转换成响应式对象

const data = {count:0}
const state = reactive(data)
  • 在reactive函数内部使用Proxy对象对数据对象进行代理,拦截响应式对象的读取和设置操作

function reactive(obj){
     return new Proxy(obj,{
        get(target,key,receiver){
            //...  收集依赖
           const depend = getDepend(target,key)
           depend.depend()
        },
        set(target,key,value,receiver){
            //...
         const depend = getDepend(obj, key)
        depend.notify()
        }
     })
}
  • 在get拦截器中通过track函数收集依赖,封装一个depend方法收集 ,创建有个weackMap,用于存储每个属性的map 在创建对应的map 用于存储对应的属性值

const targetMap = new WeakMap()
function getDepend(target,key){
    // 根据target对象获取map对象
    let map = targetMap.get(target)
    if(!map){
        map = new Map()
        targetMap.set(target,map)
    }
    // 根据key获取depend对象
    let depend = map.get(key)
    if(!depend){
        depend = new Depend()
        map.set(key,depend)
    }
    return depend
}
  • 创建depend类用于存放更新函数

class Depend{
    constructor(){
       //这里用set是防止重复的更新函数执行,要求重复更新的函数依次执行
        this.reactiveFns = new Set()
    }

    depend(){
         if(activeReactiveFn){
            this.reactiveFns.add(activeReactiveFn)
         }
    }

    notify(){
        this.reactiveFns.forEach(fn=>{
            fn()
        })
    }
}

注意reactiveFns用set的目的是为了防止重复的更新的函数一次执行,造成不必要的多次渲染,因此用

set去除重复的类

  • 分装一个响应式函数

function watchFn(fn){
    activeReactiveFn =fn
    fn()
    activeReactiveFn = null
}

声明一个全局自由变量 activeReactiveFn来收集函数的依赖

  • 完整代码

// 保存当前需要收集的响应式函数
let activeReactiveFn = null

/**
 * Depend优化:
 *  1> depend方法
 *  2> 使用Set来保存依赖函数, 而不是数组[]
 */

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }

  // addDepend(reactiveFn) {
  //   this.reactiveFns.add(reactiveFn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

// 封装一个响应式的函数
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // 根据key获取depend对象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

function reactive(obj) {
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 根据target.key获取对应的depend
      const depend = getDepend(target, key)
      // 给depend对象中添加响应函数
      // depend.addDepend(activeReactiveFn)
      depend.depend()
  
      return Reflect.get(target, key, receiver)
    },
    set: function(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      // depend.notify()
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
  name: "why", // depend对象
  age: 18 // depend对象
})

const infoProxy = reactive({
  address: "广州市",
  height: 1.88
})

watchFn(() => {
  console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})

foo.name = "bar"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值