vue2中$set的原理

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

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

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

  • 对数据对象的已有属性 进行劫持(拦截 getset 操作)
  • 当属性被访问时(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 对数组的 splicepush 等方法进行了重写(拦截),调用这些方法会自动触发更新。因此对于数组,$set 本质是通过 splice(key, 1, value) 实现的。

  2. 处理对象

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

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

总结

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

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

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

相关推荐
闲不住的李先森4 小时前
深入解析 Cursor 规则:为团队打造统一的 AI 编程规范
前端·ai编程·cursor
FlowGram4 小时前
FlowGram 官网建设
前端
~无忧花开~4 小时前
JavaScript学习笔记(二十八):JavaScript性能优化全攻略
开发语言·前端·javascript·笔记·学习·性能优化·js
BumBle4 小时前
基于UniApp实现DeepSeek AI对话:流式数据传输与实时交互技术解析
前端·uni-app
九十一5 小时前
vue3事件总线与emit
前端·vue.js
岁月向前5 小时前
不同的协议和场景防丢包方案
前端
琢磨先生TT5 小时前
一个前端工程师的年度作品:从零开发媲美商业级应用的后台管理系统!
前端·前端框架
云枫晖5 小时前
JS核心知识-Ajax
前端·javascript
玄魂5 小时前
VTable Gantt 智能 zoom(缩放)功能介绍与开发实践
前端·开源·数据可视化