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 target
是false
(target
中没有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
添加 getter 和 setter;每次 读这个属性:会执行
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 就能“偷偷知道”你改了什么 ob Vue 给每个被监听的对象打个“标签”,表示:这个是我监听的对象! 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,看到你改了,就:
调用
dep.notify()
,通知这个“组件”然后 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 做的就是:
这个
obj
是不是响应式对象?(看有没有__ob__
)没有这个属性?我就用
defineReactive
加一个 getter/setter然后立刻通知页面:“我要更新了!”