用大白话来聊聊Vue2响应式的原理

1,129 阅读4分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

试想一个这样的场景:

我们来试想下面的一个场景. 有一天你在逛掘金, 突然发现, 诶! 这个博主写的文章还不错, 我们关注一下. 这时候你就成为了这个博主的粉丝. 那么, 有一天这个博主发送了一篇新的技术文, 所有在他粉丝列表里的人包括你, 就会接收到消息, 提醒你, 你关注的博主更新了. 你就会去看他的文章, 并且帮他点赞收藏....😋

我们都知道Vue的基本流程就是: 数据一更新, 页面就跟着刷新. Vue2如此, Vue3亦是. 而上面这个场景就可以类比到Vue的流程中.

现在, 我们来看一下上面的场景中, 我们提到了几个角色:

  1. 博主(数据)
  2. 我们(页面)
  3. 掘金(Vue)

还有哪些动作呢?

  1. 关注博主(订阅数据)
  2. 博主更新内容(通知页面)
  3. 我们接收到了博主更新的消息(页面刷新)

我给场景中的角色和动作都加上了在Vue中对应的实例和操作过程, 一般在动作这里, 大家可能喜欢用派发更新, 依赖收集, 来形容发布订阅的过程, 这样的词, 诚然更准确, 但不方便理解. 我相信你看完文章, 自然就明白了这两个词的意思了.

现在我们将场景切回代码中, 这是Vue中一个最基本的页面

<template>
    <h1>I'm {{ blogger }}</h1>
</template>

<script>
export default {
    data() {
        return {
            blogger: 'Link'
        }
    }
}
</script>

在页面首次渲染的时候, 我们会第一次用到blogger这个数据, 我们可以把blogger看成一个好博主. 而我们(页面)关注了这个好博主. 那这时候我们就在博主的粉丝列表里了对吗?

ok, 那我们需要的就是把在读到blogger这个数据的时候, 就把自己添加到关注列表里. 怎么做呢, 为了方便演示, 我将template等价为函数, 实际上在Vue源码中也是这么做的.

  • 定义一个数组代表关注列表, 再定义一个watcher函数代表页面.
let dep = [] // 关注列表
let blogger = 'Link'
function watcher() { // 假装这是页面...😂
    console.log(`I'm ${blogger}`)
    // 页面关注博主, 把自己添加到博主的关注列表里
    dep.push(watcher)
}

这段代码, 显然是逻辑有问题的. 试想下, 我们在掘金中也只是点击关注按钮, 但是把我们添加到关注列表中显然不应该由我们自己来做, 而是掘金做的事情.

我们先把博主丢到data对象里, 然后请出我们的掘金Object.defineProperty

  let dep = [] // 关注列表
+ let data = {
+     blogger: 'link'
+ }
+ function defineReactive() { // 这个函数是掘金平台
+     let val = data.blogger
+     Object.defineProperty(data, 'blogger', {
+       get: function reactiveGetter() {
+           dep.push(watcher)
+           return val
+       }
+     })
+ }
+ defineReactive() // 掘金平台运行起来
  function watcher() { // 假装这是页面...😂
    console.log(`I'm ${data.blogger}`)
  }
  watcher() // 页面运行

这样只要我们运行watcher, 就会访问到blogger, defineReactive就会把watcher添加到dep数组里.好了, 到这里, 我们就把从看文章->关注博主->加入关注列表的事情做完了

现在我们需要考虑的就是当博主更新了, 我们怎么知道呢?

也很简单, 在defineProperty我们加上set逻辑, 当blogger改变了, 就重新执行一遍watcher

  let dep = [] // 关注列表
  let data = {
      blogger: 'link'
  }
  function defineReactive() { // 这个函数是掘金平台
      let val = data.blogger
      Object.defineProperty(data, 'blogger', {
        get: function reactiveGetter() {
            dep.push(watcher)
            return val
        },
 +      set: function reactiveSetter(newVal) {
 +          val = newVal  // 改变我们需要返回的值
 +          dep.forEach(watcher => { // 通知关注列表的watcher重新执行
 +              watcher()
 +          })
 +      }
      })
  }
  defineReactive() // 掘金平台运行起来
  function watcher() { // 假装这是页面...😂
    console.log(`I'm ${data.blogger}`)
  }
  watcher() // 页面运行
  data.blogger = "link2"

这时候, 只要blogger更新, 关注列表里的每一个页面函数watcher都会知道, 并且第一时间更新. image.png

好了. 到这里我们就把场景还原成了代码, 并且实现了一个简单的响应式逻辑. 当然这个逻辑只是为了让你理解原理. 真正的源码要比这个复杂很多. 不过只要原理懂了, 剩下的就好办了, 后面的文章我会完整的实现一个响应式系统, 真正硬核的还在后头哦

但是, 你也肯定发现了, 这个好像是Vue2的实现逻辑呀? 是的. 下篇文章我们来看看vue3是怎么实现响应式的.

感谢😘


如果觉得文章内容对你有帮助: