iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Vue3计算属性是如何实现的
  • 957
分享到

Vue3计算属性是如何实现的

2024-04-02 19:04:59 957人浏览 八月长安
摘要

目录计算属性使用微任务优化调度器前言: 本篇内容基于vue3响应式对象是如何实现的(2)实现。 计算属性 Vue3的官方文档中,对于计算属性有这样的描述: 对于任何包含响应式数据的复

前言:

本篇内容基于vue3响应式对象是如何实现的(2)实现。

计算属性

Vue3的官方文档中,对于计算属性有这样的描述:

  • 对于任何包含响应式数据的复杂逻辑,我们都应该使用计算属性
  • 计算属性只会在相关响应式依赖发生改变时重新求值

从上面的描述可以明确计算属性的需求,计算属性计算的是响应式数据(满足描述1),且计算结果应当被缓存(满足描述2)。让我们一个一个来实现,先使用computed创建一个计算属性。

function effect(fn) { // 副作用函数
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  effectFn.deps = [] 
  effectFn()
}
...
const data = { foo: 1, bar: 2 }
const obj = new Proxy(data, { // 响应式对象
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    trigger(target, key)
    return true
  }
})
...
const sumRes = computed(() => obj.foo + obj.bar) // (1)
console.log(sumRes.value)

在(1)处,我们简单写了一个计算属性的功能,为了实现通过sumRes.value读取计算属性值功能,在实现计算属性时,需要返回一个对象,通过对象内的get触发副作用函数。

function computed(getter) {
  const effectFn = effect(getter)
  const obj = {
    get value() {
      return effectFn()
    }
  }
  return obj
}

但这个函数显然是无法执行的,这是因为前面我们在实现effect时,需要直接执行副作用函数,不需要提供返回值。没有返回值,computed自然无法获取到effect的执行结果。因此,当在计算属性中使用effect时,需要将副作用函数返回给计算属性,由计算属性决定何时执行,而不再由effect立即执行(即懒执行)。

为了实现这点,就需要向effect中添加一个开关lazy,考虑到我们可能将来还需要对effect配置其它特性,我们使用一个对象options来封装这个开关。

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn() // (1)
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res // (2)
  }
  effectFn.deps = []
  effectFn.options = options // (3)
  if (!options.lazy) { // (4)
    effectFn()
  }
  return effectFn // (5)
}

我们在(4)处放置了lazy开关,不需要懒执行的副作用函数同样会自动执行。在(1)(2)(5)处返回了副作用函数的结果,供懒执行使用。同时在(3)处向下传递了options,保证在effect发生嵌套时,也使得副作用函数执行预期的行为。基于上述effect的修改,我们在computed中设置lazy开关。

function computed(getter) {
  const effectFn = effect(getter, { lazy: true })
  const obj = {
    get value() { // (6)
      return effectFn()
    }
  }
  return obj
}
const sumRes = computed(() => obj.foo + obj.bar)

从上图中可以看出,我们已经实现了描述1,即使用计算属性进行响应式数据的计算,当响应式数据的值发生变化时,计算属性的值也会随之改变。但观察上文代码的(6)处,不难发现,无论什么情况下,只要读取sumRes.value的值,就会触发一次副作用函数,使其重新进行可能不必要的执行。所以接着,我们尝试实现描述2,缓存计算属性的结果。

先从最简单的入手,我们用一个变量value来缓存上次计算的值,并添加一个dirty开关,记录是否需要重新触发副作用函数。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { lazy: true })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
  return obj
}

修改之后,缓存的值就能生效了。但这样做产生了一个明显的BUG,当dirty的值被置为false时,无法再变为true,这也就意味着,无论响应式数据obj.barobj.foo如何变化,计算属性的值永远都只能是缓存的值value,如下图所示。

为了解决这个问题,我们需要一种方式,能够在obj.barobj.foo的值变化时,在获取sumRes.value之前,将dirty开关的值置为true。受前面懒加载的启发,我们尝试能不能通过配置options来实现这个功能。

const obj = new Proxy(data, {
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    trigger(target, key)
    return true
  }
})

再来回忆一下响应式对象的整个流程,当响应式对象中的数据被修改时,执行了trigger去触发收集的副作用函数。而在计算属性中,我们不再需要自动的触发副作用函数。所以自然会想到,能否在这个地方将dirty置为true呢?按照这个思路,我们先对trigger进行修改。

function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  const otherFns = new Set()
  fns && fns.forEach(fn => {
    if(fn !== activeEffect) {
      otherFns.add(fn)
    }
  })
  otherFns.forEach(fn => {
    if(fn.options.scheduler) { // (7)
      fn.options.scheduler()
    } else {
      fn()
    }
  })
}

按照前文的思路,我们在(7)处增加了一个判断,如果副作用函数fn的配置项options中含有scheduler函数,我们就执行scheduler而非副作用函数fn。我们称这里的scheduler调度器。相应的,我们在计算属性中添加调度器。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { 
    lazy: true,
    scheduler() { // (8)
      dirty = true
    } 
  })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
  return obj
}

在(8)处我们添加了调度器。添加调度器后,让我们再来串一下整个流程,当响应式数据被修改时,才会执行trigger函数。由于我们传入了调度器,因此trigger函数在执行时不再触发副作用函数,转而执行调度器,此时dirty开关的值变为了true。当程序再往下执行时,由于dirty已经变为true,副作用函数就可以正常被手动触发。效果如下图所示。

到这里为止,计算属性在功能上已经实现完毕了,让我们尝试完善它。在Vue中,当计算属性中的响应式数据被修改时,计算属性值会同步更改,进而重新渲染,并在页面上更新。渲染函数也会执行effect,为了说明问题,让我们使用effect简单的模拟一下。

const sumRes = computed(() => obj.foo + obj.bar)
effect(() => console.log('sumRes =', sumRes.value))

这里我们的预期是当obj.fooobj.bar改变时,effect会自动重新执行。这里存在的问题是,正常的effect嵌套可以被自动触发(这点我们在上一篇博客中已经实现了),但sumRes包含的effect仅会在被读取value时才会被get触发执行,这就导致响应式数据obj.fooobj.bar无法收集到外部的effect,收集不到的副作用函数,自然无法被自动触发。

function computed(getter) {
  let value
  let dirty = true
  const effectFn = effect(getter, { 
    lazy: true,
    scheduler() {
      dirty = true
      trigger(obj, 'value') // (9)
    } 
  })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value') // (10)
      return value
    }
  }
  return obj
}

在(10)处我们手动收集了副作用函数,并当响应式数据被修改时,触发它们。

使用微任务优化调度器

在设计调度器时,我们忽略了一个潜在的问题。

还是先来看一个例子:

effect(() => console.log(obj.foo))
for(let i = 0; i < 1e5; i++) {
  obj.foo++
}

随着响应式数据obj.foo被不停修改,副作用函数也被不断重复执行,在明显的延迟之后,控制台打印出了大量的数据。但在前端的实际开发中,我们往往只关心最终结果,拿到结果显示在页面上。在这种条件下,我们如何避免副作用函数被重复执行呢?

const jobQueue = new Set() // (11)
const p = Promise.resolve() // (12)
let isFlushing = false // (13)
function flushJob() { // (14)
  if (isFlushing) return
  isFlushing = true
  p.then(() => {
    jobQueue.forEach(job => {
      job()
    })
  }).finally(() => {
    isFlushing = false
  })
}

这里我们的思路是使用微任务队列来进行优化。(11)处我们定义了一个Set作为任务队列,(12)处我们定义了一个Promise来使用微任务。精彩的部分来了,我们的思路是将副作用函数放入任务队列中,由于任务队列是基于Set实现的,因此,重复的副作用函数仅会保留一个,这是第一点。接着,我们执行flushJob(),这里我们巧妙的设置了一个isFlushing开关,这个开关保证了被微任务包裹的任务队列在一次事件循环中只会执行一次,而执行的这一次会在宏任务完成之后触发任务队列中所有不重复的副作用函数,从而直接拿到最终结果,这是第二点。按照这个思路,我们在effect中添加调度器。

effect(() => { console.log(obj.foo) }, {
  scheduler(fn) {
    jobQueue.add(fn)
    flushJob()
  }
})

需要注意的是,浏览器在执行微任务时,会把微任务队列中的所有微任务一口气做完。因此,微任务在执行时会阻塞页面的渲染。所以在实际使用时,要注意避免在微任务队列中放置过重的任务,以免引起卡顿。

到此这篇关于Vue3计算属性是如何实现的的文章就介绍到这了,更多相关Vue3计算属性 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Vue3计算属性是如何实现的

本文链接: https://www.lsjlt.com/news/166358.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Vue3计算属性是如何实现的
    目录计算属性使用微任务优化调度器前言: 本篇内容基于Vue3响应式对象是如何实现的(2)实现。 计算属性 Vue3的官方文档中,对于计算属性有这样的描述: 对于任何包含响应式数据的复...
    99+
    2022-11-13
  • Vue3计算属性是怎么实现的
    今天小编给大家分享一下Vue3计算属性是怎么实现的的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。计算属性是 Vue.js 开...
    99+
    2023-06-30
  • Vue3 计算属性computed的实现原理
    目录computed 的函数签名computed 的实现ComputedRefImpl 类总结版本:3.2.31 computed 的函数签名 // packages/reactiv...
    99+
    2022-11-13
    Vue3 计算属性 Vue3 种computed实现原理
  • Vue3 计算属性的用法详解
    目录computed 计算属性说明计算属性使用总结注意上一篇博文说了 vue3 项目的 toRefs 函数和 toRef 函数,今天就稍微总结一下 vue3 的计算属性,其实学过 v...
    99+
    2022-11-13
  • vue如何实现计算属性
    本文小编为大家详细介绍“vue如何实现计算属性”,内容详细,步骤清晰,细节处理妥当,希望这篇“vue如何实现计算属性”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是计算属性模板内的表达式非常便利,但是设计它们...
    99+
    2023-07-04
  • Vue3中的计算属性及侦听器如何使用
    这篇文章主要介绍“Vue3中的计算属性及侦听器如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue3中的计算属性及侦听器如何使用”文章能帮助大家解决问题。计算属性我们知道,在模板中可以直接通...
    99+
    2023-07-06
  • 详解Vue3 中的计算属性及侦听器
    目录计算属性缓存getter 和 setter侦听器配置选项其它写法计算属性 我们知道,在模板中可以直接通过插值语法显示一些data中的数据,但是在某些情况,我们可能需要对数据进行一...
    99+
    2022-11-13
    Vue3 计算属性 Vue3 侦听器
  • Vue3中的计算属性及侦听器怎么使用
    计算属性我们知道,在模板中可以直接通过插值语法显示一些data中的数据,但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算,在...
    99+
    2023-05-14
    Vue3
  • Vue计算属性实现成绩单
    本文实例为大家分享了Vue计算属性实现成绩单,供大家参考,具体内容如下 代码如下: <!DOCTYPE html> <html> <head>...
    99+
    2022-11-12
  • Vue计算属性与监视属性实现方法详解
    目录一、计算属性1、插值语法实现2、通过方法实现3、通过计算属性二、监视属性三、深度监视一、计算属性 在开发中,可以有这样的需求,在属性(date)中,有fistName和lastN...
    99+
    2022-11-13
  • 怎么用redux实现computed计算属性
    这篇文章主要介绍“怎么用redux实现computed计算属性”,在日常操作中,相信很多人在怎么用redux实现computed计算属性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用redux实现com...
    99+
    2023-06-30
  • Vue中计算属性如何使用
    Vue中计算属性如何使用,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、计算属性1.1 概述计算属性归根结底也是属性,它也是跟表现层是时刻同...
    99+
    2022-10-19
  • vue中如何使用计算属性
    这期内容当中小编将会给大家带来有关vue中如何使用计算属性,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、什么是计算属性模板内的表达式非常便利,但是设计它们的初衷是用于...
    99+
    2022-10-19
  • Vue中监视属性和计算属性的区别是什么
    这篇文章主要讲解了“Vue中监视属性和计算属性的区别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Vue中监视属性和计算属性的区别是什么”吧!计算属性顾名思义,计算属性就是计算出来的属...
    99+
    2023-07-04
  • 怎么用Vue计算属性实现成绩单
    这篇文章主要介绍“怎么用Vue计算属性实现成绩单”,在日常操作中,相信很多人在怎么用Vue计算属性实现成绩单问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用Vue计算属性实现成绩单”的疑惑有所帮助!接下来...
    99+
    2023-06-20
  • 一文教会你用redux实现computed计算属性
    目录前言原理分析:总结前言 什么是computed计算属性?它会根据所依赖的数据动态显示新的计算结果, 该计算结果会被缓存起来。如果是Vue开发者,对这个功能并不陌生,而且很常用。对...
    99+
    2022-11-13
  • Vue 计算属性之姓名案例的三种实现方法
    目录一、姓名案例-插值语法实现二、姓名案例-methods实现三、姓名案例-计算属性实现四、计算属性简写效果: 一、姓名案例-插值语法实现 <!DOCTYPE html>...
    99+
    2022-11-13
  • Vue中使用计算属性的方法是什么
    这篇文章主要介绍“Vue中使用计算属性的方法是什么”,在日常操作中,相信很多人在Vue中使用计算属性的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue中使用计算属性的方法是什么”的疑惑有所帮助!...
    99+
    2023-06-21
  • 怎么使用Vue计算属性中reduce方法实现遍历
    今天小编给大家分享一下怎么使用Vue计算属性中reduce方法实现遍历的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。vue计...
    99+
    2023-07-05
  • 如何关闭Vue计算属性自带的缓存功能
    这篇文章主要为大家展示了“如何关闭Vue计算属性自带的缓存功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何关闭Vue计算属性自带的缓存功能”这篇文章吧。使...
    99+
    2022-10-19
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作