前题问题
1.什么是副作用函数?
- 函数的执行导致函数作用域外的变量等发生变化,则该函数称作副作用函数
2.什么是响应式数据?
- 当某个数据发生变化的时候,自动触发了副作用函数,可以成为这个数据为响应式数据
// 以下举例说明副作用函数和响应式数据
// 副作用函数
function effect(){
document.body.innerText = data.text
}
// 响应式函数,可以将普通数据转为响应式数据
function reactive(obj){
// .....
}
const data = reactive({text: 'hello world'})
// 修改数据
setTimeout(() => {
// reactive对原始数据进行处理,此时这里的修改会自动触发effect函数执行
data.text = 'hello vue3'
}, 3000);
// 以上effect 函数执行会就会产生副作用,导致body标签内容发生变化,所以它就是副作用函数
// 定时器内对响应式数据进行了赋值,此时就是致使effect 函数再次执行,而为什么会导致effect执行,这就是后文要讨论的问题
响应式数据的基本实现
Vuejs2.x使用的是 Object.defineProperty
实现, Vuejs3.x 则是实用Proxy
的实现方式
基本思路:
-
将原始数据进行代理,实现 getter 和 setter 函数
-
当执行副作用 effect 函数时,会触发对应数据的 getter 函数,此时将这个 effect 函数保存到容器 bucket 中,等待在未来某时刻执行
-
当执行 data.text = xxx 操作时,会触发对应数据的 setter 函数,此时从容器 bucket 中取出所有 effect 函数并执行它们
// 副作用函数
function effect() {
document.body.innerText = data.text
}
// 响应式函数
function reactive(obj) {
if (typeof obj !== 'object') {
throw new Error('type error')
}
return new Proxy(obj, {
get(target, key) {
// 保存副作用函数 effect
bucket.add(effect)
return target[key]
},
set(target, key, value) {
target[key] = value
// 从容器 bucket 中取出 effect 函数并执行
bucket.forEach(effect => effect())
return true
}
})
}
const obj = { text: 'hello world' }
// 存储副作用函数的容器
const bucket = new Set()
const data = reactive(obj)
// 初始化执行,触发 getter 函数,收集 effect
effect()
// 3s 后触发setter 函数,触发副作用函数执行
setTimeout(() => {
data.text = 'hello vue3'
}, 3000)
以上就是简单的一个响应式实现过程。但是整体仍有许多缺陷。
- 副作用函数effect 硬编码,应该是哪怕副作用函数是匿名的,也能被正确的进行收集到容器中,从而在未来某个班时刻被执行
- 所有的副作用函数都是一股脑全部塞进了Set数据结构的bucket中,副作用函数和被操作目标的字段之间无法建立明确的关系
基于以上等问题4.3节,实现一个完善的响应式系统