
在 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
方法重新渲染)。
核心步骤拆解:
-
处理数组 :由于 Vue 对数组的
splice
、push
等方法进行了重写(拦截),调用这些方法会自动触发更新。因此对于数组,$set
本质是通过splice(key, 1, value)
实现的。 -
处理对象:
- 若属性已存在,直接赋值(会触发该属性原有的
set
拦截器)。 - 若属性不存在,通过
defineReactive
为新属性添加响应式劫持(即重新调用Object.defineProperty
拦截get
和set
)。 - 手动触发依赖更新(
ob.dep.notify()
),确保视图同步刷新。
- 若属性已存在,直接赋值(会触发该属性原有的
$set
本质是 "手动为新属性添加响应式劫持,并强制触发更新" 的封装。
总结
$set
是 Vue2 为弥补 Object.defineProperty
缺陷而设计的 API,其核心原理是:
- 对数组:通过重写的
splice
方法触发响应式更新。 - 对对象:为新属性手动添加响应式劫持(
defineReactive
),并触发依赖更新。
在 Vue3 中,由于响应式系统改用 Proxy
实现(支持监听新增 / 删除属性),因此不再需要 $set
这个 API。