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.  然后立刻通知页面:“我要更新了!”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值