前言
本文为记录笔者学习《Vue.js设计与实现》一书所写,同时也希望可以给正在学习Vue.js 的朋友一些参考。
受限于篇幅,笔者将会发布三到四篇文章做成一个系列。
读此系列文章你可以收获什么
从了解Vue3 响应式原理,到设计一个完善的响应式系统。
注:笔者将尽可能保证所写代码都可运行。
让我们开始吧
副作用函数
在我们学习响应式之前,让我们来看看副作用函数
function effect() {
document.body.innerHTML = 'Hello World'
}
当我们执行effect 函数的时候,它会设置body 文本内容,但是除了effect 函数外,其他任何函数可以对body 内容进行读取和修改。这时我们就说effect 函数产生了副作用。
理解了副作用函数后,我们再来看响应式
const obj = { text: 'Hello Vue3' }
function effect() {
//effect 函数执行的时候会读取obj.text
document.body.innerHTML = obj.text
}
我们希望在obj.text 值发生改动的时候,body 的文本内容也跟着变化(这个需求听起来似乎很合理)。
obj.text = 'Hello Aganivi'
例如这里将obj.text 的值修改了,如果我们能将effect 函数再执行一次,body 文本将会被重新赋值。
那我们应该怎么做呢?
我们可以使用一个桶,将effect 函数装进去,然后去监听对象obj 的变化,当obj.text 值发生改变的时候,将桶里的effect 函数取出来执行。听起来似乎解决了我们的问题,那么我们应该用什么监听对象obj呢?
使用Proxy 代理
关于Proxy 这里我们只需要知道,它提供了监听功能,让我们可以在对Proxy 对象进行读取和设置操作的时候进行拦截,如下面代码
// 全局变量
let result
// 副作用函数
function effect() {
result = obj.text
}
// 创建‘桶’用于存放effect 函数
const bucket = new Set()
// 原始数据
const data = { text: 'Hello Vue3' }
// 原始数据被代理后的对象
const obj = new Proxy(data, {
//拦截读取操作
get(target, key) {
// 将effect 函数存入‘桶’中
bucket.add(effect)
// 返回属性值
return target[key]
},
// 拦截设置操作
set(target, key, newVal) {
target[key] = newVal
// 将effect 函数重新执行
bucket.forEach((effect) => effect())
// 返回true 代表操作成功
return true
},
})
// 下面代码用于检验响应式是否成功
// 执行副作用函数
effect()
// 打印全局变量
console.log(result)
setTimeout(() => {
// 重新赋值
obj.text = 'Hello Aganivi'
// 二次打印全局变量
console.log(result)
}, 1000)
为了方便运行代码观察结果,我们将effect
函数稍微做了一些修改,如果你想观察effect
函数执行情况,可以在effect
函数中增添一些打印结果。
这里我们新建了bucket
来存放effect
函数,在对对象obj
进行读取操作的时候(即执行effect
函数的时候)我们将effect
函数加入桶中。然后在对obj.text
赋值的时候,将bucket
中的函数取出来执行。
这样一个简易的响应式系统便实现了。但是目前的实现还有许多缺陷,例如我们直接通过名字(effect)来获取副作用函数,这种硬编码方式极其不灵活,我们完全将副作用函数随意名字,甚至使用匿名函数。下篇文章我们再来讲讲如何进行优化。