vue源码解读--计算属性

本文探讨Vue中computed属性的预处理原因、组件创建与初始化时的不同处理,以及为何只在构造器阶段定义。重点剖析计算属性的计算过程和页面交互触发更新机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
默认情况下页面将渲染出"default",当我们第一次点击onChangeIndex函数后将显示"三岁就会写bug",同时打印出’‘update’’,当再次点击则页面不会有变化,但是仍然打印出"update";当点击onChangeName后页面展示"三岁就会写bug哦",同时打印"update",当再次点击时,则页面无变化同时不会打印"update".

那么为什么会这样呢?

几个小问题

我们之前在分析组件的createComponent和组件的init时候都跳过了部分关于computed的逻辑

在组件创建的过程中,调用extend,判断computed是否存在,然后进行计算属性的init,继而调用defineComputed

在这里插入图片描述
在组件的init过程中,调用initState方法,该方法除了对props、data执行了相关逻辑外,还判断了computed是否存在,并在存在时调用其init,继而调用defineComputed
在这里插入图片描述
也就是说,vue在构建组件构造器时候便已经对compoted进行了处理,而处理函数即defineComputed,那么我现在有3个疑问:a-为什么要提前处理?b-两处处理有什么不同?c-只处理一次行不行?

    首先看extend中的处理

在这里插入图片描述
首先拿到组件的options对象,并对computed进行遍历,并对每一个key(name函数)调用defineComputed,入参为组件的原型、name函数

        再看init过程

在这里插入图片描述
可以看到,两次都是拿到computed的key遍历调用defineComputed,不一样的是,一次传入的target是实例的原型,一次则是vm实例。我们都知道this会首先查找实例本身,若不存在则跟随原型链查找原型对象。且for…in循环具有查找原型链的能力。也就是说,同一个组件,只会在构建构造器时候执行一次。当对组件进行初始化时候,for…in将查找到原型上存在的key,故不会重复多次调用defineComputed。这可以认为是一种"数据共享",是一种优化手段

        所以,之前的疑问可以这么回答:为了避免当在同一个页面中多次使用同一个组件时每次都在组件中定义一遍key造成性能浪费,便利用原型链特性一劳永逸的在构建组件阶段放置到原型链上以避免走重复逻辑(疑问a、b);只在组件上定义会存在a、b提出的性能问题,而只在原型上定义则无法针对如mixin一类后植入的key进行处理(疑问c)

创建过程

当执行组件初始化过程中会调用initState并判断computed存在执行initComputed,传入组件实例和在组件中定义的computed对象(我们这里即name函数)

在这里插入图片描述
–向组件实例挂载_computedWatchers,默认是空对象,并通过Object.create赋予其原型链访问权力

    --isSSR在浏览器环境下为false,进入判断,进行watcher实例化。入参为:组件实例、getter函数(name函数)、noop空函数、{ lazy: true }。实例化watcher的关键信息如下

在计算时作为判断条件
在这里插入图片描述
在这里插入图片描述
–使用for…in循环拿到每一个key,即我们定义的每一个函数

    --向实例的_computedWatchers上绑定一个watcher,从之前文章的分析我们知道watcher将在一定的时机触发update进行patch最终渲染为dom

    --调用defineComputed,将组件实例、每一个计算属性的key及其对应的处理函数(name函数)传入

在这里插入图片描述
–shouldCache为true

            --sharedPropertyDefinition.get对应的是一个匿名函数

在这里插入图片描述
–由于将sharedPropertyDefinition作为Object.defineProperty的属性描述符,故当访问this.name时,将会触发拦截从而调用sharedPropertyDefinition的get方法,而get方法指向上一步返回的computedGetter函数

计算过程–default

    当vue将组件编译为render函数的过程中将对模板中的变量name进行访问,此时将触发sharedPropertyDefinition的get,即computedGetter函数

    --拿到我们在创建过程中保存的每一个computed.key对应的watcher

    --调用evalute函数

在这里插入图片描述
调用get函数,求值
在这里插入图片描述
–向dep.target保存this

–调用getter函数,即name函数
在这里插入图片描述
–调用depend函数
在这里插入图片描述
这实际上调用的是
在这里插入图片描述
当我们点击onChangeIndex函数将手动把saveIndex加加,由于saveIndex是在data中定义的响应式数据,故它将首先走向get方法进行依赖收集,此时dep中将有两个订阅者:计算属性name和saveIndex

在这里插入图片描述
加加的操作则触发saveIndex的set方法,将触发dep的notify
在这里插入图片描述
继而调用watcher的update,由于第一个dep是计算属性的,故只会执行到this.lazy中将this.dirty置为true后便会调用saveIndex对应watcher的update,此次将触发queueWatcher执行render,而在render的过程中将再次访问name,此时this.saveIndex>0成立,获取firstName和lastName并计算返回

在这里插入图片描述
因此,得出的值为"三岁就会写bug"

最后关于"当点击onChangeName后页面展示"三岁就会写bug哦",同时打印"update",当再次点击时,则页面无变化同时不会打印"update".",主要是因为在set拦截中进行了值比较,当相等时则return,因此不会重新render,故而无法调用update

在这里插入图片描述
当其依赖项发生变化时总是会重新求值,但是如果求出的值相等时vue并没有做限制,而是每次都重新计算了一次。这样的不合理可能是由于我当前看的源码版本(2.5.2)较老导致的,毕竟尤大这样的神级人物不会考虑不到这一点

### Vue3 源码解读与分析 Vue3 是现代前端框架之一,其源码设计精巧且模块化程度高。以下是关于 Vue3 源码的详细解读和分析: #### 一、目录结构解析 Vue3 的源码目录结构清晰明了,主要分为以下几个部分: - **`package/`**: 这是 Vue3 的核心源码所在位置,包含了所有的功能实现逻辑[^1]。 - **`scripts/`**: 存放用于构建项目的脚本文件,这些脚本负责编译和打包操作[^1]。 这种分层的设计使得开发者可以快速定位到具体的代码区域并理解其作用。 #### 二、响应式系统的核心机制 Vue3 中引入了基于 Proxy 的全新响应式系统,相比 Vue2 使用的 Object.defineProperty 方法更加高效和灵活。具体来说,在处理嵌套对象或者动态新增属性时,Proxy 提供了天然的支持[^4]。 当我们在一个响应式的对象上定义了一个新的键值对时,如果该对象存在原型链上的父级响应式对象,则可能会触发多次依赖更新的通知。这是因为 JavaScript 的继承特性决定了子对象会尝试查找是否存在对应的 setter/getter 定义在其原型链中[^4]。 ```javascript const obj = {}; const proto = { bar: 1 }; Object.setPrototypeOf(reactive(obj), reactive(proto)); effect(() => { console.log("🚀 ~ child:", obj.bar); }); obj.bar = 2; // 输出两次 "🚀 ~ child:" 表明发生了双重通知现象 ``` 这段代码展示了如何利用 `Object.setPrototypeOf()` 来创建具有特定行为的对象实例,并揭示了潜在的问题以及解决办法——即避免不必要的重复计算。 #### 三、虚拟 DOM 和 Patch 函数的工作原理 在组件重新渲染过程中,Vue 需要对比新旧两棵 Virtual DOM 树之间的差异以便最小化真实 DOM 操作次数。Patch 函数正是完成这项工作的关键角色之一[^2]。 对于按钮类型的节点 (`<button>`) ,当首次挂载时不会进入补丁阶段而是直接执行初始化流程;而对于已存在的元素则调用 `patchElement` 方法来进行增量更新[^2]。 ```javascript function processElement( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) { isSVG = isSVG || (n2.type as string) === 'svg'; if (n1 == null) { mountElement(...); // First Render Logic Here... } else { patchElement(n1, n2, ...args...); } } ``` 以上伪代码片段说明了根据不同条件选择合适的路径来优化性能表现。 #### 四、Tree-Shaking 支持带来的优势 得益于 ES Module 规范的发展和完善,Vue3 可以更好地支持 Tree Shaking 技术从而减少最终产物大小。这意味着未被实际使用的模块将自动排除在外而不增加额外负担[^3]。 此外,Composition API 设计理念也促进了这一点,因为它鼓励按需导入必要的钩子函数而不是整个生命周期管理器[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boJIke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值