共计 7781 个字符,预计需要花费 20 分钟才能阅读完成。
目前的副作用函数 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) // 3 | |
console.log(sumRes.value) // 3 | |
console.log(sumRes.value) // 3 | |
console.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) |