0

0

Vue3响应式系统怎么实现computed

WBOY

WBOY

发布时间:2023-05-15 13:13:06

|

1920人浏览过

|

来源于亿速云

转载

首先,我们简单回顾一下:

响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构。

Vue3响应式系统怎么实现computed

WeakMap 的 key 是原对象,value 是响应式的 Map。这样当对象销毁的时候,对应的 Map 也会销毁。

Map 的 key 就是对象的每个属性,value 是依赖这个对象属性的 effect 函数的集合 Set。然后用 Proxy 代理对象的 get 方法,收集依赖该对象属性的 effect 函数到对应 key 的 Set 中。还要代理对象的 set 方法,修改对象属性的时候调用所有该 key 的 effect 函数。

立即学习前端免费学习笔记(深入)”;

上篇文章我们按照这样的思路实现了一个比较完善的响应式系统,然后今天继续实现 computed。

实现 computed

首先,我们把之前的代码重构一下,把依赖收集和触发依赖函数的执行抽离成 track 和 trigger 函数:

Vue3响应式系统怎么实现computed

逻辑还是添加 effect 到对应的 Set,以及触发对应 Set 里的 effect 函数执行,但抽离出来清晰多了。

然后继续实现 computed。

computed 的使用大概是这样的:

const value = computed(() => {
    return obj.a + obj.b;
});

对比下 effect:

effect(() => {
    console.log(obj.a);
});

区别只是多了个返回值。

所以我们基于 effect 实现 computed 就是这样的:

function computed(fn) {
    const value = effect(fn);
   return value
}

当然,现在的 effect 是没有返回值的,要给它加一下:

Vue3响应式系统怎么实现computed

只是在之前执行 effect 函数的基础上把返回值记录下来返回,这个改造还是很容易的。

现在 computed 就能返回计算后的值了:

Vue3响应式系统怎么实现computed

但是现在数据一遍,所有的 effect 都执行了,而像 computed 这里的 effect 是没必要每次都重新执行的,只需要在数据变了之后执行。

所以我们添加一个 lazy 的 option 来控制 effect 不立刻执行,而是把函数返回让用户自己执行。

Vue3响应式系统怎么实现computed

然后 computed 里用 effect 的时候就添加一个 lazy 的 option,让 effect 函数不执行,而是返回出来。

computed 里创建一个对象,在 value 的 get 触发时调用该函数拿到最新的值:

Vue3响应式系统怎么实现computed

我们测试下:

Vue3响应式系统怎么实现computed

可以看到现在 computed 返回值的 value 属性是能拿到计算后的值的,并且修改了 obj.a. 之后会重新执行计算函数,再次拿 value 时能拿到新的值。

只是多执行了一次计算,这是因为 obj.a 变的时候会执行所有的 effect 函数:

Vue3响应式系统怎么实现computed

这样每次数据变了都会重新执行 computed 的函数来计算最新的值。

极品模板多语言企业网站管理系统1.2.2
极品模板多语言企业网站管理系统1.2.2

【极品模板】出品的一款功能强大、安全性高、调用简单、扩展灵活的响应式多语言企业网站管理系统。 产品主要功能如下: 01、支持多语言扩展(独立内容表,可一键复制中文版数据) 02、支持一键修改后台路径; 03、杜绝常见弱口令,内置多种参数过滤、有效防范常见XSS; 04、支持文件分片上传功能,实现大文件轻松上传; 05、支持一键获取微信公众号文章(保存文章的图片到本地服务器); 06、支持一键

下载

这是没有必要的,effect 的函数是否执行应该也是可以控制的。所以我们要给它加上调度的功能:

Vue3响应式系统怎么实现computed

可以支持传入 schduler 回调函数,然后执行 effect 的时候,如果有 scheduler 就传给它让用户自己来调度,否则才执行 effect 函数。

这样用户就可以自己控制 effect 函数的执行了:

Vue3响应式系统怎么实现computed

然后再试一下刚才的代码:

Vue3响应式系统怎么实现computed

可以看到,obj.a 变了之后并没有执行 effect 函数来重新计算,因为我们加了 sheduler 来自己调度。这样就避免了数据变了以后马上执行 computed 函数,可以自己控制执行。

现在还有一个问题,每次访问 res.value 都要计算:

Vue3响应式系统怎么实现computed

能不能加个缓存呢?只有数据变了才需要计算,否则直接拿之前计算的值。

当然是可以的,加个标记就行:

Vue3响应式系统怎么实现computed

scheduler 被调用的时候就说明数据变了,这时候 dirty 设置为 true,然后取 value 的时候就重新计算,之后再改为 false,下次取 value 就直接拿计算好的值了。

我们测试下:

Vue3响应式系统怎么实现computed

我们访问 computed 值的 value 属性时,第一次会重新计算,后面就直接拿计算好的值了。

修改它依赖的数据后,再次访问 value 属性会再次重新计算,然后后面再访问就又会直接拿计算好的值了。

至此,我们完成了 computed 的功能。

但现在的 computed 实现还有一个问题,比如这样一段代码:

let res = computed(() => {
    return obj.a + obj.b;
});
effect(() => {
    console.log(res.value);
});

我们在一个 effect 函数里用到了 computed 值,按理说 obj.a 变了,那 computed 的值也会变,应该触发所有的 effect 函数。

但实际上并没有:

Vue3响应式系统怎么实现computed

这是为什么呢?

这是因为返回的 computed 值并不是一个响应式的对象,需要把它变为响应式的,也就是 get 的时候 track 收集依赖,set 的时候触发依赖的执行:

Vue3响应式系统怎么实现computed

我们再试一下:

Vue3响应式系统怎么实现computed

现在 computed 值变了就能触发依赖它的 effect 了。至此,我们的 computed 就很完善了。

完整代码如下:

const data = {
    a: 1,
    b: 2
}
let activeEffect
const effectStack = [];
function effect(fn, options = {}) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      const res = fn()
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]
      return res
  }
  effectFn.deps = []
  effectFn.options = options;
  if (!options.lazy) {
    effectFn()
  }
  return effectFn
}
function computed(fn) {
    let value
    let dirty = true
    const effectFn = effect(fn, {
        lazy: true,
        scheduler(fn) {
            if(!dirty) {
                dirty = true
                trigger(obj, 'value');
            }
        }
    });
    const obj = {
        get value() {
            if (dirty) {
                value = effectFn()
                dirty = false
            }
            track(obj, 'value');
            console.log(obj);
            return value
        }
    }
    return obj
}
function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
const reactiveMap = new WeakMap()
const obj = new Proxy(data, {
    get(targetObj, key) {
        track(targetObj, key);

        return targetObj[key]
   },
   set(targetObj, key, newVal) {
        targetObj[key] = newVal

        trigger(targetObj, key)
    }
})
function track(targetObj, key) {
    let depsMap = reactiveMap.get(targetObj)
    if (!depsMap) {
    reactiveMap.set(targetObj, (depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if (!deps) {
    depsMap.set(key, (deps = new Set()))
    }
    deps.add(activeEffect)
    activeEffect.deps.push(deps);
}
function trigger(targetObj, key) {
    const depsMap = reactiveMap.get(targetObj)
    if (!depsMap) return
    const effects = depsMap.get(key)
    const effectsToRun = new Set(effects)
    effectsToRun.forEach(effectFn => {
        if(effectFn.options.scheduler) {
            effectFn.options.scheduler(effectFn)
        } else {
            effectFn()
        }
    })
}

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

535

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

21

2026.01.06

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

36

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

59

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

40

2025.11.27

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

4

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

55

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.4万人学习

Vue3.x 核心篇--十天技能课堂
Vue3.x 核心篇--十天技能课堂

共30课时 | 1.5万人学习

Vue3.x新特性篇--十天基础课堂
Vue3.x新特性篇--十天基础课堂

共20课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号