从vue源码解析Vue.set()和this.$set()
前言
最近死磕了一段时间vue源码,想想觉得还是要输出点东西,我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。Vue.set()和this.$set()应用的场景
平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作,结果发现,咦~~,他喵的,怎么页面没有重新渲染。| 1 2 3 4 5 6 7 8 9 10 | const vueInstance = ``new Vue({ ``data: { ``arr: [1, 2], ``obj1: { ``a: 3 ``} ``}});vueInstance.$data.arr[0] = 3; ``// 这种骚操作页面不会重新渲染vueInstance.$data.obj1.b = 3; ``// 这种骚操作页面不会重新渲染 |
|---|
查了一下官方文档,发现人家早就说过这种情况
Vue.set()向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = ‘hi’)
所以按照官网的写法,我们应该使用下面这种写法:
| 1 2 3 4 | Vue.set(vueInstance.$data.arr, 0, 3); ``// 这样操作数组可以让页面重新渲染vueInstance.$set(vueInstance.$data.arr, 0, 3); ``// 这样操作数组也可以让页面重新渲染Vue.set(vueInstance.$data.obj1, b, 3); ``// 这样操作对象可以让页面重新渲染vueInstance.$set(vueInstance.$data.obj1, b, 3); ``// 这样操作对象也可以让页面重新渲染 |
|---|
Vue.set()和this.$set()实现原理
是时候看一波这两个api的源码了,我们先来看看Vue.set()的源码:| 1 2 3 4 | import { set } from ``'../observer/index'...Vue.set = set... |
|---|
再来看看this.$set()的源码:
| 1 2 3 4 | import { set } from ``'../observer/index'...Vue.prototype.$set = set... |
|---|
结果我们发现Vue.set()和this.$set()这两个api的实现原理基本一模一样,都是使用了set函数。set函数是从 …/observer/index 文件中导出的,区别在于Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上。
接下来我们根据 …/observer/index 中找出set函数:
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | function set (target: Array<any> | Object, key: any, val: any): any { ``if (process.env.NODE_ENV !== ``'production' && ``(isUndef(target) || isPrimitive(target)) ``) { ``warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}) ``} ``if (Array.isArray(target) && isValidArrayIndex(key)) { ``target.length = Math.max(target.length, key) ``target.splice(key, 1, val) ``return val ``} ``if (key ``in target && !(key ``in Object.prototype)) { ``target[key] = val ``return val ``} ``const ob = (target: any).__ob__ ``if (target._isVue || (ob && ob.vmCount)) { ``process.env.NODE_ENV !== ``'production' && warn( ``'Avoid adding reactive properties to a Vue instance or its root $data ' + ``'at runtime - declare it upfront in the data option.' ``) ``return val ``} ``if (!ob) { ``target[key] = val ``return val ``} ``defineReactive(ob.value, key, val) ``ob.dep.notify() ``return val} |
| :— | :— |
我们发现set函数接收三个参数分别为 target、key、val,其中target的值为数组或者对象,这正好和官网给出的调用Vue.set()方法时传入的参数参数对应上。如下图所示:
我们接着往下看:
| 1
2
3
4
5 | if (process.env.NODE_ENV !== ``'production' && ``(isUndef(target) || isPrimitive(target)) ``) { ``warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}) ``} |
| :— | :— |
我们先看isUndef和isPrimitive方法,从名字就可以看出,isUndef是判断target是不是等于undefined或者null。isPrimitive是判断target的数据类型是不是string、number、symbol、boolean中的一种。所以这里的意思是如果当前环境不是生产环境并且 isUndef(target) || isPrimitive(target) 为真的时候,那么就抛出错误警告。
数组的实现原理
接着向下看:| 1 2 3 4 5 | if (Array.isArray(target) && isValidArrayIndex(key)) { ``target.length = Math.max(target.length, key) ``target.splice(key, 1, val) ``return val ``} |
|---|
这里实际就是修改数组时调用set方法时让我们能够触发响应的代码,不过在分析这段代码之前我们来看看vue中的数组实际上是长什么样的。 下图分别是vue中的数组和普通的js数组:


vue中的数组我们命名为arrVue,js中的普通数组命名为arrJs。其实我们平时调用普通数组的push、pop等方法是调用的Array原型上面定义的方法, 从图中我们可以看出arrJs的原型是指向Array.prototype,也就是说 arrJs.proto == Array.prototype 。
但是在vue的数组中,我们发现arrVue的原型其实不是指向的Array.prototype,而是指向的一个对象(我们这里给这个对象命名为arrayMethods)。arrayMethods上面只有7个push、pop等方法,并且arrayMethods的原型才是指向的Array.prototype。所以我们在vue中调用数组的push、pop等方法时其实不是直接调用的数组原型给我们提供的push、pop等方法,而是调用的arrayMethods给我们提供的push、pop等方法。vue为什么要给数组的原型链上面加上这个arrayMethods呢?这里涉及到了vue的数据响应的原理,我们这篇文章暂时不谈论数据响应原理的具体实现。这里你可以理解成vue在arrayMethods对象中做过了特殊处理,如果你调用了arrayMethods提供的push、pop等7个方法,那么它会触发当前收集的依赖(这里收集的依赖可以暂时理解成渲染函数),导致页面重新渲染。换句话说,对于数组的操作,我们只有使用arrayMethods提供的那7个方法才会导致页面渲染,这也就解释了为什么我们使用 vueInstance.$data.arr[0] = 3; 时不会导致页面出现渲染。
搞清楚vue中的数组具体是怎么实现了之后,我们再来看上面的代码:
| 1 2 3 4 5 | if (Array.isArray(target) && isValidArrayIndex(key)) { ``target.length = Math.max(target.length, key) ``target.splice(key, 1, val) ``return val ``} |
|---|
首先if判断当前target是不是数组,并且key的值是有效的数组索引。然后将target数组的长度设置为target.length和key中的最大值,这里为什么要这样做呢?是因为我们可能会进行下面这种骚操作:
| 1 2 | arr1 = [1,3];Vue.set(arr1,10,1) ``// 如果不那样做,这种情况就会出问题 |
|---|
接着向下看,我们发现这里直接调用了target.splice(key, 1, val),在前面我们说过调用arrayMethods提供的push、pop等7个方法可以导致页面重新渲染,刚好splice也是属性arrayMethods提供的7个方法中的一种。
总结一下Vue.set数组实现的原理:其实Vue.set()对于数组的处理其实就是调用了splice方法,是不是发现其实很简单~~
对象的实现原理
我们接着向下看代码:| 1 2 3 4 | if (key ``in target && !(key ``in Object.prototype)) { ``target[key] = val ``return val ``} |
|---|
这里先判断如果key本来就是对象中的一个属性,并且key不是Object原型上的属性。说明这个key本来就在对象上面已经定义过了的,直接修改值就可以了,可以自动触发响应。
关于对象的依赖收集和触发原理我们本文也不做详细解释,你可以暂时先这样理解。vue是使用的Object.defineProperty给对象做了一层拦截,当触发get的时候就会进行依赖收集(这里收集的依赖还是像数组那样,理解成渲染函数),当触发set的时候就会触发依赖,导致渲染函数执行页面重新渲染。那么第一次是在哪里触发get的呢?其实是在首次加载页面渲染的时候触发的,这里会进行递归将对象的属性都依赖收集,所以我们修改对象已有属性值得时候会导致页面重新渲染。这也刚好解释了我们使用 vueInstance.$data.obj1.b = 3; 的时候为什么页面不会重新渲染,因为这里的属性b不是对象的已有属性,也就是说属性b没有进行过依赖收集,所以才会导致修改属性b的值页面不会重新渲染。
我们接着向下看代码:
| 1
2
3
4
5
6
7
8
9
10
11
12 | const ob = (target: any).__ob__ ``if (target._isVue || (ob && ob.vmCount)) { ``process.env.NODE_ENV !== ``'production' && warn( ``'Avoid adding reactive properties to a Vue instance or its root $data ' + ``'at runtime - declare it upfront in the data option.' ``) ``return val ``} ``if (!ob) { ``target[key] = val ``return val ``} |
| :— | :— |
首先定义变量ob的值为 target.ob ,这个 ob 属性到底是什么对象呢?vue给响应式对象都加了一个 ob 属性,如果一个对象有这个 ob 属性,那么就说明这个对象是响应式对象,我们修改对象已有属性的时候就会触发页面渲染。
target._isVue || (ob && ob.vmCount) 的意思是:当前的target对象是vue实例对象或者是根数据对象,那么就会抛出错误警告。
if (!ob) 为真说明当前的target对象不是响应式对象,那么直接赋值返回即可。
接着向下看:
| 1 2 3 | defineReactive(ob.value, key, val) ``ob.dep.notify() ``return val |
|---|
这里其实才是vue.set()真正处理对象的地方。 defineReactive(ob.value, key, val) 的意思是给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染。
ob.dep.notify() 这句代码的意思是触发当前的依赖(这里的依赖依然可以理解成渲染函数),所以页面就会进行重新渲染。
总结
从源码层次看vue提供的vue.set()和this.$set()这两个api还是很简单的,由于本文没有详细解释vue依赖收集和触发,所以有的地方说的还是很模糊。
相关文章:
从vue源码解析Vue.set()和this.$set()
前言 最近死磕了一段时间vue源码,想想觉得还是要输出点东西,我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。 Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作ÿ…...
深入浅出:UniApp 从入门到精通全指南
https://juejin.cn/post/7440119937644101684 uni-app官网 uniapp安卓离线打包流程_uniapp离线打包-CSDN博客 本文是关于 UniApp 从入门到精通的全指南,涵盖基础入门(环境搭建、创建项目、项目结构、编写运行)、核心概念与进阶知识&#x…...
DeepSeek未来发展趋势:开创智能时代的新风口
DeepSeek未来发展趋势:开创智能时代的新风口 随着人工智能(AI)、深度学习(DL)和大数据的飞速发展,众多创新型技术已经逐渐走向成熟,而DeepSeek作为这一领域的新兴力量,正逐步吸引越…...
阻塞队列的实现(线程案例)
一.什么是阻塞队列? 1.如果对于一个满的队列,还要把元素入队列,此时这个队列就会阻塞等待,一直阻塞到这个队列不满为止,从而把这个元素入队列! 2.如果对于一个空的队列,还要从队列拿出元素&…...
http status是什么?常见的http状态码指的是什么意思?
HTTP 状态码 HTTP 状态码(HTTP Status Code)是服务器在响应客户端请求时返回的一个三位数字代码,用于表示请求的处理结果。HTTP 状态码是 HTTP 协议的一部分,帮助客户端(如浏览器或应用程序)了解请求是否成…...
react组件分离,降低耦合
分离前 分离后...
【AI】AI白日梦+ChatGPT 三分钟生成爆款短视频
引言 随着人工智能(AI)技术的快速发展,AI在各个领域都展现出了强大的应用潜力。其中,自然语言处理技术的进步使得智能对话系统得以实现,而ChatGPT作为其中的代表之一,具有自动生成文本的能力,为…...
MYSQL的安装教程
mysql安装分为:普通安装和压缩包安装 压缩包安装很多会存在安装失败的情况,所以我这里就用了普通安装 一、官网下载安装包 www.mysql.com 点击DOWNLOADS: 进入社区版本下载: 点击最下面一行进行下载: 选择第二个离…...
深入解析 C# 中的泛型:概念、用法与最佳实践
C# 中的 泛型(Generics) 是一种强大的编程特性,允许开发者在不预先指定具体数据类型的情况下编写代码。通过泛型,C# 能够让我们编写更灵活、可重用、类型安全且性能优良的代码。泛型广泛应用于类、方法、接口、委托、集合等多个方…...
NUMA架构介绍
NUMA 架构详解 NUMA(Non-Uniform Memory Access,非统一内存访问) 是一种多处理器系统的内存设计架构,旨在解决多处理器系统中内存访问延迟不一致的问题。与传统的 UMA(Uniform Memory Access,统一内存访问…...
数据安全VS创作自由:ChatGPT与国产AI工具隐私管理对比——论文党程序员必看的避坑指南
文章目录 数据安全VS创作自由:ChatGPT与国产AI工具隐私管理对比——论文党程序员必看的避坑指南ChatGPTKimi腾讯元宝DeepSeek 数据安全VS创作自由:ChatGPT与国产AI工具隐私管理对比——论文党程序员必看的避坑指南 产品隐私设置操作路径隐私协议ChatGPT…...
python爬虫:python中使用多进程、多线程和协程对比和采集实践
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 1. 多进程爬虫1.1 python多进程样例1.2 实现多进程爬虫2. 多线程爬虫2.1 python多线程样例2.2 实现多线程爬虫3. 协程爬虫3.1 python协程样例3.2 实现协程爬虫在网络爬虫中,为了提高抓取效率,常常需要使用多进程、多线…...
《OpenCV》—— dlib库
文章目录 dlib库是什么?OpenCV库与dlib库对比dlib库安装dlib——人脸应用实例——人脸检测dlib——人脸应用实例——人脸关键点定位dlib——人脸应用实例——人脸轮廓绘制 dlib库是什么? OpenCV库与dlib库对比 dlib库安装 dlib——人脸应用实例——人脸检…...
Linux搜索---find
find搜索 find 命令的核心功能是在指定的目录路径下,递归地搜索文件和目录,并且可以根据多种条件对搜索结果进行筛选,还能对符合条件的文件和目录执行特定操作。 一、基础语法结构 find [起始目录] [匹配条件] [执行操作] # 基本示例 find…...
python之爬虫入门实例
链家二手房数据抓取与Excel存储 目录 开发环境准备爬虫流程分析核心代码实现关键命令详解进阶优化方案注意事项与扩展 一、开发环境准备 1.1 必要组件安装 # 安装核心库 pip install requests beautifulsoup4 openpyxl pandas# 各库作用说明: - requests&#x…...
Blender常用快捷键的汇总
一、基础操作 全选/取消全选:A(全选)、AA(连续按两次A取消全选)复制物体:Shift D(复制后需点击确认位置)移动物体:G(按X/Y/Z可约束轴向移动)旋转…...
鸿蒙启动页开发
鸿蒙启动页开发 1.1 更改应用名称和图标 1.更改应用图标 找到moudle.json5文件,找到应用启动的EntryAbility下面的icon,将原来的图标改成自己设置的即可 2.更改应用名称 3.效果展示 2.1 广告页面开发 3.1 详细介绍 3.1.1 启动页面 import { PrivacyDialog } fr…...
Unity 文字高度自适应
期望 文字有字号限制,输入文字文字后先判断高度是否适用于限制字号,若处于最小字号时高度任不适用,则调整RectTransform 的高度。 核心代码 每次输入文字时先将字号设定为原始字号。 comp.fontSize fontSize; comp.text content; 拓展T…...
Teaching Small Language Models Reasoning throughCounterfactual Distillation
2024.emnlp-main.333.pdfhttps://aclanthology.org/2024.emnlp-main.333.pdf 1.概述 大型语言模型(LLM),如GPT-3,在各种下游任务中表现出色,包括通过链式思维(CoT)进行问题解答。CoT鼓励模型在解决问题时生成中间推理步骤。尽管LLM取得了成功,但由于模型大小的限制,其…...
快速开始React开发(一)
快速开始React开发(一) React是一个JavaScript库,用于构建交互式网站,并且能够快捷创建SPA(Single Page App),其组件化的思想也是被一再传播,无论是普通的Web网站还是嵌入移动端交互…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
