从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依赖收集和触发,所以有的地方说的还是很模糊。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IO学习day3

一、思维导图 二、练习 1、使用文件IO读取图片 文件大小&#xff0c;文件偏移量&#xff0c;宽度&#xff0c;高度 2.向一个程序中输入文件名&#xff0c;判断指定目录下是否有这个文件&#xff0c;如果有这个文件, 将这个文件的属性信息输出。如果不存在输出不存在即可。 .…

MWC 2025|紫光展锐联手美格智能发布5G通信模组SRM812

在2025年世界移动通信大会&#xff08;MWC 2025&#xff09;期间&#xff0c;紫光展锐携手美格智能正式推出了基于紫光展锐V620平台的第二代5G Sub6G R16模组SRM812&#xff0c;以超高性价比方案&#xff0c;全面赋能合作伙伴&#xff0c;加速5G规模化应用在各垂直领域的全面落…

场景题:10亿QQ用户,如何统计在线人数?

现在卷的环境下&#xff0c;面试除了八股文算法项目外&#xff0c;场景题也是问的越来越多了。一方面是就业市场竞争者较多所带来的必然结果&#xff1b;另一方面是公司对于应聘者的技术要求也越来越高了。 今天继续介绍Java面试常见的场景题&#xff1a;在线人数统计 现在用户…

Markdown HTML 图像语法

插入图片 Markdown ![图片描述](图片链接)一般来说&#xff0c;直接复制粘贴过来就行了&#xff0c;部分网页/应用可以拖拽&#xff0c;没人会真敲图片的链接吧…… 示例图片&#xff1a; ![Creeper?](https://i-blog.csdnimg.cn/direct/f5031c8c4f15421c9882d7eb23540b8…

3D建模--犀牛Rhino for Mac

介绍 Rhino 8是一款功能强大的三维构建软件&#xff0c;它可以帮助用户创建各种类型的3D模型&#xff0c;包括产品设计、建筑设计、工业设计计划等。具有直观的界面和丰富的工具库&#xff0c;让你可以快速轻松地进行建模、编辑、分析和漂染。支持多种文件格式的导入和导出&am…

Axure原型模板与元件库APP交互设计素材(附资料)

为了高效地进行APP和小程序的设计与开发&#xff0c;原型设计工具Axure凭借其强大的功能和灵活性&#xff0c;成为了众多产品经理和设计师的首选。本文将详细介绍Axure原型模板APP常用界面组件元件库、交互设计素材&#xff0c;以及多套涵盖电商、社区服务、娱乐休闲、农业农村…

使用sympy求解给定函数表达式的拉普拉斯变换

拉普拉斯变换是一种重要的数学工具&#xff0c;在工程、物理和经济学等多个领域有着广泛的应用。Sympy是一个Python库&#xff0c;专门用于符号数学计算&#xff0c;其中包括求解拉普拉斯变换。 使用sympy&#xff0c;我们可以方便地计算给定函数表达式的拉普拉斯变换&#xff…

mongodb安装教程以及mongodb的使用

MongoDB是由C语言编写的一种面向文档的NoSQL数据库&#xff0c;旨在为WEB应用提供可扩展的高性能数据存储解决方案。与传统的关系型数据库&#xff08;如 MySQL 或 PostgreSQL&#xff09;不同&#xff0c;MongoDB 存储数据的方式是以 BSON&#xff08;类似于 JSON 的二进制格式…

家政保洁维修行业有没有必要做小程序?

【家政创业必看】家政行业小程序值得做吗&#xff1f;4大核心优势告诉你&#xff01; 随时随地下单&#xff1a;客户手机一键预约&#xff0c;告别找电话/翻页面的麻烦 品牌专业升级&#xff1a;精美界面服务详情用户评价&#xff0c;打造可信赖形象 营销神器&#xff1…

电力设备基础概念解析

设备 配变终端 配电主站 位于城市调度中心&#xff0c;负责全面监控和管理整个配网的运行状况。 配电子站 常常设立在 110kV/35kV 变电站内&#xff0c;它们像是一个个“分中心”&#xff0c;负责各自辖区内的监控任务。子站与所辖区域内的DTU/TTU/FTU等电力终端设备保持紧…

C++ 内存序在多线程中的使用

目录 一、内存顺序 二、 指令重排在多线程中的问题 2.1 问题与原因 2.2 解决方案 三、六种内存序 3.1 memory_order_relaxed 3.2 memory_order_consume 3.3 memory_order_acquire 3.4 memory_order_release 3.5 memory_order_acq_rel 3.6 memory_order_seq_cst 一、…

大模型+知识图谱:重塑企业制度标准管理

在数字化转型的浪潮中&#xff0c;制度标准管理领域正迎来一场革命性的变革。借助大模型和知识图谱等前沿人工智能技术&#xff0c;制度标准管理不再仅仅是简单的文档存储和检索&#xff0c;而是演变为一个智能化、高效化、精准化的管理体系。 1.关键技术 我们的制度标准管理…

FPGA学习(一)——DE2-115开发板编程入级

FPGA学习&#xff08;一&#xff09;——DE2-115开发板编程入级 一、实验目的 通过 1 位全加器的详细设计&#xff0c;深入掌握原理图输入以及 Verilog 的两种设计方法&#xff0c;熟悉 Quartus II 13.0 软件的使用流程&#xff0c;以及在 Intel DE2-115 开发板上的硬件测试过…

【大模型基础_毛玉仁】1.2 基于RNN的语言模型

【大模型基础_毛玉仁】1.2 基于RNN的语言模型 1.2 基于RNN的语言模型1.2.1 循环神经网络RNN1.2.2 基于RNN的语言模型1&#xff09;概率说明&#xff1a;2&#xff09;损失函数3&#xff09;问题 1.2 基于RNN的语言模型 循环神经网络&#xff08;RecurrentNeuralNetwork,RNN&am…

三参数水质在线分析仪:从源头保障饮用水安全

【TH-ZS03】饮用水安全是人类健康的重要保障&#xff0c;其质量直接关系到人们的生命健康。随着工业化、城市化的快速发展&#xff0c;水体污染问题日益严峻&#xff0c;饮用水安全面临着前所未有的挑战。为了从源头保障饮用水安全&#xff0c;科学、高效的水质监测手段必不可少…

MiniMind用极低的成本训练属于自己的大模型

本篇文章主要讲解&#xff0c;如何通过极低的成本训练自己的大模型的方法和教程&#xff0c;通过MiniMind快速实现普通家用电脑的模型训练。 日期&#xff1a;2025年3月5日 作者&#xff1a;任聪聪 一、MiniMind 介绍 基本信息 在2小时&#xff0c;训练出属于自己的28M大模型。…

后验概率估计

前言 本文隶属于专栏《机器学习数学通关指南》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见《机器学习数学通关指南》 正文 一、基本定义 &#x1f9ee; …

错误: 加载主类时出现 LinkageError,java.lang.UnsupportedClassVersionError 解决方案

分析: 可能就是我们在配置完jdk的path时候,电脑没有重启idea还没有更新path环境jdk版本. 解决办法: 1.重启电脑 2.seting设置对应的jdk版本 Project Structure中设置jdk版本 运行就解决了 一键三连 一起学习 一起进步. 推动科技发展, 为科技赋能.

学习记录-用例设计编写

黑马测试视频记录 目录 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法​编辑 5、错误推荐法 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法 5、错误推荐法 时间紧任务重…

软件测试(三)——Bug篇

文章目录 Bug篇软件测试的生命周期BugBug的概念Bug的要素Bug的级别Bug的生命周期 与开发发生争执怎么办 Bug篇 大部分的Bug都是测试人员提出的&#xff0c;因此在Bug篇的开始会先介绍软件测试的生命周期。同时&#xff0c;了解软件测试的生命周期能帮助我们了解测试的工作&…