【笔记】Vue-4-computed分析

码途琉璃狩
• 阅读 1078

Vue3.0 composition API

  • reactive 和ref类似,不同的是接受一个对象作为参数,并且会深度遍历这个对象,对各个属性进行拦截
  • ref 将一个基本类型的值作为入参,然后返回一个响应式并包含一个value属性的对象
  • readonly 只读,set操作时返回警告,不能进行写操作
  • computed 计算属性,基于内部的响应式依赖进行缓存,只有在相关响应式依赖发生改变时才会重新求值

    let x= computed (()=> count.value + 3)
  • watchEffect 数据变化时立即变化,执行后返回一个函数(stop:停止监听数据变化)
  • watch 监听数据变化,并在回调函数中返回数据变更前后的两个值;用于在数据变化后执行异步操作或者开销较大的操作

computed

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>
点赞
收藏
评论区
推荐文章
海军 海军
4年前
关于JavaScript 对象的理解
关于JavaScript对象的理解对象理解对象ECMA262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中的值可以是
Wesley13 Wesley13
3年前
java8新特性
Stream将List转换为Map,使用Collectors.toMap方法进行转换背景:User类,类中分别有id,name,age三个属性。List集合,userList,存储User对象1、指定keyvalue,value是对象中的某个属性值。 Map<Integer,StringuserMap1userList.str
Stella981 Stella981
3年前
Mybatis之foreach用法
在mybatis的xml文件中构建动态sql语句时,经常会用到标签遍历查询条件。特此记录下不同情况下书写方式!仅供大家参考1\.foreach元素的属性collection:需做foreach(遍历)的对象,作为入参时,list、array对象时,collection属性
Wesley13 Wesley13
3年前
Java对象的浅拷贝和深拷贝&&String类型的赋值
Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、方法传参或返回值时,会有值传递和引用(地址)传递的差别。浅拷贝(ShallowCopy):①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,
Stella981 Stella981
3年前
JavaScript学习总结(十七)——Javascript原型链的原理
一、JavaScript原型链ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。在JavaScript中,用__proto__属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript会向上遍历原型
Stella981 Stella981
3年前
Redis都有哪些数据类型
string这是最基本的类型了,就是普通的set和get,做简单的kv缓存hash这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以操作hash里的某个字段。key150value{“id”:
Wesley13 Wesley13
3年前
Go 使用 JSON
Encode将一个对象编码成JSON数据,接受一个interface{}对象,返回\\byte和errfuncMarshal(vinterface{}){byte,err}Marshal函数将会递归遍历整个对象,依次按照成员类型对这个对象进行编码,类型转换如下:1bool类型转换成JSON
Wesley13 Wesley13
3年前
mysql中时间比较的实现
MySql中时间比较的实现unix\_timestamp()unix\_timestamp函数可以接受一个参数,也可以不使用参数。它的返回值是一个无符号的整数。不使用参数,它返回自1970年1月1日0时0分0秒到现在所经过的秒数,如果使用参数,参数的类型为时间类型或者时间类型的字符串表示,则是从1970010100:00:0
Stella981 Stella981
3年前
API之Object.keys()
一、语法Object.keys(obj)参数:枚举自身属性的对象。返回值:一个表示给定对象的所有可枚举属性的字符串数组。二、处理对象,返回可枚举的属性数组varobj{projId:'SM31726327362187',pro
Easter79 Easter79
3年前
Spring两种依赖注入方式的比较
我们知道,Spring对象属性的注入方式有两种:设值注入和构造注入。先看代码:  假设有个类为People,该对象包含三个属性,name和school还有age,这些属性都有各自的setter和getter方法,还有一个包含这三个属性的构造方法。如果用spring来管理这个对象,那么有以下两种方式为People设置属性:  1.设值注入:
小万哥 小万哥
1年前
灵活配置 Spring 集合:List、Set、Map、Properties 详解
使用标签的value属性配置原始数据类型和ref属性配置对象引用的方式来定义Bean配置文件。这两种情况都涉及将单一值传递给Bean。那么如果您想传递多个值,例如Java集合类型,如List、Set、Map和Properties怎么办?为了处理这种情况,S
码途琉璃狩
码途琉璃狩
Lv1
人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
文章
4
粉丝
0
获赞
0