当前位置: 首页 > news >正文

Vue.set 响应式原理详解:源码级逐行带入实战解析

vue 2.x 响应式系统中 Vue.set 的核心源码(内部就是调用 set 函数)。我们现在就带入真实值逐行执行解释每一步判断和行为

也就是经常使用this.$set(),或者vue.set调用的话vue2内部是怎么样的

这个就是执行这个源码set函数

function set(target, key, val) {// 如果是数组 & key 是数字索引if (Array.isArray(target) && Number.isInteger(key)) {// 让数组通过 splice 替换原值,这样才能触发响应式target.splice(key, 1, val);return val;}// 判断这个属性是否已存在(旧的 reactive 属性会走这段)if (key in target && !(key in Object.prototype)) {// 属性存在,直接赋值即可,已有 getter/setter 自动响应target[key] = val;return val;}// 关键:如果是响应式对象(有 __ob__),才执行响应式处理const ob = target.__ob__;if (!ob) {// 如果不是响应式对象(没挂 __ob__),就简单赋值,不通知视图target[key] = val;return val;}// 真正核心:动态把这个属性变成响应式的!defineReactive(target, key, val);// 响应式系统通知视图去更新(比如重新渲染组件)ob.dep.notify();return val;
}

假设我们手动执行如下代码:

const target = {name: 'Alice',__ob__: {dep: {notify() {console.log('通知视图更新!');}}}
};set(target, 'age', 30);

🔹 第一步:

if (Array.isArray(target) && Number.isInteger(key)) {

  • target 是对象,不是数组(Array.isArray(target)false)。

  • 所以这个判断不成立,跳过这段。

🔹 第二步:

if (key in target && !(key in Object.prototype)) {
  • 'age' in targetfalsetarget 中没有 age)。

  • 所以这个判断也不成立,跳过

🔹 第三步:

const ob = target.__ob__;

🔹 第四步:

defineReactive(target, key, val);

也就是

defineReactive(target, 'age', 30);
defineReactive 会做什么?
  • target.age 添加一个 getter 和 setter

  • 每当你读取 target.age 就会触发 getter。

  • 每当你改值 target.age = xxx,就会触发 setter,并通知更新

所以 age 属性此时被“变成了响应式”。

function defineReactive(obj, key, val) {const dep = new Dep(); // 为每个属性创建一个依赖管理器Object.defineProperty(obj, key, {get() {// 依赖收集(比如模板里用了这个属性,就收集 watcher)dep.depend();return val;},set(newVal) {if (newVal === val) return;val = newVal;// 触发更新dep.notify();}});
}
  • ✅ 创建一个 dep,用来后续收集和通知依赖(Dep 是个类,里面维护了一个数组 subs);

  • ✅ 调用 Object.defineProperty

    • target.age 添加 gettersetter

    • 每次 读这个属性:会执行 dep.depend(),把依赖(如模板渲染)收集进来;

    • 每次 写这个属性:会执行 dep.notify(),通知依赖去更新 UI;

  • ✅ 以后你只要写了 target.age = 40,Vue 就能感知变化并更新页面。

 那这个 dep 是谁?

dep 是 Vue 里面的核心类之一,作用就是:

  • 收集依赖(比如这个属性被哪个组件、哪个计算属性用到了);

  • 通知更新(比如你把 age 从 30 改成 40,要重新渲染页面)。

class Dep {constructor() {this.subs = []; // 存放 watcher}depend() {if (Dep.target) {this.subs.push(Dep.target);}}notify() {this.subs.forEach(sub => sub.update());}
}
  • dep.depend():当前这个属性被谁用到了,记下来;

  • dep.notify():当前这个属性变化了,通知这些人(watcher)更新视图。

🔹 第五步:

ob.dep.notify();
  • 调用了 notify(),即:console.log('通知视图更新!')

  • 视图(或依赖该数据的组件)会被重新渲染。

最后返回:

return val;

返回 30,即你传入的值。

总结流程图 

调用 set(obj, 'age', 30)

检查是不是数组 → 不是,跳过

属性 'age' 是否已存在 → 不存在,跳过

是响应式对象吗?(有 __ob__)→ 是

defineReactive:将 age 变成响应式

ob.dep.notify():通知视图更新

return val(返回值)

调用 set(target, 'age', 30) 后:

  • target.age 成功新增;

  • 是响应式的;

  • 页面会因为 notify() 而更新!

注意:

为什么叫 getter / setter

代码是

get() { ... },
set() { ... }

但我们约定俗成地称这两个函数为:

  • getter:属性被「读取」时执行的函数;

  • setter:属性被「修改」时执行的函数

名字意思
getter当你“读取”数据时,Vue 就能“偷偷知道”你读取了什么
setter当你“修改”数据时,Vue 就能“偷偷知道”你改了什么
obVue 给每个被监听的对象打个“标签”,表示:这个是我监听的对象!
dep.depend()当前组件“用到”这个数据了,Vue 把这个记录下来
dep.notify()当数据改了,Vue 就通知所有用到它的地方去“更新页面”

vm.person = { name: '张三' }

第一步:Vue 给你加监听器

Object.defineProperty(vm.person, 'name', { get() { // 有人来读“张三”了,记录下来! return name; }, set(val) { // 张三被改名字了!通知所有看他的人!name = val; dep.notify(); // 通知“观众” } })

第二步:当页面渲染 <p>{{ person.name }}</p>

Vue 在后台记录:

Dep.target = 当前的组件

然后读了 person.name,Vue 就记录下:

这个组件“用到了 person.name”


最后修改了:

vm.person.name = '李四'

Vue 走 setter,看到你改了,就:

  1. 调用 dep.notify(),通知这个“组件”

  2. 然后 Vue 就重新渲染页面!


 那么 defineReactive 是干嘛的?

它就是帮你给每个属性加 getter / setter 的人,比如:

defineReactive(person, 'name', '张三')

背后就相当于:

Object.defineProperty(person, 'name', { get() {...}, // 记录依赖 set(val) {...} // 通知更新 })


__ob__ 是干嘛的?

Vue 在 person 对象上挂了:

person.__ob__ = { dep: {}, // 就是依赖记录本 }

以后只要看到有 __ob__,Vue 就知道:

这个对象是“我 Vue 处理过的”,我知道它有没有变。


最后总结一句话 

Vue.set(obj, 'age', 30)

Vue 做的就是:

  1.  这个 obj 是不是响应式对象?(看有没有 __ob__

  2.  没有这个属性?我就用 defineReactive 加一个 getter/setter

  3.  然后立刻通知页面:“我要更新了!”

http://www.dtcms.com/a/309894.html

相关文章:

  • 【go】slice元素去重
  • MonoGame游戏开发框架日记 -07
  • 【Go】P1 GoLang 语言简介与起源
  • iPhone 恢复出厂设置是否会删除所有内容?
  • 充电桩车位占用识别准确率↑32%:陌讯动态特征融合算法实战解析
  • STM32 使用 RTC 实现实时时钟功能
  • tauri实用教程:项目打包为安装包时如何包含其他文件
  • InfluxDB 与 Golang 框架集成:Gin 实战指南(一)
  • 噪声对比估计(NCE):原理、演进与跨领域应用
  • 第一个大语言模型的微调
  • 电路基础学习
  • 字节跳动招机器人数据算法研究员-Top Seed
  • 开源医院信息管理系统:基于若依框架的智慧医疗解决方案
  • Chrontel【CH7219A-BF】CH7219A USB-C和DP 1.4至HDMI 2.1协议转换器,带DSC解码功能
  • [2025CVPR-图象生成方向]ODA-GAN:由弱监督学习辅助的正交解耦比对GAN 虚拟免疫组织化学染色
  • 【Mysql】联合索引生效分析案例
  • 新手小白如何快速检测IP 的好坏?
  • AI有限元、聚合物复合材料多尺度建模材料性能预测及大模型应用实践,打破传统研发模式!
  • 【跨国数仓迁移最佳实践4】MaxCompute 企业级能力升级:跨域访问控制与数据安全特性增强
  • Apache RocketMQ中 Normal Message(普通消息)的说明
  • LRU缓存淘汰算法的详细介绍与具体实现
  • 智能体之外部工具篇(2)
  • SpringBoot英语学习系统开发实战
  • TOGAF指南1
  • JavaWeb--Student2025项目:条件查询、批量删除、新增、修改
  • a=b、深拷贝和浅拷贝
  • pycharm快捷键设置为和vscode一样
  • 日志管理工具 ——Graylog
  • 外卖“0元购”退场后,即时零售大战才刚开始
  • 一个可以检测本机的字节顺序,并对任意数据进行字节顺序的反转操作的代码。