【Vue.js设计与实现】第四章:响应式系统的作用与实现(一)

234 阅读3分钟


持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

4.1 响应式数据与副作用函数

4.1.1 副作用函数

副作用函数指的是会产生副作用的函数。 在前面TreeShake的那里有出现过。如下面代码所示:

function effect() {
    document.body.innerHTML = 'hello vue3'
}

effect 函数执行的时候,它会设置body中的文本内容,但是 除了这个函数之外,任何函数都可以对body的文本内容进行读写。 也就是说, effect 函数的执行会间接或直接影响到其他函数的执行,就比如说别的函数把body的文本内容设为hello world,然后你运行 effect 函数把人家body的文本内容改成了hello vue3,这就是影响到其他函数了。 这种时候,我们就说 effect函数产生了副作用

副作用很容易产生,就比如说在函数内部修改外部的全局变量,这就已经产生了副作用了。

4.1.2 响应式数据

假设在一个副作用函数中,读取了某个对象的属性:

const obj = {text: 'hello vue3'}

// effect 函数的执行会读取 obj.text
function effect() {
    document.body.innerText = obj.text
}

然后,重点来了, 我们希望 obj.text 的值发生变化时,副作用函数 effect 会重新执行。 如果能实现这个目标,那么我们就称这个obj对象是响应式数据。

4.2 响应式数据的基本实现

一个数据可以通过以下两条线索来实现响应式。

  1. 当副作用函数 effect 执行的时候,会触发字段 obj.text读取操作
  2. 当修改 obj.text 的值的时候,会触发字段 obj.text设置操作

假设我们可以拦截一个对象的读取和设置操作,那么实现上面的需求就简单了。当我们读取字段 obj.text 的时候,我们可以把副作用函数存储到一个“桶”里,然后当我们设置 obj.text 的时候,我们再把副作用函数从这个“桶”里拿出来执行即可。

那么,如何才能拦截一个对象的读取和设置操作呢?在Vue2中,是通过 Object.defineProperty 函数来实现的,而在Vue3中, 则是通过代理对象 Proxy 来实现的。

image.png

上面的代码虽然实现了响应式,但是 还存在很多缺陷。 比如说,副作用函数逻辑和函数名是紧密耦合的,用人话说就是,我们直接通过名称 effect 来获取副作用函数的,改个名字就完蛋了,这种硬编码的方式很不灵活。我们要想办法去掉这种硬编码的机制,副作用函数的名字可以任意取,甚至是一个匿名函数。

4.3 设计一个完善的响应式系统

从上面我们可以看出 一个响应式系统的工作流程

  • 读取 操作发生时,将副作用函数收集到“桶”中;
  • 设置 操作发生时,从“桶”中取出副作用函数并执行;

上面实现的微型响应式的缺陷就是它的硬编码机制,一旦副作用函数不叫 effect,那段代码就无法正确的工作了。 而一个完善的响应式系统,应该做到 哪怕副作用函数是一个匿名函数,也应该能被正确收集到“桶”里去。

我们可以通过 提供一个用来注册副作用函数的机制 来实现我们的需求。

image.png