面试官 : “ 说一下 Vue 的响应式原理是什么? ”

64 阅读6分钟

Vue 响应式原理深度解析

⚠️面试要点

常见面试题

Q1: Vue 2 的响应式原理是什么?

// 回答要点:
1. 使用 Object.defineProperty 劫持对象的 getter/setter
2. 每个属性都有对应的 Dep 依赖收集器
3. 在 getter 中收集依赖(Watcher)
4. 在 setter 中通知依赖更新
5. 数组需要特殊处理(重写7个方法)

Q2: Vue 3 为什么要用 Proxy 替代 Object.defineProperty?

// 回答要点:
1. Proxy 可以直接代理整个对象,不用遍历属性
2. 支持数组索引变化、length 变化的检测
3. 支持动态新增/删除属性
4. 性能更好,按需响应
5. 更好的浏览器兼容性

Q3: Vue 的异步更新机制是如何工作的?

// 回答要点:
1. 数据变化时,Watcher 不会立即执行,而是加入队列
2. 同一个 Watcher 在同一个事件循环中只会被添加一次
3. 通过 nextTick 在下一个事件循环执行更新
4. 使用微任务优先(Promise > MutationObserver > setImmediate > setTimeout)
5. 保证更新顺序(父组件在子组件之前)

1. 核心思想

响应式原理的本质数据变化 → 自动更新视图


// 传统方式
data.name = '张三';
document.getElementById('name').innerText = data.name;  // 手动更新

// Vue 响应式
vm.name = '张三';  // 自动更新所有使用到 name 的地方

2. Vue 2 响应式原理(Object.defineProperty)

2.1 基本实现

 
// 简化的响应式实现
function defineReactive(obj, key, val) {
  // 每个属性都有自己的依赖管理器
  const dep = new Dep();
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log(`读取 ${key}: ${val}`);
      
      // 依赖收集
      if (Dep.target) {
        dep.depend();  // 将当前 watcher 添加到依赖列表
      }
      
      return val;
    },
    set(newVal) {
      console.log(`修改 ${key}: ${val} -> ${newVal}`);
      
      if (newVal === val) return;
      
      val = newVal;
      
      // 派发更新
      dep.notify();  // 通知所有依赖的 watcher 更新
    }
  });
}

2.2 依赖收集系统

 
// 依赖管理器(发布者)
let uid = 0;

class Dep {
  static target = null;  // 当前正在计算的 watcher
  
  constructor() {
    this.id = uid++;
    this.subs = [];  // 订阅者列表
  }
  
  // 添加订阅者
  addSub(sub) {
    this.subs.push(sub);
  }
  
  // 收集依赖
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
  
  // 通知所有订阅者更新
  notify() {
    const subs = this.subs.slice();
    
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();  // 触发每个 watcher 更新
    }
  }
}

// 观察者(订阅者)
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.deps = [];
    this.depIds = new Set();
    
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = this.parseGetter(expOrFn);
    }
    
    this.value = this.get();  // 首次求值,触发依赖收集
  }
  
  get() {
    Dep.target = this;  // 设置当前 watcher
    let value;
    
    try {
      value = this.getter.call(this.vm, this.vm);
    } finally {
      Dep.target = null;  // 收集完成
    }
    
    return value;
  }
  
  addDep(dep) {
    const id = dep.id;
    
    if (!this.depIds.has(id)) {
      this.depIds.add(id);
      this.deps.push(dep);
      dep.addSub(this);
    }
  }
  
  update() {
    // 异步更新队列
    queueWatcher(this);
  }
  
  run() {
    const value = this.get();
    const oldValue = this.value;
    
    if (value !== oldValue) {
      this.value = value;
      this.cb.call(this.vm, value, oldValue);
    }
  }
}

2.3 Observer 观察者类

 
class Observer {
  constructor(value) {
    this.value = value;
    this.dep = new Dep();
    
    // 标记对象已被观察
    def(value, '__ob__', this);
    
    if (Array.isArray(value)) {
      // 数组的特殊处理
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
  
  walk(obj) {
    const keys = Object.keys(obj);
    
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  }
  
  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i]);
    }
  }
}

// 对外暴露的 observe 函数
function observe(value) {
  if (!isObject(value) || value instanceof VNode) {
    return;
  }
  
  let ob;
  
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  
  return ob;
}

3. 数组的响应式处理

3.1 数组方法重写

 
// 需要拦截的数组方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

methodsToPatch.forEach(function(method) {
  const original = arrayProto[method];
  
  def(arrayMethods, method, function mutator(...args) {
    console.log(`数组方法 ${method} 被调用`);
    
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    
    // 对新插入的元素进行响应式处理
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    
    if (inserted) ob.observeArray(inserted);
    
    // 通知更新
    ob.dep.notify();
    
    return result;
  });
});

3.2 数组响应式的局限性

 
// Vue 2 中数组响应式的限制
const vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
});

// ✅ 响应式的方法
vm.items.push('d');           // 正确
vm.items.splice(0, 1, 'x');   // 正确
vm.items.sort();              // 正确

// ❌ 非响应式的方法
vm.items[0] = 'x';            // 不会触发更新
vm.items.length = 0;          // 不会触发更新

// ✅ 解决方法
Vue.set(vm.items, 0, 'x');    // 正确
vm.items.splice(0, 1, 'x');   // 正确

4. Vue 3 响应式原理(Proxy)

4.1 Proxy 基本实现

 
// Vue 3 的 reactive 函数
function reactive(target) {
  return createReactiveObject(
    target,
    mutableHandlers,     // 普通对象的处理器
    mutableCollectionHandlers,  // 集合类型的处理器
    reactiveMap          // 缓存映射
  );
}

function createReactiveObject(target, baseHandlers, collectionHandlers, proxyMap) {
  // 如果不是对象,直接返回
  if (!isObject(target)) {
    return target;
  }
  
  // 如果已经是响应式对象,直接返回
  if (target[ReactiveFlags.RAW]) {
    return target;
  }
  
  // 如果已经存在代理,返回缓存
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  
  // 创建代理
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  );
  
  // 缓存代理
  proxyMap.set(target, proxy);
  
  return proxy;
}

4.2 Proxy 处理器

 
// 普通对象的处理器
const mutableHandlers = {
  get(target, key, receiver) {
    // 追踪依赖
    track(target, key);
    
    const res = Reflect.get(target, key, receiver);
    
    // 深层响应式
    if (isObject(res)) {
      return reactive(res);
    }
    
    return res;
  },
  
  set(target, key, value, receiver) {
    const oldValue = target[key];
    
    // 判断是新增属性还是修改属性
    const hadKey = hasOwn(target, key);
    
    const result = Reflect.set(target, key, value, receiver);
    
    if (!hadKey) {
      // 新增属性
      trigger(target, key, TriggerOpTypes.ADD, value);
    } else if (hasChanged(value, oldValue)) {
      // 修改属性
      trigger(target, key, TriggerOpTypes.SET, value, oldValue);
    }
    
    return result;
  },
  
  deleteProperty(target, key) {
    const hadKey = hasOwn(target, key);
    const result = Reflect.deleteProperty(target, key);
    
    if (hadKey && result) {
      trigger(target, key, TriggerOpTypes.DELETE);
    }
    
    return result;
  }
};

4.3 依赖追踪系统

 
// 依赖追踪
const targetMap = new WeakMap();  // 存储所有响应式对象的依赖
let activeEffect = null;          // 当前活动的 effect

function track(target, key) {
  if (!activeEffect) return;
  
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  dep.add(activeEffect);
  activeEffect.deps.push(dep);
}

function trigger(target, key, type, newValue, oldValue) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  const effects = new Set();
  const add = (effectsToAdd) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        effects.add(effect);
      });
    }
  };
  
  // 调度执行
  const run = (effect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect);
    } else {
      effect();
    }
  };
  
  effects.forEach(run);
}

5. 响应式系统的核心组件

5.1 响应式数据创建

 
// Vue 2
new Vue({
  data() {
    return {
      message: 'Hello',
      user: { name: 'John' },
      list: [1, 2, 3]
    };
  }
});

// Vue 3 Composition API
import { reactive, ref } from 'vue';

export default {
  setup() {
    const state = reactive({
      message: 'Hello',
      user: { name: 'John' }
    });
    
    const count = ref(0);
    
    return { state, count };
  }
};

5.2 计算属性实现原理


// 计算属性本质是特殊的 Watcher/Effect
class ComputedRefImpl {
  constructor(getter, setter) {
    this._dirty = true;  // 脏检查标记
    this._value = undefined;
    this._getter = getter;
    this._setter = setter;
    
    // 创建 effect
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true;
        triggerRefValue(this);
      }
    });
  }
  
  get value() {
    // 收集依赖
    trackRefValue(this);
    
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run();
    }
    
    return this._value;
  }
  
  set value(newValue) {
    this._setter(newValue);
  }
}

5.3 侦听器实现原理

 
// watch 实现原理
function watch(source, cb, options) {
  return doWatch(source, cb, options);
}

function doWatch(source, cb, options) {
  let getter;
  let deep = false;
  
  if (typeof source === 'function') {
    getter = source;
  } else {
    getter = () => traverse(source);
    deep = true;
  }
  
  let oldValue = {};
  const job = () => {
    if (cb) {
      const newValue = effect.run();
      if (deep || hasChanged(newValue, oldValue)) {
        cb(newValue, oldValue);
        oldValue = newValue;
      }
    }
  };
  
  const effect = new ReactiveEffect(getter, job);
  
  // 初始执行
  oldValue = effect.run();
  
  if (options.immediate) {
    job();
  }
  
  return () => {
    effect.stop();
  };
}

6. 异步更新队列

6.1 为什么需要异步更新

 
// 同步更新的问题
vm.count = 1;
vm.count = 2;
vm.count = 3;
// 会触发三次重新渲染,性能差

// Vue 的解决方案:异步更新队列
let queue = [];
let waiting = false;

function queueWatcher(watcher) {
  const id = watcher.id;
  
  if (!has[id]) {
    has[id] = true;
    queue.push(watcher);
    
    if (!waiting) {
      waiting = true;
      nextTick(flushSchedulerQueue);
    }
  }
}

function flushSchedulerQueue() {
  queue.sort((a, b) => a.id - b.id);
  
  for (let i = 0; i < queue.length; i++) {
    const watcher = queue[i];
    has[watcher.id] = null;
    watcher.run();
  }
  
  // 重置队列
  resetSchedulerState();
}

6.2 nextTick 实现

 
const callbacks = [];
let pending = false;

function nextTick(cb, ctx) {
  let _resolve;
  
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  
  if (!pending) {
    pending = true;
    timerFunc();  // 异步执行
  }
  
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve;
    });
  }
}

// 异步执行器
let timerFunc;

if (typeof Promise !== 'undefined') {
  // 优先使用 Promise
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== 'undefined') {
  // 降级到 MutationObserver
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, { characterData: true });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== 'undefined') {
  // 再降级到 setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 最后使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

7. 虚拟DOM与响应式结合

7.1 渲染Watcher


// 渲染 Watcher
new Watcher(vm, updateComponent, noop, {
  before() {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);

function updateComponent() {
  vm._update(vm._render(), hydrating);
}

7.2 Patch 过程

 
// 虚拟DOM对比更新
function patch(oldVnode, vnode, hydrating, removeOnly) {
  if (isUndef(vnode)) {
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
    return;
  }
  
  let isInitialPatch = false;
  const insertedVnodeQueue = [];
  
  if (isUndef(oldVnode)) {
    // 新增节点
    isInitialPatch = true;
    createElm(vnode, insertedVnodeQueue);
  } else {
    // 更新节点
    const isRealElement = isDef(oldVnode.nodeType);
    
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // 相同节点,进行 patch
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
    } else {
      // 不同节点,替换
      createElm(vnode, insertedVnodeQueue, oldVnode.elm);
    }
  }
  
  return vnode.elm;
}

8. Vue 2 与 Vue 3 响应式对比

8.1 技术实现对比

特性Vue 2 (Object.defineProperty)Vue 3 (Proxy)
数据劫持方式属性劫持代理整个对象
数组响应式需要重写方法原生支持
新增属性检测不支持支持
删除属性检测不支持支持
性能递归遍历所有属性惰性代理
内存占用每个属性一个 Dep按需收集

8.2 使用差异

 
// Vue 2
export default {
  data() {
    return {
      user: { name: 'John' },
      list: [1, 2, 3]
    };
  },
  created() {
    // ❌ 不会响应
    this.user.age = 30;
    this.list[0] = 100;
    
    // ✅ 需要特殊处理
    this.$set(this.user, 'age', 30);
    this.list.splice(0, 1, 100);
  }
};

// Vue 3
import { reactive } from 'vue';

export default {
  setup() {
    const state = reactive({
      user: { name: 'John' },
      list: [1, 2, 3]
    });
    
    // ✅ 直接响应
    state.user.age = 30;    // 自动响应
    state.list[0] = 100;    // 自动响应
    delete state.user.name; // 自动响应
    
    return { state };
  }
};

9. 响应式系统的限制和注意事项

9.1 无法检测的变化

 
// Vue 2 中的限制
const vm = new Vue({
  data: {
    obj: { a: 1 },
    arr: [1, 2, 3]
  }
});

// 1. 新增对象属性
vm.obj.b = 2;  // ❌ 不会响应
Vue.set(vm.obj, 'b', 2);  // ✅ 正确

// 2. 删除对象属性
delete vm.obj.a;  // ❌ 不会响应
Vue.delete(vm.obj, 'a');  // ✅ 正确

// 3. 数组索引设置
vm.arr[0] = 100;  // ❌ 不会响应
vm.arr.splice(0, 1, 100);  // ✅ 正确
Vue.set(vm.arr, 0, 100);  // ✅ 正确

// 4. 修改数组长度
vm.arr.length = 0;  // ❌ 不会响应
vm.arr.splice(0);  // ✅ 正确

9.2 性能优化

 
// 1. 避免大数据结构的响应式
data() {
  return {
    // ❌ 大型列表完全响应式
    largeList: Array(10000).fill().map((_, i) => ({ id: i })),
    
    // ✅ 只对需要的属性响应式
    paginatedList: [],  // 分页加载
  };
},

// 2. 冻结不需要响应的数据
data() {
  return {
    config: Object.freeze({  // 不会被响应式处理
      apiUrl: '/api',
      timeout: 5000
    })
  };
},

// 3. 合理使用计算属性缓存
computed: {
  // ✅ 自动缓存
  filteredItems() {
    return this.items.filter(item => item.active);
  }
},

10. 完整示例代码

 
// 简化的 Vue 响应式系统实现
class SimpleVue {
  constructor(options) {
    this.$options = options;
    this.$data = options.data();
    
    // 响应式化
    this.observe(this.$data);
    
    // 编译
    new Compile(options.el, this);
    
    // 执行 created 钩子
    options.created && options.created.call(this);
  }
  
  observe(data) {
    if (!data || typeof data !== 'object') return;
    
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
      this.proxyData(key);
    });
  }
  
  defineReactive(obj, key, val) {
    const dep = new Dep();
    
    this.observe(val);
    
    Object.defineProperty(obj, key, {
      get() {
        Dep.target && dep.depend();
        return val;
      },
      set(newVal) {
        if (newVal === val) return;
        
        val = newVal;
        this.observe(newVal);
        dep.notify();
      }
    });
  }
  
  proxyData(key) {
    Object.defineProperty(this, key, {
      get() { return this.$data[key]; },
      set(val) { this.$data[key] = val; }
    });
  }
}

// 使用示例
const app = new SimpleVue({
  el: '#app',
  data() {
    return {
      message: 'Hello Vue',
      count: 0
    };
  },
  created() {
    console.log('实例创建完成');
  }
});

// 自动更新
app.message = 'Changed';  // 触发更新

11. 总结

Vue 响应式系统的核心要点

  1. 数据劫持

    • Vue 2: Object.defineProperty
    • Vue 3: Proxy
  2. 依赖收集

    • 每个响应式数据都有对应的依赖收集器
    • 在 getter 中收集当前正在计算的 Watcher
  3. 派发更新

    • 数据变化时通知所有依赖的 Watcher
    • 通过异步更新队列优化性能
  4. 虚拟DOM

    • Watcher 触发重新渲染
    • 生成新的虚拟DOM
    • Diff 算法对比更新真实DOM

设计思想

  • 数据驱动视图
  • 组件级更新
  • 异步批量更新
  • 按需响应(Vue 3)

实际应用理解

 
// 当你在 Vue 中这样写时:
this.message = 'new value';

// Vue 内部发生的事情:
1. 触发 message 的 setter
2. 通知相关的 Dep
3. Dep 通知所有 Watcher
4. Watcher 加入更新队列
5. nextTick 后执行更新
6. 重新渲染组件
7. 虚拟DOM diff
8. 更新真实DOM

这就是 Vue 响应式系统的完整工作原理!