本文只针对vue3的设计与实现进行展开。后续称
vue
vue响应系统
响应式系统的实现
首先需要先介绍一下副作用函数。
// 什么是副作用函数?
function effect() {
document.body.innerHTML = 'hello world';
}
// effect的执行,会改变body的内容,除了effect函数,别的函数也可以改变body的内容。也就是说,effect的执行,会直接或者间接影响别的函数的执行结果,所以effect函数就是副作用函数。
// 影响全局的副作用函数
var a = 'hello world';
function effect1() {
a = 'hi world';
}
响应式实现的目标是:当数据发生改变的时候,页面会展示最新的结果。下面的例子并不会有这样的效果。effect仅执行了一次。
const obj = {
text: 'hello world'
}
function effect() {
document.body.innerHTML = obj.text
}
effect()
// 此时改变text的值的时候,页面展示的内容并没有发生改变
// 响应式需要的就是当对象属性值发生改变的时候,重新触发副作用函数
obj.text = 'hi world'
此时我们vue利用proxy实现对obj的代理。
const obj = {
text: 'hello world'
}
const bucket = new Set() // 用于存储副作用函数
const proxy = new Proxy(obj, {
get(target,key) {
bucket.add(effect) // 当对象有读操作的时候,把副作用函数添加到bucket中。
return target[key]
},
set(target,key,value) {
target[key] = value
bucket && bucket.forEach(fn => fn()) // 当对象有取操作的时候,将bucket里的函数逐一执行。
return true
}
})
// 副作用函数
function effect() {
document.body.innerHTML = proxy.text
}
effect()
proxy.text = 'hi world'
// 此时页面就会展示 hi world
此时effect仅是一个函数,可以进行一下优化,当effect接受的是一个匿名函数也能执行,看下面代码:
const obj = {
text: 'hello world',
}
const bucket = new Set()
const proxy = new Proxy(obj, {
get(target,key) {
bucket.add(activeEffect) // 此时activeEffect函数就是副作用函数要执行的函数。
return target[key]
},
set(target,key,value) {
target[key] = value
bucket && bucket.forEach(fn => fn())
return true
}
})
// 注册副作用函数
let activeEffect
function effect(fn) {
activeEffect = fn
fn()
}
effect(() => {
document.body.innerHTML = proxy.text
})
proxy.text = 'hi world'
以上的内容,就可以称为一个简易的响应式系统,但是距离一个完善的响应式系统,还有一些距离。
首先是,副作用函数和目标数据的字段没有明确的联系。当设置目标数据的任意字段值,都会触发同样的副作用函数。
一个完善的响应式系统,是需要建立副作用函数与被操纵字段的联系的。如下所示:
target->key1->effect1
->key2->effect2
......->......
具体实现思路如下:
const obj = {
text: 'hello world',
ok: true,
}
// 副作用函数与对象属性的关联关系: target -> key -> effect
const bucket = new WeakMap()
const proxy = new Proxy(obj, {
get(target,key) {
let despMap = bucket.get(target)
if(!despMap) {
bucket.set(target,(despMap = new Map()))
}
let deps = despMap.get(key)
if(!deps) {
despMap.set(key,(deps = new Set()))
}
deps.add(activeEffect)
return target[key]
},
set(target,key,value) {
target[key] = value
let despMap = bucket.get(target)
if(!despMap) return
let effects = despMap.get(key)
effects && effects.forEach(fn => fn())
return true
}
})
// 注册副作用函数
let activeEffect
function effect(fn) {
activeEffect = fn
fn()
}
effect(() => {
console.log('执行了副作用函数');
document.body.innerHTML = obj.ok ? obj.text : '123'
})
obj.text = 1
proxy.text = 'hi world'
上面代码我们分别使用了weakMap、Map、Set三种数据结构
其中:
weakMap是由target->>>Map构成
Map是由key->>>Set构成。
以上便是一个较为完善的响应式系统的实现,也是vue响应式设计的思路。