Vue3源码reactivity响应式篇之computed计算属性

概述

vue3中,computed函数用于表示计算属性,有惰性求值、响应式追踪依赖的特点。本文将介绍computed的实现原理以及其机制细节。

源码解析

computed计算属性和computed方法、ComputedRefImpl类以及refreshComputed方法有关。

computed方法

computed暴露给外部的就是computed方法,其源码实现如下:

js 复制代码
function computed(getterOrOptions, debugOptions, isSSR = false) {
  let getter;
  let setter;
  if (shared.isFunction(getterOrOptions)) {
    getter = getterOrOptions;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  const cRef = new ComputedRefImpl(getter, setter, isSSR);
  return cRef;
}

computed方法实现比较简单,需要关注参数getterOrOptionsisSSRisSSR默认为false,它在服务端渲染会传值为truedebugOptions用以在开发环境调试。

computed会先判断getterOrOptions是否是函数,若是函数,则将其赋值给getter;当然getterOptions也可以是一个包含getset方法的对象。computed方法返回的是ComputedRefImpl实例,一般我们读取计算属性的值也是读取它的返回值的.value

ComputedRefImpl

ComputedRefImpl用于构造一个计算属性。

ComputedRefImpl的源码实现如下:

js 复制代码
class ComputedRefImpl {
    constructor(fn, setter, isSSR) {
        this.fn = getter; //计算函数
        this.setter = setter; // 设置函数(可选)
        this["_value"] = undefined; // 缓存的结果,计算属性的值
        this.dep = new Dep(this); // 依赖收集器(收集依赖此计算属性的副作用effect)
        this["__v_isRef"] = true; // 表示为ref类型
        this["__v_isReadonly"] = undefined; // 只读标记
        this.deps = undefined; //当前计算属性依赖的响应式集合对象链表头
        this.depsTail = undefined; //链表尾
        this.flags = 16; //状态标记
        this.globalVersion = globalVersion - 1;// 全局版本号,用于脏检查
        this.isSSR = undefined; //服务端渲染标记
        this.next = undefined; //用于在effect链表中指向下一个节点
        this.effect = this; // 指向自身
        this["__V_isReadonly"] = !setter; //若无setter,则表示计算属性是只读的
        this.isSSR = isSSR;//ssr标记赋值
    }
    // 依赖变更调用
    notify() {
        this.flags |= 16;
        if (!(this.flags & 8) && activeSub !== this) {
            batch(this, true);
            return true;
        }
    }
    // 计算属性值访问器
    get value() {
        const link = this.dep.track()
        refreshComputed(this);
        if (link) {
            link.version = this.dep.version;
        }
        return this._value;
    }
    // 计算属性值设置器
    set value(newValue) {
        if (this.setter) {
            this.setter(newValue)
        } else {
            warn("Write operation failed: computed value is readonly");
        }
    }
}

ComputedRefImpl中除了在构造器中定义了相关属性外,就是包含两个属性访问器函数和一个notify方法

notify

当计算属性依赖的响应式值发生变化时,会调用notify方法.notify方法会先设置this.flags标志位的值,将其第4位置为1 ,表示有更新请求;然后判断标志位的第3位是否为1 并且当前激活订阅(副作用)是不是自身,若条件满足,则调用batch方法,将该计算属性添加到更新队列中,进行批量更新,最后返回true,表示更新已调度;若不满足条件,则返回false,表示更新被跳过。

getter

getter属性访问器,会在读取计算属性的值时触发。先进行依赖收集,追踪当前正在运行的effect,然后调用refreshComputed方法进行有条件性的重新计算,若当前计算属性被其他effect依赖,则更新依赖的版本,最后返回this._value

setter

setter属性设置,一般情况下计算属性只是只读的,若this.setter方法存在,则可以调用该方法设置计算属性的值。

refreshComputed方法

refreshComputed方法就是用于进行刷新计算属性的值,满足条件就重新进行计算,得到最新的计算属性的值。

refreshComputed的源码实现如下:

js 复制代码
function refreshComputed(computed) {
    // 检查更新条件
    if (computed.flags & 4 && !(computed.flags & 16)) {
        return;
    }
    // 清除pending状态
    computed.flags & =-17;

    // 全局版本校验 避免重复计算
    if (computed.globalVersion === globalVersion) {
        return;
    }
    computed.globalVersion = globalVersion;

    // 缓存有效性检查 
    if (!computed.isSSR && computed.flags & 128 && (!computed.deps && !computed._dirty || !isDirty(computed))) {
        return;
    }

    // 标记计算状态
    computed.flags |= 2;
    const dep = computed.dep;
    const prevSub = activeSub;
    const prevShouldTrack = shouldTrack;
    activeSub = computed;
    shouldTrack = true;
    try {
       // 依赖收集准备
        prepareDeps(computed);

        // 执行计算函数
        const value = computed.fn(computed._value);
        
        // 值变化检测
        if (dep.version === 0 || hasChanged(value, computed._value)) {
            computed.flags |= 128; // 标记valid
            computed._value = value;  
            dep.version++; // 触发依赖更新
        }
    } catch (err) {
        dep.version++;
        throw err;
    } finally {
        activeSub = prevSub;
        shouldTrack = prevShouldTrack;
        cleanupDep(computed); // 清理过期依赖
        computed.flags &= -3; // 清除computing状态
    }
}

refreshComputed方法会先检查flags的值,若是被移除或者没有更新请求,则直接返回;然后修改flags状态,清除pending状态;比较全局版本号和计算属性的版本号,若二者一样,则返回,避免是在计算属性中修改了响应式属性引起的重新计算;修改响应式的版本号;然后做缓存有效性的检查;若是脏数据,则返回;再次标记flags状态,表示是计算中;将当前effect计算属性切换为activeSub,修改shouldTracktrue,调用prepareDeps进行依赖收集,然后执行计算属性的fn,即传入computed的参数函数,得到新的value,比较计算属性的值是否发生改变,赋值this._value,并将其依赖dep的版本递增,如此会触发依赖该计算属性的副作用effect更新;最后恢复activeSubshouldTrack,清理过期依赖以及清除计算状态。

refreshComputed是计算属性computed的核心方法,依据一些规则判断需要执行fn,获取最新的value以及触发相关依赖。

相关推荐
QQ1__8115175154 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态4 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子4 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室4 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI4 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing4 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者4 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册4 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李4 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢4 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web