vue2中$set的原理

在 Vue2 中,$set 是一个核心 API,用于解决对象或数组新增属性时无法触发响应式更新的问题。要理解其原理,需要先了解 Vue2 响应式系统的底层实现。

1. Vue2 响应式的核心:Object.defineProperty

Vue2 的响应式系统基于 Object.defineProperty 实现,其核心逻辑是:

  • 对数据对象的已有属性 进行劫持(拦截 get 和 set 操作)
  • 当属性被访问时(get),收集依赖(Watcher)
  • 当属性被修改时(set),触发依赖更新(通知视图重新渲染)

但这种方式存在天然限制:只能劫持对象已存在的属性。对于新增属性或删除属性,默认无法触发响应式更新。

2. $set 的作用

$set 的设计目的就是解决上述限制,它能让新增的属性也具备响应式能力,同时触发视图更新。

js 复制代码
// 响应式对象
const vm = new Vue({
  data() {
    return {
      obj: { name: 'foo' }, // 已有属性 name 是响应式的
      arr: ['a', 'b']
    }
  }
})

// 直接新增属性,不会触发更新
vm.obj.age = 20; // 非响应式

// 使用 $set 新增属性,会触发更新
this.$set(vm.obj, 'age', 20); // 响应式

// 数组新增元素(Vue 对数组方法做了特殊处理,但直接通过索引修改仍需 $set)
this.$set(vm.arr, 2, 'c'); // 响应式

3. $set 的实现原理

$set 源码(简化版)的核心逻辑如下:

js 复制代码
function set(target, key, value) {
  // 1. 处理数组:利用重写的 splice 方法触发更新
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, value);    // splice 已被 Vue 重写,会触发更新
    return value;
  }

  // 2.处理对象:如果属性已存在,直接赋值(会触发 set 拦截)
  if (key in target && !(key in Object.prototype)) {
    target[key] = value;
    return value;
  }

  // 获取响应式对象的 Observer 实例
  const ob = target.__ob__;

  // 3. 非响应式对象(如普通对象),直接赋值
  if (!ob) {
    target[key] = value;
    return value;
  }

  // 4.为新属性添加响应式劫持(调用 defineReactive)
  defineReactive(ob.value, key, value);
  
  // 触发依赖更新(通知视图渲染)
  ob.dep.notify();
  
  return value;
}

defineReactive 是 Vue2 响应式系统的核心函数,它的主要作用是将对象的属性转换为响应式属性,实现对属性读写的拦截,从而在数据变化时自动更新视图

  • 递归响应式 :如果属性值是对象(或数组),会通过 observe 函数递归调用 defineReactive,实现深层响应式。
  • 依赖收集 :借助 Dep 类管理依赖,Dep.target 指向当前正在渲染的组件对应的 Watcher,读取属性时会将 Watcher 加入依赖列表。
  • 触发更新 :当属性被修改时,set 函数会调用 dep.notify(),遍历依赖列表并触发所有 Watcher 的更新逻辑(最终调用组件的 render 方法重新渲染)。

核心步骤拆解:

  1. 处理数组 :由于 Vue 对数组的 splice、push 等方法进行了重写(拦截),调用这些方法会自动触发更新。因此对于数组,$set 本质是通过 splice(key, 1, value) 实现的。

  2. 处理对象:

    • 若属性已存在,直接赋值(会触发该属性原有的 set 拦截器)。
    • 若属性不存在,通过 defineReactive 为新属性添加响应式劫持(即重新调用 Object.defineProperty 拦截 get 和 set)。
    • 手动触发依赖更新(ob.dep.notify()),确保视图同步刷新。

$set 本质是 "手动为新属性添加响应式劫持,并强制触发更新" 的封装。

总结

$set 是 Vue2 为弥补 Object.defineProperty 缺陷而设计的 API,其核心原理是:

  • 对数组:通过重写的 splice 方法触发响应式更新。
  • 对对象:为新属性手动添加响应式劫持(defineReactive),并触发依赖更新。

在 Vue3 中,由于响应式系统改用 Proxy 实现(支持监听新增 / 删除属性),因此不再需要 $set 这个 API。

相关推荐
仿生狮子5 小时前
OpenAI 攻下千禧年难题之后,开发者似乎无动于衷
前端·数学·vibecoding
Lvan的前端笔记8 小时前
docker:每个前端项目一个 Nginx 容器还是只有一个Nginx容器
前端·nginx·docker
三天不学习9 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194059 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
明月_清风10 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
Csvn11 小时前
框架性能优化
前端
回眸&啤酒鸭13 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户693717500138413 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员13 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子13 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端