当前位置:   article > 正文

Vue源码分析(响应式)_object _internalvalue

object _internalvalue

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源码分析(插件编写)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/283833?site
推荐阅读
相关标签
  

闽ICP备14008679号