概述
computed、watch这两个api在响应式系统是衍生的api,通过proxy实现的响应式逐渐改良,利用缓存及动态监听、同时考虑底层性能优化,相比React的callback、memo等的手动性能优化,vue放开了开发者的双手。
为什么需要调度器?
首先调度器是什么? 调度器是就是调配响应式数据的中心,叫scheduler。本质就是一个回调函数,内部单独处理当前存在的响应式依赖的数据集合的方法。简单理解为一个options配置项。用scheduler我们可以实现对应的数据懒加载,执行顺序的控制,控制执行次数优化性能,同时也抽离代码实现复用。
调度器的简单实现:
effect(() => {
console.log("obj.text");
}, {
scheduler(fn) {
}
})
这里的effect副作用函数相当于响应式对象在读取的时候存入的依赖,一旦监听的对象发生变化,那么effect会被执行。可以简单理解为computed内部挂载的函数就是响应式数据的不同依赖集合。调度器本质在内部通过effect函数内部传入一个scheduler回调,那么意味着每个依赖的逻辑内都有对应的配置项,我们只要监听变化后遍历这个effect。通过Promise的微任务机制来控制执行顺序,用set结构去重防止多次重复执行。
computed、lazy、scheduler是如何配合的?
computed就是计算属性,可以实现缓存,他的本质其实就是依赖其他响应式数据的变化而变化,其本身是被动更新的数据。computed的内部实现其实通过配置scheduler的lazy变量来控制effect执行, 通过回调返回effect,以手动的方式在外部实时监听变化,一旦变化执行此effect函数。同时内部也会被标记dirty,意味着此时对这些依赖会实时监听并渲染。
实现computed !
function computed(getter) {
let value
let dirty = true //检查是否被依赖
//下面等同于你缓存的函数,等同于computed内部的fn()
const effectFn = effect(getter, {
lazy: true, //延迟执行,本质不立马执行effect,通过return暴露方法再执行其实等同于延迟执行
scheduler() {
//通过这个变量控制是否需要根据依赖变化更新,本质就是一个优化
if(!dirty) {
dirty = true
trigger(obj, 'value')
}
}
})
const obj = {
get value() {
//一旦属性对读取,那么得到对应暴露的effect副作用函数
if(dirty) {
value = effectFn()
dirty = false
}
//当读取value,手动调用追踪
track(obj, 'value')
return value
}
}
//返回这个对象,这个对象包含了计算属性,计算属性包含了对应的effect副作用函数
return obj
}
watch实现为什么需要调度器?
watch本质监听数据变化而变化,会触发scheduler内的这个函数,函数可以传入oldVal,newVal以及immediate立即执行属性,deep属性。那么就需要用一个调度器来配置响应式。这些控制本质在收集依赖的effect内部挂载了对应的属性,如果一旦内部存在这个属性那么就执行watch的回调。
watch、immediate原理是啥?
watch实现原理就是一旦set监听到数据改变,那么调度器scheduler在effect副作用函数中执行对应的watch回调。 immediate本质就是一个开关,只要get的时候,我的scheduler内部存在immediate就主动执行当前的effect。
实现watch !
//source代表监听数据,cb是回调,option配置deep、immediate
function watch (source, cb, options = {}) {
let getter //将source的响应式数据改造成副作用函数
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue, newValue
//cleanup存储过期的回调
let cleanup
function onInvalidate (fn) {
cleanup = fn
}
const job = () => {
newValue = effectFn()
if (cleanup) {
cleanup()
}
cb(oldValue, newValue, onInvalidate)
oldValue = newValue
}
const effectFn = effect(
() => getter(),
{
lazy: true,
scheduler: () => {
if (options.flush === 'post') {
const p = Promise.resolve()
p.then(job)
} else {
job()
}
}
}
)
if (options.immediate) {
job()
} else {
oldValue = effectFn()
}
}
总结
简单总结下,computed利用lazy暴露出effect函数,手动执行达到被动延迟响应。watch在set的时候利用scheduler的回调去执行,deep就是递归对象的属性监听的操作,immediate也不过就是一个变量控制是否立即执行,因为默认lazy是延迟的。