持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
响应系统的作用与实现
响应系统是Vue.js的重要组成部分。而响应式数据和副作用又是响应系统的重要组成部分。
响应式数据与副作用函数
function effect() {
document.body.innerText = 'hello vue3'
}
上面函数中,当effect执行便会影响到body中的内容,而除了effect之外,任何函数又都可以获取与设置body中的内容。简单来说就是effect函数的执行会直接或间接影响到其他函数的执行。这时我们就可以说effect产生了副作用。副作用的触发条件很简单,只要修改了全局变量,就算是一个副作用。
var a = 1
function effect(){
a = 2 // 修改全局变量,产生副作用
}
我们知道了什么是副作用函数,那么什么是响应式数据呢?
const obj = { text: "hello world" }
function effect(){
// effect函数会读取obj.text
document.body.innerText = obj.text
}
上述代码中,effect的执行会将body元素的innerText属性设置为obj.text。我们希望得到的是,当obj.text发生变化时,会自动执行effect函数。
如果能实现修改了obj.text的值,副作用函数能自动重新执行,那么我们就说obj对象是响应式的。
响应式数据的基本实现
想要让obj变成响应式数据,我们需要实现两点:
- 当副作用
effect执行时,会触发字段obj.text的读取操作; - 当修改
obj.text的值时,会触发字段obj.text的设置操作。
如果我们能拦截到一个对象的读取与设置,一切就都变的简单明了。
当读取到obj.text时,我们将副作用存储到桶中,当设置obj.text时,再把副作用函数effect从桶中提取出来并执行。
而对于最关键的拦截操作,我们如何才能拦截对象属性的读取和设置呢。在ES2015之前,我们只能通过Object.defineProperty实现,也就是Vue2的实现方式。而如今呢,我们可以用Proxy去实现,Vue3也是这么做的。
// 副作用函数
function effect() {
document.body.innerText = obj.text
}
// 存储副作用函数的桶
const bucket = new Set()
// 原始数据
const data = { text: 'hello world' }
// 对原始数据代理
const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {
// 将副作用函数effect存入副作用函数桶中
bucket.add(effect)
// 返回属性值
return target[key]
},
// 拦截设置操作
set(target, key, newValue) {
// 设置属性值
target[key] = newValue
// 把副作用函数从桶中取出执行
bucket.forEach((fn) => fn())
// 返回执行成功
return true
},
})
我们先创建了一个用于存储副作用函数的桶bucket,将它设置为Set类型。然后定义原始数据data。obj是原始数据类型的代理对象,我们分别设置了set和get拦截函数,用于读取与设置的拦截。当读取时将副作用函数存储到桶中,当设置属性值时,将副作用从桶中取出并执行,这样我们就实现了一个简单的响应式数据。我们可以通过下面的代码测试一下。
setTimeout(() => {
obj.text = 'hello vue3'
}, 1000)
以上就是一个简单的响应式数据的实现,还有这太多不完善的地方,现在我们是直接通过名字(effect)来获取的副作用函数,这样显然是不可取的,如果更换了副作用函数名,该响应式便失效了。那么如何去掉这种硬编码机制,如何理解响应式数据的基本实现和工作原理,我们在下一篇文章中再继续探讨。