目前的副作用函数effect是立刻执行的:

effect(() => {  console.log(obj.foo)})

在某些场景下并不心愿effect立刻执行, 因而就能够增加options增加属性:

effect(() => {  console.log(obj.foo)},// options{  lazy: true})

这里的lazy就是后面文章介绍的调度, 当options.lazytrue时不立刻执行副作用函数:

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}

其实传递给effectfn才是真正的副作用函数, 而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)