Vue2 的数据响应式原理给实例新增响应式属性

Vue2 响应式原理的案例

<template> <div id="app"> <div>用户名:{{ user.name }}</div> <div>年龄:{{ user.age }}</div> <button @click="addAgeDirectly">直接添加年龄(无响应式)</button> </div> </template> <script> name: 'vue2Dome1', data() { return { user: { name: '张三' }, likelist: ["苹果", "香蕉", "橙子"] }; }, methods: { addAgeDirectly() { this.user.age = 10; this.likelist[1] = "方块西瓜" console.log('user.age:', this.user.age); console.log(this.user); console.log(this.likelist); }, } }; </script>

问题一:

  1. user.age属性是否添加成功?

  2. 能否顺利输出user.age的值?

  3. 视图中user.age会更新吗?

答案:

  1. 不会

这就要讲到我们数据响应式更新的原理[向下]

Vue2 数据响应式的原理

核心组件(主流分类):

核心组件

核心定位

作用

Observer(观察者)

数据劫持层

data里的所有属性变成 “可监听” 的响应式数据

Dep(依赖收集器)

依赖存储层

给每个响应式属性 “配一个专属容器”,存依赖该属性的Watcher

Watcher(订阅者)

更新执行层

触发依赖收集 + 收到通知后执行更新(重新渲染 / 执行 watch 回调)

VNode/Patch(虚拟DOM/Diff)

视图更新层

生成虚拟 DOM、对比新旧 VNode,最小化更新真实 DOM

流程图

Vue 初始化过程中:

Data 中的属性会在 ,被 Observer 类通过 Object.defineProperty ()添加 getter/setter,转为响应式数据。

在执行 render 函数时:

需要访问 data 中的数据,触发对应属性的 getter;此时 Dep 检测(Dep.target, 指向渲染 Watcher)到当前有活跃的 Watcher,调用 dep.depend () 完成依赖收集(Dep 存入该 Watcher,Watcher 也记录对应的 Dep)。render 执行完成后生成虚拟 DOM 树,Vue 立刻调用 patch 方法将虚拟 DOM 树挂载为真实 DOM(首次渲染)。

当修改 data 数据时

触发对应属性的 setter;Dep 调用 notify () 遍历所有订阅(依赖)该数据的 Watcher,通知数据已更新。Watcher 收到通知后、去重(避免同一 Watcher 重复入队)、入队,再将队列交给 nextTick 放入异步微任务队列(等待本轮 JS 执行完)。异步队列执行时,Watcher 重新执行、 render 生成新的虚拟 DOM 树,Vue 对比新旧虚拟 DOM 树(Diff 算法),只将差异部分更新为真实 DOM。

具体实现(mini-vue2)

// 1. 依赖管理器:收集Watcher,通知更新 class Dep { constructor() { this.subs = []; // 存储所有依赖(Watcher实例) } // 添加依赖 addSub(sub) { this.subs.push(sub); } // 通知所有依赖更新 notify() { this.subs.forEach(sub => sub.update()); } } // 2. 观察者:负责更新视图(模拟Vue的Watcher) class Watcher { /** * @param {Object} vm 模拟Vue实例 * @param {string} key 要观察的属性名 * @param {Function} cb 属性变化时的回调(更新视图) */ constructor(vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb; // 把当前Watcher实例挂载到Dep.target,用于依赖收集 Dep.target = this; // 触发getter,完成依赖收集 this.vm[this.key]; // 收集完成后清空,避免重复收集 Dep.target = null; } // 触发更新回调 update() { this.cb.call(this.vm, this.vm[this.key]); } } // 3. 数据劫持:对对象属性重写get/set function defineReactive(obj, key, val) { // 每个属性对应一个Dep实例 const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, // 可枚举 configurable: true, // 可配置 // 读取属性时触发 get() { // 如果有Watcher在等待收集依赖,就添加到Dep if (Dep.target) { dep.addSub(Dep.target); } return val; }, // 修改属性时触发 set(newVal) { if (newVal === val) return; // 值未变化则不处理 val = newVal; // 通知所有依赖更新 dep.notify(); } }); } // 4. 响应式处理:遍历对象所有属性,实现劫持 function observe(obj) { // 只处理对象(简化版,忽略数组) if (typeof obj !== 'object' || obj === null) { return; } // 遍历对象属性,逐个劫持 Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]); }); } // 5. 模拟Vue实例 class Vue { constructor(options) { this.$data = options.data(); // 模拟Vue的data选项 // 对data进行响应式处理 observe(this.$data); // 把data的属性代理到Vue实例上(简化版,模拟vm.xxx访问data.xxx) Object.keys(this.$data).forEach(key => { Object.defineProperty(this, key, { get() { return this.$data[key]; }, set(newVal) { this.$data[key] = newVal; } }); }); // 模拟挂载阶段:创建Watcher,关联属性和更新逻辑 if (options.watch) { Object.keys(options.watch).forEach(key => { new Watcher(this, key, options.watch[key]); }); } } } // 测试代码 const vm = new Vue({ data() { return { msg: 'Vue2响应式' }; }, watch: { msg(newVal) { console.log('视图更新:', newVal); } } }); // 修改属性,触发响应式 vm.msg = 'Hello Vue2'; // 输出:视图更新:Hello Vue2 vm.msg = '数据劫持+依赖收集'; // 输出:视图更新:数据劫持+依赖收集

那么经过上述讲解,大家都知道Vue2实现数据劫持是依靠Object.defineProperty(),但是这种方式有一个缺点:无法拦截新增的属性。

so,what should we do?

应用: 如何给实例新增响应式属性?

Vue.set/this.$set

vue2官方推荐的新增响应式属性的方式,适用于对象 / 数组场景。

  1. 基本语法

// 全局方法 Vue.set(target, propertyName/index, value) // 实例方法(更常用) this.$set(target, propertyName/index, value) // target:目标对象(响应式)或数组 // propertyName/index:对象属性名(字符串)或数组索引(数字) // value:新增属性的取值
给对象新增响应式对象
export default { name: 'vue2Dome2', data() { return { user: { name: '张三' } }; }, methods: { addAgeWithSet() { this.$set(this.user, 'age', 20); } } };
给数组新增 / 修改元素

修改数组元素

this.$set(this.likelist, 1, '西瓜') //超出数组索引式新增 this.$set(this.likelist,this.likelist.length,'阳光青提') //注意当我们直接用push()方法给数组新增元素时,视图也会更新。

Vue 2 为了解决数组下标修改无法检测的问题,重写了数组的 7 个原生变异方法,这些方法调用后会自动触发依赖更新(视图刷新)

push()

向数组末尾添加元素

pop()

删除数组最后一个元素

shift()

删除数组第一个元素

unshift()

向数组开头添加元素

splice()

增 / 删 / 改数组元素(万能)

sort()

数组排序

reverse()

数组反转

那么也就是说,如果我们想响应式更新数组中的元素有多了一种方法。

使用这些Vue2重新封装了的原生变异方法,其中最万能的:

splice(起始下标,*操作数量,插入元素)

示例:

this.likelist.splice(0, 1, '火晶柿子')//修改索引0 this.likelist.splice(this.likelist.length,1,'东北冻梨')//添加

一句话总结:Vue.set/this.$set给响应式对象添加响应式数据

仅对「响应式对象的不存在属性」生效:绑定get/set+ 触发视图更新;

对「已存在的属性」:

若属性本身是响应式(初始化声明)→ 等价于普通赋值,仍触发更新;

若属性是非响应式(直接赋值新增)→ 仅普通赋值,无响应式处理(看似 “失效”);

对「非响应式对象」→ 仅普通赋值,无任何响应式处理。

$set是 Vue2 给「响应式对象新增属性」的 “专属补丁”,对已存在的属性,它只是个 “普通赋值工具”


除此之外,我们运用扩展运算符也可以实现对新增响应式属性

原理:扩展运算符会浅拷贝原对象的所有属性,并合并新增属性生成一个新对象;再通过 Vue 的响应式 API(Vue 2 的Vue.set/Vue.observable、Vue 3 的reactive/ref)将新对象设为响应式,从而让新增属性具备响应式特性。

也就是说,这种方法相当于重新创建了一个同名的响应式对象,合并了新属性(替换式新增),达成了既“新增”又“响应式”的结果。

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

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

相关文章

高薪又缺人!国家超重视,网安这 8 个专业超吃香

国家超重视&#xff0c;高薪又缺人&#xff1a;这8个网络安全领域专业很“吃香”&#xff01; 9月3日&#xff0c;在抗日战争暨反法西斯战争胜利80周年阅兵仪式上&#xff0c;网络安全方队首次进行检阅。 近年&#xff0c;随着计算机和通信网络的广泛应用&#xff0c;网络空间…

学长亲荐2026TOP10AI论文软件:MBA毕业论文写作全测评

学长亲荐2026TOP10AI论文软件&#xff1a;MBA毕业论文写作全测评 2026年MBA论文写作工具测评&#xff1a;为何需要一份权威榜单&#xff1f; 在MBA学习过程中&#xff0c;撰写高质量的毕业论文不仅是学术能力的体现&#xff0c;更是对未来职业发展的重要铺垫。然而&#xff0…

BUUCTF:[GXYCTF2019]Ping Ping Ping

BUUCTF&#xff1a;[GXYCTF2019]Ping Ping Ping 本文知识点&#xff1a; 变量替换${}和命令替换$()&#xff1a; 变量替换&#xff1a;IFS,大括号中的是一个变量&#xff0c;这个的功能就是将变量的值展开出来&#xff0c;比如假设有一个变量‘a123‘,‘{IFS},大括号中的是一个…

计算机Java毕设实战-基于java+springboot+vue的扶贫助农系统基于springboot的助农扶贫系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

AI写代码像AI写文章一样会“翻车“?手把手教你构建品牌RAG系统,让AI生成内容不再“人格分裂“

还顺手吐槽了一众电商操盘手&#xff0c;结果发出去之后&#xff0c;有人私信我一句话&#xff1a;“骂归骂&#xff0c;你别光说大道理&#xff0c;你倒是告诉我&#xff0c;怎么让 AI 高产但不跑偏&#xff1f;” 本来就想着今天继续写品牌RAG怎么做&#xff0c;今天就继续拿…

python: excel 两个工作表中的员工比对 UI

Domain Layer# encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述: # Author : geovindu,Geovin Du 涂聚文. # IDE : Py…

【CTF Writeup】Reverse题型之加壳程序脱壳与逆向分析

前言 一、加壳程序识别 1.1 使用Exeinfo PE识别壳类型 将程序protected.exe拖入Exeinfo PE&#xff0c;显示“UPX 3.96 - 1.00 unpacked”&#xff0c;确定为UPX加壳程序。UPX是开源压缩壳&#xff0c;脱壳难度较低&#xff0c;可使用工具自动脱壳。 二、UPX加壳程序脱壳 …

AI大洪水来袭!90%的人还在卷算法,聪明的已经盯上“铁饭碗”——协调人

AI大洪水来袭!90%的人还在卷算法,聪明的已经盯上“铁饭碗”——协调人 目录 AI大洪水来袭!90%的人还在卷算法,聪明的已经盯上“铁饭碗”——协调人 🔴 淘汰预警:纯技术“工具人” 🔵 晋升密码:协调型“问题终结者” 我们应该怎么做 做 LLM 技术,这样转型 “技术 + 协…

Java计算机毕设之 基于Spring Boot的助农扶贫综合服务平台开发基于springboot的助农扶贫系统(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

国产大模型第一梯队!

为百万财经人士倾心打造的投研资讯平台 为您解析宏观及政策、研判产业格局及动态&#xff0c; 与君共同见证中国资本市场的壮阔奋进时代&#xff01; ——研讯社 当前正值国产大模型新一轮更新迭代周期&#xff0c;梳理下国产大模型第一梯队。 深度求索DeepSeek DS实控人是…

权利的本质是插队?县城婆罗门的毛细血管,我们该走还是留?

权利的本质是插队&#xff1f;县城婆罗门的毛细血管&#xff0c;我们该走还是留&#xff1f; 目录 权利的本质是插队&#xff1f;县城婆罗门的毛细血管&#xff0c;我们该走还是留&#xff1f;不是宗教意义上的阶层&#xff0c;而是基层独有的生态于是很多人心里都冒出来一个天…

ClickHouse 原理:深入理解数据分片 Part 和分区 Partition

在 ClickHouse 中&#xff0c;磁盘上存储表数据一部分的物理文件被称为数据分片 part。数据分区 partition 则是通过分区键创建表的数据逻辑划分。通过分区&#xff0c;用户可以更高效地存储、查询和操作数据的子集&#xff0c;从而提升大表的性能和可管理性。在本博客系列的第…

ClickHouse 高分笔记

1. 原理 实时数据分析数据库 ClickHouse 介绍开源OLAP引擎&#xff08;ClickHouse、Doris、Presto、ByConity&#xff09;性能对比分析ClickHouse 原理&#xff1a;如何为列式存储构建快速 UPDATE I&#xff1a;特别设计的专用引擎ClickHouse 原理&#xff1a;深入理解数据分片…

全国逛同一座城?我们都活在“复制粘贴”的模块化世界里

全国逛同一座城&#xff1f;我们都活在“复制粘贴”的模块化世界里 目录 全国逛同一座城&#xff1f;我们都活在“复制粘贴”的模块化世界里&#x1f50d; 看懂“复制粘贴”的本质&#xff1a;不是巧合&#xff0c;是底层逻辑的必然&#x1f4a1; 普通人如何透过现象看本质&…

【课程设计/毕业设计】基于springboot的助农扶贫系统家乡扶贫助农系统【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

微信小程序 == rsa加解密工具

wxmp-rsa 1、简介 前端rsa加解密工具。 基于jsencrypt修改扩展功能。兼容小程序环境&#xff0c;压缩后60kb左右的大小&#xff0c;节省小程序空间。支持超长文本加解密。支持中文字符的加解密。 仓库地址 https://github.com/jiayc4215/wxmp-rsa2、安装 npm i wxmp-rsa …

logging模块,scrapy全站爬取

1.longging模块 logging是python内置的日志处理模块 在scrapy中,可以配置LOG_LEVEL来设置输出的日志等级,也可以在scrapy/settin/default_setting.py 路径下查看日志相关默认参数(需要修改的参数可以直接在settings.py文件中修改) #添加日志 LOG_LEVEL="WAREING"…

CrawlSpider自动爬取,ImagePipeline

1.crawlspider自动爬取 csrapy框架在scrapy.spiders模块中提供了crawlspider类专门用来自动爬取,crawlspider类是spider的派生类,spider类的设计原则是只爬取srart_url列表中的网页,而CrawlSpider类可以定义一些规则来进行url的跟进,我们可以使用跟进的这个特性达到自动翻页…

TPDO vs RPDO 对比总结

TPDO vs RPDO 对比总结 核心本质对比 维度 TPDO RPDO 名字全称 Tx Process Data Object Rx Process Data Object 通信方向 Slave → Master (从站上传主站) Master → Slave (主站下发从站) 通信内容 状态反馈: 位置、速度、力矩等 控制指令: 目标位置、模式切换 在 RTLink 中…

贪吃蛇 set和deque使用

#include <vector> #include <string> #include <deque> #include <set>using namespace std;// 您提供的 Node 结构体 typedef struct Node{int _x;int _y;Node(int x, int y){_x x;_y y;}// 重载 < 运算符&#xff0c;方便放入 set 中进行去重/…