Vue3&Vue2响应式实现

说到Vue的响应式,这恐怕是我们前端人员在面试的时候必问的问题了吧,还记得我刚大三暑假出来面试的时候,面试官A问:说说你对Vue响应式的理解,3和2两者有什么区别,当时我心里就想这你***的什么呀,老子都没听过

当然是了解过的呀,我解释完了之后面试官说了一句还行,只是不太深入

那今天咱们就来认识认识什么是响应式

一、什么是响应式?

看一下代码,当我们修改num的值时,想要对应的执行某一个引用num的函数

就像Vue中,修改了data里的属性时,对应的computed也会发生改变

上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式

let num = 3;

function changeNum() {
    
    console.log('num被响应式的改变了', num);
    
}

num = 10;

二、怎么实现一个简单的响应式?

我们以Vue3的Proxy为例

    const obj = {
      name: '张三',
      age: 19
    }

    // 类具有更好的收集性
    class Depend {
      constructor() {
        //这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
        this.reactives = new Set()
      }

      addDepend(fn) {
        this.reactives.add(fn)
      }

      notify() {
        this.reactives.forEach(fn => fn && fn())
      }
    }

    //定义一个监听对应函数变化的函数, 形参fn接收变化的函数
    const depend = new Depend()
    function watchFn(fn) {
      depend.addDepend(fn)
    }

    //这里不限于使用箭头函数,匿名函数,命名函数...都可以
    watchFn(() => {
      console.log(`${obj.name}被响应了`)
    })

    watchFn(() => {
      console.log(`${obj.age}被响应了`)
    })

    depend.notify()

    setTimeout(() => {
      obj.name = "curry"
      depend.notify()
    }, 3000);

此时我们就实现了一个简单的响应式

 但是这里有一个问题,我们每次修改obj属性时,得手动去调用notify(),这有点不太响应式

将上面的代码使用Proxy代理监听属性的变化,自动执行notify()

 但是我们看到控制台的打印结果,我们明明是只对name属性进行了修改,但是age的响应函数也被执行了,我们应该让每一个属性对应一个depend依赖

    const obj = {
      name: '张三',
      age: 19
    }

    // 类具有更好的收集性
    class Depend {
      constructor() {
        //这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
        this.reactives = new Set()
      }

      addDepend(fn) {
        this.reactives.add(fn)
      }

      notify() {
        this.reactives.forEach(fn => fn && fn())
      }
    }
    const objProxy = new Proxy(obj, {
      get(target, key) {
        // 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
        return Reflect.get(target, key)
      },
      set(target, key, newValue) {
        Reflect.set(target, key, newValue)
        depend.notify()
      }
    })
    //定义一个监听对应函数变化的函数, 形参fn接收变化的函数
    const depend = new Depend()
    function watchFn(fn) {
      depend.addDepend(fn)
    }

    //这里不限于使用箭头函数,匿名函数,命名函数...都可以
    watchFn(() => {
      console.log(`${objProxy.name}被响应了`)
    })

    watchFn(() => {
      console.log(`${objProxy.age}被响应了`)
    })

    depend.notify()

    setTimeout(() => {
      objProxy.name = "curry"
    }, 3000);

接下来我们来实现属性对应独立的依赖的方法

    const obj = {
      name: '张三',
      age: 19
    }

    // 类具有更好的收集性
    class Depend {
      constructor() {
        //这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
        this.reactives = new Set()
      }

      addDepend(fn) {
        this.reactives.add(fn)
      }

      notify() {
        this.reactives.forEach(fn => fn && fn())
      }
    }

    // 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
    const weakMap = new WeakMap()
    function getDepend(target, key) {
      let map = weakMap.get(target)
      // 在第一次获取的时候肯定是没有对应的依赖的
      if (!map) {
        map = new Map()
        weakMap.set(target, map)
      }

      let depend = map.get(key)
      if (!depend) {
        depend = new Depend()
        map.set(key, depend)
      }

      return depend
    }

    const objProxy = new Proxy(obj, {
      get(target, key) {
        // 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

        // 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
        const depend = getDepend(target, key)
        depend.addDepend(reFn)
        return Reflect.get(target, key)
      },
      set(target, key, newValue) {
        Reflect.set(target, key, newValue)
        // 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可 
        const depend = getDepend(target, key)
        depend.notify()
      }
    })
    //定义一个监听对应函数变化的函数, 形参fn接收变化的函数
    // const depend = new Depend()
    let reFn = null;
    function watchFn(fn) {
      reFn = fn
      fn()
      reFn = null
    }

    //这里不限于使用箭头函数,匿名函数,命名函数...都可以
    watchFn(() => {
      console.log(`${objProxy.name}被响应了`)
    })

    watchFn(() => {
      console.log(`${objProxy.age}被响应了`)
    })

    setTimeout(() => {
      objProxy.name = "curry"
    }, 3000);

 此时我们修改name,就只会执行对应的name依赖,age的响应函数就不会被执行了

但是如果我们有多个对象,就要去再重新写一下new Proxy ,换一种封装的方法,将Proxy也封装起来

    // 类具有更好的收集性
    class Depend {
      constructor() {
        //这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
        this.reactives = new Set()
      }

      addDepend(fn) {
        this.reactives.add(fn)
      }

      notify() {
        this.reactives.forEach(fn => fn && fn())
      }
    }

    // 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
    const weakMap = new WeakMap()
    function getDepend(target, key) {
      let map = weakMap.get(target)
      // 在第一次获取的时候肯定是没有对应的依赖的
      if (!map) {
        map = new Map()
        weakMap.set(target, map)
      }

      let depend = map.get(key)
      if (!depend) {
        depend = new Depend()
        map.set(key, depend)
      }

      return depend
    }
    function reactive(obj) {
      return new Proxy(obj, {
        get(target, key) {
          // 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

          // 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
          const depend = getDepend(target, key)
          depend.addDepend(reFn)
          return Reflect.get(target, key)
        },
        set(target, key, newValue) {
          Reflect.set(target, key, newValue)
          // 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可 
          const depend = getDepend(target, key)
          depend.notify()
        }
      })
    }

    const obj = {
      name: '张三',
      age: 19
    }

    const objProxy = reactive(obj)
    //定义一个监听对应函数变化的函数, 形参fn接收变化的函数
    // const depend = new Depend()
    let reFn = null;
    function watchFn(fn) {
      reFn = fn
      fn()
      reFn = null
    }

    //这里不限于使用箭头函数,匿名函数,命名函数...都可以
    watchFn(() => {
      console.log(`${objProxy.name}被响应了`)
    })

    watchFn(() => {
      console.log(`${objProxy.age}被响应了`)
    })

    setTimeout(() => {
      objProxy.name = "curry"
    }, 3000);

    const infoProxy = reactive({
      address: "金州"
    })

    watchFn(() => {
      console.log(`我的家在${infoProxy.address}`)
    })

    setTimeout(() => {
      infoProxy.address = "勇士"
    }, 5000);

Vue2的方式,就是采用Object.defineProperty 其实这个api在设计时并不是为了响应式而生的,所以我们还是应该使用Proxy

    // 类具有更好的收集性
    class Depend {
      constructor() {
        //这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
        this.reactives = new Set()
      }

      addDepend(fn) {
        this.reactives.add(fn)
      }

      notify() {
        this.reactives.forEach(fn => fn && fn())
      }
    }

    // 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
    const weakMap = new WeakMap()
    function getDepend(target, key) {
      let map = weakMap.get(target)
      // 在第一次获取的时候肯定是没有对应的依赖的
      if (!map) {
        map = new Map()
        weakMap.set(target, map)
      }

      let depend = map.get(key)
      if (!depend) {
        depend = new Depend()
        map.set(key, depend)
      }

      return depend
    }
    function reactive(obj) {
      Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
          get() {
            // 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
            const depend = getDepend(obj, key)
            depend.addDepend(reFn)
            return value
          },
          set(newValue) {
            value = newValue
            // 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可 
            const depend = getDepend(obj, key)
            depend.notify()
          }
        })
      })
      return obj

    }

    const obj1 = {
      name: '张三',
      age: 19
    }

    const objProxy = reactive(obj1)
    //定义一个监听对应函数变化的函数, 形参fn接收变化的函数
    // const depend = new Depend()
    let reFn = null;
    function watchFn(fn) {
      reFn = fn
      fn()
      reFn = null
    }

    //这里不限于使用箭头函数,匿名函数,命名函数...都可以
    watchFn(() => {
      console.log(`${objProxy.name}被响应了`)
    })

    watchFn(() => {
      console.log(`${objProxy.age}被响应了`)
    })

    setTimeout(() => {
      objProxy.name = "curry"
    }, 3000);

    const infoProxy = reactive({
      address: "金州"
    })

    watchFn(() => {
      console.log(`我的家在${infoProxy.address}`)
    })

    setTimeout(() => {
      infoProxy.address = "勇士"
    }, 5000);

致辞,实现完毕,有疑问或者建议欢迎下方留言

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值