• 猿创征文|Vue源码分析(响应式)


    Vue源码分析(响应式

    写在前面的话:当我们在使用vue的时候,可能会非常好奇,为什么vue能实现这种响应式的数据更新,为什么可以动态渲染。

    这篇文章并不是直接对vue的源码进行阅读,而是通过一些小demo理解vue的作用原理。参考往年尤雨溪的公开课程。

    getter-setter

    我们首先要实现的就是vue的核心数据监听,如何监听对象中属性的变化并修改它。相信大家也都了解,就是Object.defineProperty(vue2)

    定义一个convert函数,实现对象的getter和setter监听

    // 代码中api的用法请参考MDN
    function convert(obj) {
      Object.keys(obj).forEach(key => {
        // 创建internalValue存储对象的值
        let internalValue = obj[key]
        Object.defineProperty(obj, key, {
          get() {
            console.log(`getting key "${key}":${internalValue}`);
            return internalValue
          },
          set(newValue) {
            console.log(`setting key "${key}"to:${newValue}`);
            internalValue = newValue
          }
        })
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    测试效果

    let obj = {
      name: 'mkbird',
      age: 20
    }
    convert(obj)
    obj.name // should log: 'getting key "name":mkbird '
    obj.age = 21 // should log:' setting key "age"to:21 '
    console.log(obj) // { age:21, name:'mkbird' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    观察上面的代码就可以发现使用Object.defineProperty,obj有多少个属性就要绑定多少次,而且还有一些限制,于是在vue3中使用了proxy

    依赖跟踪

    创建一个Dep依赖跟踪类,它包含两个方法

    • depend用于收集依赖项
    • notify用于触发依赖项执行

    下面是Dep类的效果,调用dep.depend收集依赖,当调用dep.notify时,会再次执行依赖项

    autorun函数是接收一个函数,这个函数帮助我们创建一个响应区,当代码放在这个响应区内,就可以通过dep.depend方法注册依赖项

    // 即我们期望的功能为
    const dep = new Dep()
    
    autorun(() => {
      dep.depend()
      console.log('updated')
    })
    // should log: "updated"
    
    dep.notify()
    // should log: "updated"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们可以将上面的过程理解为发布订阅模式
    请添加图片描述

    整个代码分为两个部分

    1. 能全局保存依赖项的autorun函数
    2. Dep类创建订阅任务队列,添加依赖项和执行依赖项
    window.Dep = class Dep {
      constructor() {
        // 订阅任务队列
        this.subscribers = new Set()
      }
      // 用于注册依赖项
      depend() {
        if (activeUpdate) {
          this.subscribers.add(activeUpdate)
        }
      }
      // 用于发布消息,触发依赖项重新执行
      notify() {
        this.subscribers.forEach(subscriber => subscriber())
      }
    }
    
    // 下面代码的作用
    // 将当前执行的wrappedUpdate存入activeUpdate全局变量中,用于之后注册依赖
    // 执行update函数本身
    let activeUpdate
    function autorun(update) {
      const wrappedUpdate = () => {
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
      }
      wrappedUpdate()
    }
    // 上面为什么要在autorun中嵌套wrappedUpdate呢
    // 因为本demo只实现了最精简的功能,实际项目中wrappedUpdate还要有依赖筛选等功能
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    测试效果

    const dep = new Dep()
    // 创建想要执行的函数,并且使用dep.depend()表明我想要订阅该事件
    let myFun = () => {
      dep.depend()
      console.log('updated')
    }
    // 在autorun区域内执行并订阅
    autorun(myFun) // should log: "updated"
    // 使用dep.notify()发布事件
    dep.notify() // should log: "updated"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    基于数据监听和依赖跟踪实现Vue的更新系统

    前面已经实现了convert函数用于对象的getter和setter监听,也通过发布订阅模式实现了依赖的跟踪,接下来将二者结合实现vue的数据更新系统。

    我们将convert改名为observe在getter中调用dep.depend订阅事件,在setter中调用dep.notify发布事件,即可实现vue的数据更新。

    // 加一层Object判断
    function isObject(obj) {
      return typeof obj === 'object'
        && !Array.isArray(obj)
        && obj !== null
        && obj !== undefined
    }
    
    // 监听
    function observe(obj) {
      if (!isObject(obj)) {
        throw new TypeError()
      }
      // 遍历属性
      Object.keys(obj).forEach(key => {
        let internalValue = obj[key]
        let dep = new Dep()
        Object.defineProperty(obj, key, {
          get() {
            // 订阅
            dep.depend()
            return internalValue
          },
          set(newValue) {
            if (internalValue !== newValue) {
              internalValue = newValue
              // 发布
              dep.notify()
            }
          }
        })
      })
    }
    
    // 创建dep
    window.Dep = class Dep {
      constructor() {
        this.subscribes = new Set()
      }
      depend() {
        // 订阅
        if (activeUpdate) {
          this.subscribes.add(activeUpdate)
        }
      }
      notify() {
        // 发布
        this.subscribes.forEach(subscriber => subscriber())
      }
    }
    
    // 创建事件辅助函数
    let activeUpdate = null
    function autorun(update) {
      function wrappedUpdate() {
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
      }
      wrappedUpdate()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    测试代码

    const state = {
      a: 0
    }
    // 监听数据
    observe(state)
    // 创建事件
    autorun(() => {
      console.log(state.a); // should log: 0
    })
    state.a++  // should log: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    下一节:Vue源码分析(插件编写)

  • 相关阅读:
    2023影视源接口分享 影视仓配置接口大全 TVBox接口地址源 订阅源地址大全
    停车场智能化管理:车位引导系统实现车位资源优化与数据分析
    请求参类型params,json,data 含义理解
    【SQLServer】并行的保留线程和已使用线程
    兴达易控DP主站转TCP把ABB流量计接入到施耐德PLC
    基于SpringBoot+Vue的疫苗接种管理系统
    007-JAVA循环语句详细介绍
    百度文心一言 4.0 :如何申请百度文心一言 4.0
    【二分图】 二分图上匹配问题 和 匈牙利算法正确性说明
    Ubantu GoLand安装
  • 原文地址:https://blog.csdn.net/qq_47234456/article/details/126755740