Vue.js---计算属性computed和lazy

news2025/12/17 6:28:17

4.6 计算属性computed和lazy

懒执行的effect:一般的effect一下子就执行了,但是懒加载effect是等需要的时候才会执行

这时我们通过在options中添加lazy属性来达到目的

  function effect (fn , options = {}) {
    const effectFn = () => {
    // 调用clearup函数完成清除工作
    clearUp(effectFn);
    activeEffect = effectFn; 
    // 在调用副作用函数之前将副作用函数压入栈中
    effectStack.push(effectFn);
    fn();
      // 执行完之后抛出,把activeEffect还原成原来的值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    }
    // 将options挂在到fn上
    effectFn.options = options;
    // deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = [];
    // 只有非lazy的时候才会执行副作用函数
    if(!options.lazy){
         effectFn(); 
    }
    return effectFn
  }

那什么时候才执行这个函数呢?

因为我们前面返回了effectFn作为effect函数的返回值,那我们就可以手动调用该副作用函数了

 const effectFn =  effect(
      () => {
      console.log(obj.foo);
    } ,
    // 添加lazy
           {
     lazy : true
    })
    // 手动执行副作用函数
    effectFn();
    obj.foo++;
    obj.foo++;

现在我们要实现假设传递给effect函数的是getter函数,可以返回getter函数的值

  const effectFn =  effect(
      () => {
      obj.foo + obj.bar
    } ,
    // 添加lazy
           {
     lazy : true
    })
    // 手动执行副作用函数
    const value = effectFn();
    console.log(value);

我们希望输出的是 obj.foo + obj.bar的值

实现computed函数

    // computed函数
    // 接收getter作为参数
    function computed (getter) {
      // 将gettwe作为副作用函数,创建一个lazy effect
      const effectFn = effect( getter, {
        lazy : true
      })
    const obj = {
      // 对象的value是一个访问器属性
      // 只有当读取value的值时,才会执行effctFn并将其作为结果返回
      get value(){
        return effectFn()
      }
    }
      // 返回一个对象
      return obj;
    }
    // 开始使用计算属性
     const sumRes = computed(() => obj.foo + obj.bar)
     console.log(sumRes);//3
    

但是有一个bug。多次访问会导致effectFn多次计算

解决—对值进行缓存

    function computed (getter) {
      // value用来缓存
      let value
      // dirty标志,标识是否需要重新计算
      let dirty = true
      // 将gettwe作为副作用函数,创建一个lazy effect
      const effectFn = effect( getter, {
        lazy : true
      })
    const obj = {
      // 对象的value是一个访问器属性
      // 只有当读取value的值时,才会执行effctFn并将其作为结果返回
      get value(){
        // 只有true时才可以进行计算
        if(dirty){
          value =  effectFn()
          // 将dirty设置为false,下一次直接使用缓存到value当中的值
          dirty = false;
        }
        return value
      }
    }
      // 返回一个对象
      return obj;
    }

无论我们执行了多少次sumRes都不会重新执行啦,直接取value里面的值

但是我们如果修改obj.foo的值,我们发现并没有响应的修改最后的sumRes

  get value(){
        // 只有true时才可以进行计算
        if(dirty){
          value =  effectFn()
          // 将dirty设置为false,下一次直接使用缓存到value当中的值
          dirty = false;
        }
        return value
      }

dirty = false;这便是原因

解决:当值发生变化的时候,改变dirty的值就可以啦,这时我们就要使用scheduler选项

    // computed函数
    // 接收getter作为参数
    function computed (getter) {
      // value用来缓存
      let value
      // dirty标志,标识是否需要重新计算
      let dirty = true
      // 将gettwe作为副作用函数,创建一个lazy effect
      const effectFn = effect( getter, {
        lazy : true,
        scheduler(){
          dirty = true
        }
      })
    const obj = {
      // 对象的value是一个访问器属性
      // 只有当读取value的值时,才会执行effctFn并将其作为结果返回
      get value(){
        // 只有true时才可以进行计算
        if(dirty){
          value =  effectFn()
          // 将dirty设置为false,下一次直接使用缓存到value当中的值
          dirty = false;
        }
        return value
      }
    }
      // 返回一个对象
      return obj;
    }
    // 开始使用计算属性
     const sumRes = computed(() => obj.foo + obj.bar)
     console.log(sumRes.value);//4
    obj.foo++
        console.log(sumRes.value);//5

我们的计算属性已经趋于完美了,但是还有一个问题

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

我们希望的是在完成 obj.foo++之后是可以重新渲染的,但是我们发现并没有

其实这是一个典型的effect嵌套,一个计算属性的内部有effect,并且它是懒执行的,只有当真正读取计算属性的值才会执行。

  • 例如,假设我们有一个计算属性 fullName,它依赖于响应式数据 firstNamelastName。只有当我们读取 fullName 的值时,才会执行计算 fullNameeffect

但是getter里面访问的响应式数据只会把computed内部的effect收集为依赖。

而当把计算属性用于另一个effect时,就会发生effect嵌套,外层的effect不会被内层的effect所收集

  • 例如,假设我们有一个 effect 函数 updateUI,它读取 fullName。这时,updateUIeffectfullNameeffect 就会嵌套在一起。

解决—当读取计算属性的值时,我们可以手动调用track函数进行追踪,当计算属性发生变化的时候,手动调用trigger触发响应

  • 当读取计算属性的值时,手动调用 track 函数,将当前 effect(如 updateUI)添加到计算属性的依赖列表中。
  • 这样,当计算属性的依赖发生变化时,可以触发当前 effect
  • 当计算属性的值发生变化时,手动调用 trigger 函数,通知所有依赖该计算属性的 effect 进行更新。
  • 这样,外层的 effect(如 updateUI)会在计算属性(如 fullName)的依赖发生变化时被触发。

完整代码:


  <script setup>
  let activeEffect;
  // effect栈
  const effectStack = [];
  function effect (fn , options = {}) {
    const effectFn = () => {
    // 调用clearup函数完成清除工作
    clearUp(effectFn);
    activeEffect = effectFn; 
    // 在调用副作用函数之前将副作用函数压入栈中
    effectStack.push(effectFn);
    // 将effect的值存储起来
    const res =  fn();
      // 执行完之后抛出,把activeEffect还原成原来的值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
      // 返回res
    return res;
    }
    // 将options挂在到fn上
    effectFn.options = options;
    // deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = [];
    // 只有非lazy的时候才会执行副作用函数
    if(!options.lazy){
         effectFn(); 
    }
    return effectFn
  }
  
  const bucket = new WeakMap();
  const data = { foo : 2 , 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 , newVal);
    }
  });
  // 追踪变化
  function track(target , key){
    if(!activeEffect){
        return;
      }
      // 根据tartget取来的depsMap,它是一个map类型
      let depsMap = bucket.get(target);
      // 如果不存在
      if(!depsMap){
        // 创建一个
        bucket.set(target, (depsMap = new Map()));
      }
      // 根据key取来的deps,它是一个set类型
      let deps = depsMap.get(key);
      // 如果不存在
      if(!deps){
        // 创建一个
        depsMap.set(key, (deps = new Set()));
      }
      deps.add(activeEffect); // 添加当前活跃的副作用函数
      activeEffect.deps.push(deps);
  }
// 触发变化:处理调度逻辑
  function trigger(target, key, newVal) {
    const depsMap = bucket.get(target);
    if (!depsMap) {
      return;
    }
    const effects = depsMap.get(key);
        // 开始执行
    const effectsToRun = new Set(effects);
    effects && effects.forEach(effectFn => {
      if(activeEffect !== effectFn){
        effectsToRun.add(effectFn);
      }
    });
    effectsToRun.forEach(effectFn =>{
      if(effectFn.options.scheduler){
        effectFn.options.scheduler(effectFn);
      }else {
         effectFn();
      }
    });
    // effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数
  }
  // 清除函数
   function clearUp (effectFn){
     // 遍历然后进行删除
     for(let i = 0 ; i < effectFn.deps.length ; i++){
       const deps = effectFn.deps[i];
       // 移除
       deps.delete(effectFn);
     }
     // 最后重置effectFn.deps数组
     effectFn.deps.length = 0;
   }
    
  //   effect(() => {
  //     console.log(obj.foo);
  //   })
  // obj.foo ++;
  //   obj.foo++;
    // 连续执行同一代码,只饭后最后一次计算的结果
    // 定义一个任务集合set:可以进行去重操作
    const jobQueue = new Set();
    // 使用promise.resolve()创建一个promise实例:将一个任务添加到微任务队列当中
    const p = Promise.resolve();
    // 开始刷新队列
    let isFlushing = false;
    function flushJob(){
      // 如果队列正在被刷新->return
      if(isFlushing){
        return;
      }
        // 没有刷新,进行刷新操作
        isFlushing = true;
        p.then(() => {
          jobQueue.forEach(job => job());
          
        }).finally(() => {
          // 结束后重置
          isFlushing = false;
        })
    }
    // computed函数
    // 接收getter作为参数
    function computed (getter) {
      // value用来缓存
      let value
      // dirty标志,标识是否需要重新计算
      let dirty = true
      // 将gettwe作为副作用函数,创建一个lazy effect
      const effectFn = effect( getter, {
        lazy : true,
        scheduler(){
          // 设置value进行trigger
          trigger(obj , 'value');
          dirty = true
        }
      })
    const obj = {
      // 对象的value是一个访问器属性
      // 只有当读取value的值时,才会执行effctFn并将其作为结果返回
      get value(){
        // 只有true时才可以进行计算
        if(dirty){
          value =  effectFn()
          // 将dirty设置为false,下一次直接使用缓存到value当中的值
          dirty = false;
        }
        // 读取value,进行追踪
        track(obj , 'value')
        return value
      }
    }
      // 返回一个对象
      return obj;
    }
    // 开始使用计算属性
     const sumRes = computed(() => obj.foo + obj.bar)
     console.log(sumRes.value);
     obj.foo++
        console.log(sumRes.value);
    effect(() => {
      console.log(sumRes.value)
    })
    obj.foo

</script>

总结:

computed属性的实现我们首先用到了懒加载effect,需要使用的时候才使用。因为我们前面返回了effectFn作为effect函数的返回值,那我们就可以手动调用该副作用函数了。接着我们实现了computed属性,我们是传入一个getter函数和懒加载属性,为了解决多次访问会导致effectFn多次计算,我们需要缓存value。但是我们如果修改obj.foo的值,我们发现并没有响应的修改最后的sumRes,那是因为dirty并没有在修改值之后被修改为true,所以我们就要使用scheduler选项,在 effectoptions 中添加 scheduler,当计算属性的依赖发生变化时,将 dirty 设为 true,以便下次读取 value 时重新计算。为了解决effect嵌套的问题,我们进行了手动追踪和触发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

找客户的app

找客户的 app 在竞争激烈的商业环境中&#xff0c;找客户的 APP 成为企业拓展业务的利器。 微拓客 APP&#xff0c;集智能获客、营销素材、客户管理于一体。支持关键词、附近客源等多方式采集&#xff0c;覆盖 300 行业&#xff1b;一键采集客源&#xff0c;一键导出到通讯录…

​​金融合规革命:​​R²AIN SUITE 如何重塑银行业务智能​

一、市场发展背景与核心驱动因素​ 信息过载​&#xff1a;单家银行年均新增监管文件大量增加&#xff0c;人工解读效率极低。 客户体验升级​&#xff1a;高净值客户期待“724小时专业级响应”&#xff0c;但客户经理难以实时掌握数百款产品动态。 风险防控​&#xff1a;传…

论文阅读:Self-Collaboration Code Generation via ChatGPT

地址&#xff1a;Self-Collaboration Code Generation via ChatGPT 摘要 尽管大型语言模型&#xff08;LLMs&#xff09;在代码生成能力方面表现出色&#xff0c;但在处理复杂任务时仍存在挑战。在现实软件开发中&#xff0c;人类通常通过团队协作来应对复杂任务&#xff0c;…

2025年PMP 学习十五 第10章 项目资源管理

2025年PMP 学习十五 第10章 项目资源管理 序号过程过程组1规划沟通管理规划2管理沟通执行3监控沟通监控 项目沟通管理包括为确保项目的信 息及时且恰当地规划、收集、生成、发布、存储、检索、管理、控制、监 警和最终处理所需的过程&#xff1b; 项目经理绝大多数时间都用于与…

如何使用易路iBuilder智能体平台快速安全深入实现AI HR【实用帖】

随着企业组织经营对降本、增效、提质的需求日益迫切&#xff0c;越来越多企业启动人力资源数智化转型战略。而在AI战略实际推进过程中&#xff0c;企业组织往往在选型、搭建、使用、管控等问题上面临困惑&#xff1a; 如何快速、低成本接入AI能力&#xff0c;实现人力资源管理…

免费实用的远程办公方案​

假如你需要快速检索出远程电脑文件并下载&#xff1f; 假如你需要访问远程电脑的共享文件夹&#xff1f; 假如你需要访问远程电脑的USB设备&#xff0c;例如软件加密狗、调试器、固件烧录器、U盘等&#xff1f; 本篇文章能够解决以上痛点。 这个方案非常实用&#xff0c;也很…

【springboot项目服务假死、内存溢出问题排查】

问题现象&#xff1a;springboot服务A刚启动时正常&#xff0c;但运行几个小时后就会接口请求无响应&#xff0c;但服务器网络、磁盘I/O和CPU都没有出现爆满的情况&#xff0c;且A服务日志没有异常报错。 线上SpringBoot假死现象 SpringBoot应用会出现无法访问的情况。具体的表…

Java 线程状态详解:从创建到销毁的完整旅途

前言 在 Java 多线程编程中&#xff0c;线程的状态管理是理解并发逻辑的核心。本文将用通俗的语言和代码示例&#xff0c;解析线程的6种状态及其转换条件&#xff0c;助你彻底掌握线程的生命周期。 一、线程的6种状态 状态含义NEW线程对象已创建&#xff0c;但未启动&#xf…

操作系统|| 虚拟内存页置换算法

题目 写一个程序来实现 FIFO 和 LRU 页置换算法。首先&#xff0c;产生一个随机的页面引用序列&#xff0c;页面数从 0~9。将这个序列应用到每个算法并记录发生的页错误的次数。实现这个算法时要将页帧的数量设为可变。假设使用请求调页。可以参考所示的抽象类。 抽象类&…

Maven 项目构建时编译错误问题排查与解决

1. 问题描述 Maven 项目执行命令 mvn clean package 时出现编译错误&#xff0c;如下图所示 2. 问题分析 由于是源码编译错误&#xff0c;于是通过查看项目 pom.xml 文件&#xff0c;得到项目源码使用的 Java 版本为 21 <project xmlns"http://maven.apache.org/P…

安全生产调度管理系统的核心功能模块

安全生产调度管理系统是运用现代信息技术构建的智能化管理平台&#xff0c;旨在实现生产安全风险的全面管控和应急资源的优化调度。该系统通过整合物联网、大数据、人工智能等前沿技术&#xff0c;建立起覆盖风险监测、预警预测、指挥调度、决策支持的全链条安全管理体系。 一…

Linux进程信号(一)之信号的入门

文章目录 信号入门1. 生活角度的信号2. 技术应用角度的信号3. 注意4. 信号概念5.用kill -l命令可以察看系统定义的信号列表6. 信号处理常见方式 信号入门 1. 生活角度的信号 你在网上买了很多件商品&#xff0c;再等待不同商品快递的到来。但即便快递没有到来&#xff0c;你也…

基于springboot+vue的机场乘客服务系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 用户管理 航班信…

基于SpringBoot的房屋租赁管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

STM32外设AD/DA-基础及CubeMX配置

STM32外设AD/DA-基础及CubeMX配置 一&#xff0c;什么是AD/DA二&#xff0c;基础概念1&#xff0c;模拟 vs 数字2&#xff0c;AD转换1&#xff0c;分辨率 (Resolution)2&#xff0c;参考电压 (Reference Voltage, Vref)3&#xff0c;采样率 (Sampling Rate) 3&#xff0c;DA转换…

React Native简介

React Native 是由 Meta&#xff08;原 Facebook&#xff09;开源的跨平台移动应用开发框架&#xff0c;基于 React 和 JavaScript&#xff0c;允许开发者使用同一套代码库构建 iOS 和 Android 原生应用。通过 JavaScript 调用原生组件实现高性能渲染。 跨平台开发 共享 80%-9…

GCC 使用说明

参数 -fPIC ppc_85xx-gcc -shared -fPIC liberr.c -o liberr.so -fPIC 作用于编译阶段&#xff0c;告诉编译器产生与位置无关代码(Position-Independent Code)&#xff0c; 则产生的代码中&#xff0c;没有绝对地址&#xff0c;全部使用相对地址&#xff0c;故而代码可以被加…

Verilog HDL 语言整理

Verilog HDL 语言 Verilog HDL 简介 硬件描述语言Hardware Description Language是一种用形式化方法即文本形式 来描述和设计数字电路和数字系统的高级模块化语言 Verilog HDL&#xff08;Hardware Description Language&#xff09;是一种硬件描述语言&#xff0c;用于建模…

车道线检测----Lane-ATT

本文针对车道线检测----Lane-ATT论文所有细节进行阐述&#xff0c;有帮助的话点个收藏关注吧 保持对车道的关注&#xff1a;注意力引导的车道检测 摘要 但许多方法在保持实时效率方面存在问题&#xff0c;这对于自动驾驶车辆至关重要。在本文中&#xff0c;我们提出了LaneATT…

linux安装宝塔面板到数据盘

操作很简单&#xff0c;假如数据盘挂载在cipan1&#xff0c;在数据盘新建目录www&#xff0c;为了方便对应。 执行一下命令&#xff0c;创建软连接 ln -s /cipan1/www www 此时&#xff0c;根目录就出现了www文件夹 下面正常安装宝塔即可