目前的副作用函数effect
是立刻执行的:
effect(() => { console.log(obj.foo)})
在某些场景下并不心愿effect
立刻执行, 因而就能够增加options
增加属性:
effect(() => { console.log(obj.foo)},// options{ lazy: true})
这里的lazy
就是后面文章介绍的调度, 当options.lazy
为true
时不立刻执行副作用函数:
function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } effectFn.options = options options.deps = [] // 只有非懒加载时才执行副作用函数 if (!options.lazy) effectFn() // 将副作用函数作为返回值返回 return effectFn}
因为最初一行将副作用函数裸露在了函数内部因而能够手动执行改副作用函数:
const effectFn = effect(() => { console.log(obj.foo)},// options{ lazy: true})// 手动执行effectFn()
仅仅是这样的意义其实不太大, 但手动执行后如果能够拿到传入effect
函数(fn
)的返回值要好的多:
const effectFn = effect(() => obj.foo + obj.bar,// options{ lazy: true})// val 是 传函数的返回值const val = effectFn()
因而须要对effect
函数进行批改:
function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) // 将fn的后果返回到res中 const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] // 将 res 作为 effectFn 的返回值 return res } effectFn.options = options effectFn.deps = [] // 只有非懒加载时才执行副作用函数 if (!options.lazy) effectFn() // 将副作用函数作为返回值返回 return effectFn}
其实传递给effect
的fn
才是真正的副作用函数, 而effectFn
是对fn
的再包装, 也正因而effectFn
执行后也应该返回fn
得进去的值也就新增了const res = fn()
与return res
说句题外话, 依据前一段时间发的文章<闭包浅谈>, 在这外面新增的res
变量是闭包哦,fn
也正好是回调函数, 当然effectFn
也是闭包
当初实现了懒执行的副作用函数并且能够拿到副作用函数的执行后果, 可实现计算属性了:
function computed (getter) { // 将 getter 作为副作用函数 const effectFn = effect(getter, { lazy: true }) const obj = { // 当读取 value 时才执行 effectFn get value () { return effectFn() } } return obj}
当初能够应用computed
函数创立一个计算属性:
const data = { foo: 1, bar: 2 }const obj = new Proxy(data, { /* ... */ })const sumRes = computed(() => obj.foo + obj.bar)console.log(sumRes.value)
到目前残缺的代码是:
// 贮存副作用函数的桶const bucket = new WeakMap()// 用于贮存被注册的副作用的函数let activeEffect = undefined// 副作用函数栈const effectStack = []function cleanup (effectFn) { for (let itme of effectFn.deps) { itme.delete(effectFn) } effectFn.deps.length = []}function effect (fn, options = {}) { const effectFn = () => { console.log('effect'); cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) // 将fn的后果返回到res中 const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] // 将 res 作为 effectFn 的返回值 return res } effectFn.options = options effectFn.deps = [] // 只有非懒加载时才执行副作用函数 if (!options.lazy) effectFn() // 将副作用函数作为返回值返回 return effectFn}// const data = {// text: 'hello world',// ok: true// }const data = { foo: 1, bar: 2 }const obj = new Proxy(data, { // 拦挡读取操作 get (target, key) { track(target, key) // 返回属性值 return target[key] }, // 拦挡设置操作 set (target, key, newVal) { // 设置属性值 target[key] = newVal trigger(target, key) }})function track (target, key) { // 没有 activeEffect, 间接 return if (!activeEffect) return target[key] // 依据 target 从'桶'中回去 depsMap, 它也是一个 Map 类型: key ---> effects let depsMap = bucket.get(target) // 如果 depsMap 不存在, 则新建一个 Map 并与 target 关联 if (!depsMap) bucket.set(target, (depsMap = new Map())) // 再依据 key 从depsMap 中去的 deps, 它是一个 Set 类型 // 外面存贮所有与以后 key 相干的副作用函数: effects let deps = depsMap.get(key) // 如果 deps 不存在, 同样新建一个 Set 并与 key 关联0 if (!deps) depsMap.set(key, (deps = new Set())) // 最初将以后激活的副作用函数增加到'桶'里 deps.add(activeEffect)}function trigger (target, key) { const depsMap = bucket.get(target) if (!depsMap) return const effects = depsMap.get(key) const effectsToRun = new Set() effects && effects.forEach(effectFn => { // 如果 trigger 触发执行的副作用函数与以后正在执行的函数雷同则不触发执行 if (effectFn !== activeEffect) { effectsToRun.add(effectFn) } }) effectsToRun.forEach(effectFn => { // 如果一个副作用函数有调度器则调用改调度器, 并将副作用函数作为参数传递 if (effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { // 否则间接执行副作用函数 effectFn() } })}function computed (getter) { // 将 getter 作为副作用函数 const effectFn = effect(getter, { lazy: true }) const obj = { // 当读取 value 时才执行 effectFn get value () { return effectFn() } } return obj}const sumRes = computed(() => obj.foo + obj.bar)console.log(sumRes.value) // 3console.log(sumRes.value) // 3console.log(sumRes.value) // 3console.log(sumRes.value) // 3
在函数effectFn
中打印了字符串'effect'会发现sumRes.value
取了四次, 函数effectFn
就执行了四次:
因而在computed
须要增加值得缓存:
function computed(getter) { // value 用来缓存上一次计算的值 let value // dirty 标记, 用来标识是否须要从新计算, 为 true 标识须要计算 let dirty = true const effectFn = effect(getter, { lazy: true }) const obj = { get value () { if (dirty) { value = effectFn() // 将 dirty 设置为 false, 下一次间接拜访 value 中存储的值 dirty = false } return value } } return obj}
此时的确只会计算一次, 且每次拜访不会从新执行副作用函数, 然而置信聪慧如你曾经发现问题了, 如果咱们扭转obj
中的值后在拜访sumRes.value
会发现拜访的值没有变动, 这里就不做演示了. 解决办法就是当其中的某一个值放生扭转时将dirty
从新设为true
就能够了, 这时咱们能够增加调度器(请参看: 执行调度):
function computed(getter) { // value 用来缓存上一次计算的值 let value // dirty 标记, 用来标识是否须要从新计算, 为 true 标识须要计算 let dirty = true const effectFn = effect(getter, { lazy: true, // 增加调度器, 将 dirty 重置 scheduler () { dirty = true } }) const obj = { get value () { if (dirty) { value = effectFn() // 将 dirty 设置为 false, 下一次间接拜访 value 中存储的值 dirty = false } return value } } return obj}
当初根本完满了, 只是在某些状况下呈现一个缺点:
const sumRes = computed(() => obj.foo + obj.bar)effect(() => { // 在副作用函数中读取计算属性 console.log(sumRes.value) // 3})obj.bar++
在obj.bar++
期间望是触发计算属性, 从新渲染, 但实际上并没有, 起因是因为计算属性是有本人的effect
并且是懒执行的, 只有真正在读取计算属性的值才会执行. 对于计算属性的getter
函数, 它外面拜访的响应数据只会把计算属性函数外部的effect
收集为依赖, 而在下面的例子中把计算属性用于另一个effect
时就产生了effect
嵌套, 且外层的effect
不会被内层的effect
中的响应式数据收集
从computed
函数中咱们也能够看到外面从新顶一个了一个对象obj
并手动赋予它get
函数, 并没有像这样:
const data = { foo: 1, bar: 2 }const obj = new Proxy(data, { // 拦挡读取操作 get (target, key) { track(target, key) // 返回属性值 return target[key] }, // 拦挡设置操作 set (target, key, newVal) { // 设置属性值 target[key] = newVal trigger(target, key) }})
应用代理, 两个其实是齐全离开的, 只是应用了同一个副作用函数
解决办法很简略既然计算属性没有绑定跟踪那就手动调用:
function computed(getter) { // value 用来缓存上一次计算的值 let value // dirty 标记, 用来标识是否须要从新计算, 为 true 标识须要计算 let dirty = true const effectFn = effect(getter, { lazy: true, // 增加调度器, 将 dirty 重置 scheduler () { dirty = true // 当计算属性依赖的响应式数据发生变化时, 手动触发响应 trigger(obj, 'value') } }) const obj = { get value () { if (dirty) { value = effectFn() // 将 dirty 设置为 false, 下一次间接拜访 value 中存储的值 dirty = false } // 当读取 value 时, 手动调用 track 函数进行跟踪 track(obj, 'value') return value } } return obj}
到此就实现了!
外面有许多的闭包...., 及其常见的闭包
目前的残缺代码为:
// 贮存副作用函数的桶const bucket = new WeakMap()// 用于贮存被注册的副作用的函数let activeEffect = undefined// 副作用函数栈const effectStack = []function cleanup (effectFn) { for (let itme of effectFn.deps) { itme.delete(effectFn) } effectFn.deps.length = []}function effect (fn, options = {}) { const effectFn = () => { console.log('effect'); cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) // 将fn的后果返回到res中 const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] // 将 res 作为 effectFn 的返回值 return res } effectFn.options = options effectFn.deps = [] // 只有非懒加载时才执行副作用函数 if (!options.lazy) effectFn() // 将副作用函数作为返回值返回 return effectFn}// const data = {// text: 'hello world',// ok: true// }const data = { foo: 1, bar: 2 }const obj = new Proxy(data, { // 拦挡读取操作 get (target, key) { track(target, key) // 返回属性值 return target[key] }, // 拦挡设置操作 set (target, key, newVal) { // 设置属性值 target[key] = newVal trigger(target, key) }})function track (target, key) { // 没有 activeEffect, 间接 return if (!activeEffect) return target[key] // 依据 target 从'桶'中回去 depsMap, 它也是一个 Map 类型: key ---> effects let depsMap = bucket.get(target) // 如果 depsMap 不存在, 则新建一个 Map 并与 target 关联 if (!depsMap) bucket.set(target, (depsMap = new Map())) // 再依据 key 从depsMap 中去的 deps, 它是一个 Set 类型 // 外面存贮所有与以后 key 相干的副作用函数: effects let deps = depsMap.get(key) // 如果 deps 不存在, 同样新建一个 Set 并与 key 关联0 if (!deps) depsMap.set(key, (deps = new Set())) // 最初将以后激活的副作用函数增加到'桶'里 deps.add(activeEffect)}function trigger (target, key) { const depsMap = bucket.get(target) if (!depsMap) return const effects = depsMap.get(key) const effectsToRun = new Set() effects && effects.forEach(effectFn => { // 如果 trigger 触发执行的副作用函数与以后正在执行的函数雷同则不触发执行 if (effectFn !== activeEffect) { effectsToRun.add(effectFn) } }) effectsToRun.forEach(effectFn => { // 如果一个副作用函数有调度器则调用改调度器, 并将副作用函数作为参数传递 if (effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { // 否则间接执行副作用函数 effectFn() } })}function computed(getter) { // value 用来缓存上一次计算的值 let value // dirty 标记, 用来标识是否须要从新计算, 为 true 标识须要计算 let dirty = true const effectFn = effect(getter, { lazy: true, // 增加调度器, 将 dirty 重置 scheduler () { dirty = true // 当计算属性依赖的响应式数据发生变化时, 手动触发响应 trigger(obj, 'value') } }) const obj = { get value () { if (dirty) { value = effectFn() // 将 dirty 设置为 false, 下一次间接拜访 value 中存储的值 dirty = false } // 当读取 value 时, 手动调用 track 函数进行跟踪 track(obj, 'value') return value } } return obj}const sumRes = computed(() => obj.foo + obj.bar)effect(() => { // 在副作用函数中读取计算属性 console.log(sumRes.value) // 3})obj.bar++// effect(() => {// console.log('effect run');// document.body.innerText = obj.ok ? obj.text : 'not'// })// setTimeout(() => {// obj.ok = false// }, 2000)