笔记Vue4computed分析

computed的应用,传入一个函数:
let x = computed(() => count.value + 3)

新增computed函数

  1. 减少一个computed函数,传入一个函数fn,返回一个蕴含可监听value值的对象
  2. value值置为传入函数执行的后果,再将value返回
  3. 因为须要实现缓存,减少dirty标记记录依赖的值是否变动,只有变动了才从新计算
  4. 计算赋值之后将标记置为false。只有数据没有变动,就不会再从新计算。
  5. 何时将dirty重置为true?在执行传入函数fn时,监听的响应式数据变动之后将dirty重置为true。
  6. 就是在执行notify()时,将所有的依赖进行一次播送,将工作退出队列,之后执行。notify中的dep对应的为watchEffect传入的cb,因而须要革新watchEffect。
  // 传入一个函数,返回一个蕴含可监听value值的对象
  let computed = (fn) => {
    let value
    // 须要设置一个标记记录依赖的值是否变动,只有变动了才从新计算
    let dirty = true
    return {
      get value() {
        if (dirty) {
          // 何时将dirty重置为true
          // 在执行fn时,监听的响应式数据变动之后将dirty重置为true
          // 就是在执行notify()时
          // notify中的dep对应的为watchEffect传入的cb,因而须要革新watchEffect
          value = fn() // value值置为传入函数执行的后果
          // 计算之后将标记置为false。只有数据没有变动,就不会再从新计算
          dirty = false
        }
        return value
      }
    }
  }

革新watchEffect

  1. 新增effect函数,将原来watchEffect中的内容放进去
  2. effect中新建一个_effect函数,将fn额定包装了一层,用于给它增加属性,为了保障fn函数的纯正性
  let active

  let effect = (fn, options = {}) => {
    // _effect 额定包装了一层,用于给它增加属性
    // 为了保障fn函数的纯正性
    let _effect = (...args) => {
      try {
        active = _effect
        return fn(...args) // 须要增加return语句用于computed函数中拿到变动之后的值
      } finally {
        // 无论是否抛出异样最初finally都会执行
        // 这句代码是在`return fn(...args)`后须要执行,因而须要放进try{}finally{}中
        active = null
      }
    }
    _effect.options = options
    return _effect
  }

  // 之前的watch实现的即是watchEffect函数的性能
  let watchEffect = (cb) => {
    /* active = cb
    active()
    active = null */
    // 将原来局部的逻辑提取到effect函数中
    let runner = effect(cb)
    runner()
  }

notify函数中触发

  1. computed函数中须要用effect去代替fn,这样能够增加钩子函数,即传入effect中的options参数
  2. notify中执行钩子函数
  // 传入一个函数,返回一个蕴含可监听value值的对象
  let computed = (fn) => {
    let value
    // 须要设置一个标记记录依赖的值是否变动,只有变动了才从新计算
    let dirty = true
    let runner = effect(fn, {
      schedular() {
        if (!dirty) {
          dirty = true
        }
      }
    })
    return {
      get value() {
        if (dirty) {
          // 何时将dirty重置为true
          // 在执行fn时,监听的响应式数据变动之后将dirty重置为true
          // 就是在执行notify()时
          // notify中的dep对应的为watchEffect传入的cb,因而须要革新watchEffect
          // value = fn() // value值置为传入函数执行的后果
          value = runner()
          // 计算之后将标记置为false。只有数据没有变动,就不会再从新计算
          dirty = false
        }
        return value
      }
    }
  }

  let ref = initValue => {
    let value = initValue
    let dep = new Dep()
    return Object.defineProperty({}, 'value', {
      get() {
        dep.depend()
        return value
      },
      set(newValue) {
        value = newValue
        // active()
        dep.notify()
      }
    })
  }
  // 触发
    notify() {
      this.deps.forEach(dep => {
        queueJob(dep)
        // 执行钩子函数
        dep.options && dep.options.schedular && dep.options.schedular()
      })
    }

残缺代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="add">add</button>
  <div id="app"></div>
</body>
<script>
  let active

  let effect = (fn, options = {}) => {
    // _effect 额定包装了一层,用于给它增加属性
    // 为了保障fn函数的纯正性
    let _effect = (...args) => {
      try {
        active = _effect
        return fn(...args) // 须要增加return语句用于computed函数中拿到变动之后的值
      } finally {
        // 无论是否抛出异样最初finally都会执行
        // 这句代码是在`return fn(...args)`后须要执行,因而须要放进try{}finally{}中
        active = null
      }
    }
    _effect.options = options
    return _effect
  }

  // 之前的watch实现的即是watchEffect函数的性能
  let watchEffect = (cb) => {
    /* active = cb
    active()
    active = null */
    // 将原来局部的逻辑提取到effect函数中
    let runner = effect(cb)
    runner()
  }

  let nextTick = (cb) => Promise.resolve().then(cb)

  // 队列
  let queue = []

  // 增加队列
  let queueJob = (job) => {
    if (!queue.includes(job)) {
      queue.push(job)
      // 增加之后,将执行放到异步工作中
      nextTick(flushJob)
    }
  }

  // 执行队列
  let flushJob = () => {
    while (queue.length > 0) {
      let job = queue.shift()
      job && job()
    }
  }


  let Dep = class {
    constructor() {
      // 寄存收集的active
      this.deps = new Set()
    }
    // 依赖收集
    depend() {
      if (active) {
        this.deps.add(active)
      }
    }
    // 触发
    notify() {
      this.deps.forEach(dep => {
        queueJob(dep)
        // 执行钩子函数
        dep.options && dep.options.schedular && dep.options.schedular()
      })
    }
  }

  // 传入一个函数,返回一个蕴含可监听value值的对象
  let computed = (fn) => {
    let value
    // 须要设置一个标记记录依赖的值是否变动,只有变动了才从新计算
    let dirty = true
    let runner = effect(fn, {
      schedular() {
        if (!dirty) {
          dirty = true
        }
      }
    })
    return {
      get value() {
        if (dirty) {
          // 何时将dirty重置为true
          // 在执行fn时,监听的响应式数据变动之后将dirty重置为true
          // 就是在执行notify()时
          // notify中的dep对应的为watchEffect传入的cb,因而须要革新watchEffect
          // value = fn() // value值置为传入函数执行的后果
          value = runner()
          // 计算之后将标记置为false。只有数据没有变动,就不会再从新计算
          dirty = false
        }
        return value
      }
    }
  }

  let ref = initValue => {
    let value = initValue
    let dep = new Dep()
    return Object.defineProperty({}, 'value', {
      get() {
        dep.depend()
        return value
      },
      set(newValue) {
        value = newValue
        // active()
        dep.notify()
      }
    })
  }

  // 应用:
  let count = ref(0)
  // computedValue 当count.value的值扭转时才变动
  let computedValue = computed(() => count.value + 3)
  document.getElementById('add').addEventListener('click', function () {
    count.value++
  })

  let str
  watchEffect(() => {
    str = `hello ${count.value} ${computedValue.value}`
    document.getElementById('app').innerText = str
  })

</script>

</html>

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理