Skip to content

Vue 响应式系统

概述

Vue 3 使用 Proxy 实现响应式系统,是整个框架的核心。理解响应式原理对于掌握 Vue 至关重要。

一、Vue 2 vs Vue 3 响应式对比

特性Vue 2 (defineProperty)Vue 3 (Proxy)
数组监听需要重写数组方法原生支持
新增属性需要 $set自动响应
删除属性需要 $delete自动响应
性能初始化递归遍历惰性代理
Map/Set不支持支持
兼容性IE9+不支持 IE

二、Proxy 基础

Proxy 拦截操作

javascript
const target = { name: 'Vue' };

const proxy = new Proxy(target, {
  // 读取属性
  get(target, key, receiver) {
    console.log(`get ${key}`);
    return Reflect.get(target, key, receiver);
  },
  
  // 设置属性
  set(target, key, value, receiver) {
    console.log(`set ${key} = ${value}`);
    return Reflect.set(target, key, value, receiver);
  },
  
  // 删除属性
  deleteProperty(target, key) {
    console.log(`delete ${key}`);
    return Reflect.deleteProperty(target, key);
  },
  
  // 检查属性
  has(target, key) {
    console.log(`has ${key}`);
    return Reflect.has(target, key);
  },
  
  // 获取所有 key
  ownKeys(target) {
    console.log('ownKeys');
    return Reflect.ownKeys(target);
  }
});

proxy.name;      // get name
proxy.age = 18;  // set age = 18
delete proxy.age; // delete age
'name' in proxy; // has name
Object.keys(proxy); // ownKeys

为什么使用 Reflect?

javascript
const obj = {
  _name: 'Vue',
  get name() {
    return this._name; // this 指向很重要
  }
};

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    // ❌ 直接访问 target,getter 中的 this 是原对象
    // return target[key];
    
    // ✅ 使用 Reflect,getter 中的 this 是 proxy
    return Reflect.get(target, key, receiver);
  }
});

三、响应式核心实现

核心 API

javascript
// reactive - 对象响应式
const state = reactive({ count: 0 });

// ref - 基本类型响应式
const count = ref(0);
count.value++; // 需要 .value

// shallowReactive - 浅层响应式
const state = shallowReactive({ nested: { a: 1 } });
state.nested = { a: 2 }; // 响应式
state.nested.a = 3;      // 非响应式

// readonly - 只读代理
const state = readonly({ count: 0 });
state.count++; // 警告,不生效

// shallowReadonly - 浅层只读

reactive 实现

javascript
// 缓存已代理对象
const reactiveMap = new WeakMap();

function reactive(target) {
  // 只处理对象
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  // 已经是响应式对象
  if (reactiveMap.has(target)) {
    return reactiveMap.get(target);
  }
  
  const proxy = new Proxy(target, {
    get(target, key, receiver) {
      // 特殊标记
      if (key === '__v_isReactive') return true;
      
      const result = Reflect.get(target, key, receiver);
      
      // 依赖收集
      track(target, key);
      
      // 深层响应式(惰性)
      if (typeof result === 'object' && result !== null) {
        return reactive(result);
      }
      
      return result;
    },
    
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      
      // 值变化时触发更新
      if (oldValue !== value) {
        trigger(target, key);
      }
      
      return result;
    },
    
    deleteProperty(target, key) {
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const result = Reflect.deleteProperty(target, key);
      
      if (hadKey && result) {
        trigger(target, key);
      }
      
      return result;
    }
  });
  
  reactiveMap.set(target, proxy);
  return proxy;
}

ref 实现

javascript
function ref(value) {
  return new RefImpl(value);
}

class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value;
    this.__v_isRef = true;
  }
  
  get value() {
    track(this, 'value');
    return this._value;
  }
  
  set value(newValue) {
    if (newValue !== this._value) {
      this._value = isObject(newValue) ? reactive(newValue) : newValue;
      trigger(this, 'value');
    }
  }
}

// 自动解包
function unref(ref) {
  return isRef(ref) ? ref.value : ref;
}

// 在模板中自动解包
// template: {{ count }} 而不是 {{ count.value }}

四、依赖收集与派发更新

核心数据结构

targetMap: WeakMap {
  target -> depsMap: Map {
    key -> dep: Set [ effect1, effect2, ... ]
  }
}

依赖收集 (track)

javascript
let activeEffect = null;
const targetMap = new WeakMap();

function track(target, key) {
  if (!activeEffect) return;
  
  // 获取或创建 depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取或创建 dep
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  // 添加当前 effect
  dep.add(activeEffect);
  
  // 双向记录,用于清理
  activeEffect.deps.push(dep);
}

派发更新 (trigger)

javascript
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  const dep = depsMap.get(key);
  if (!dep) return;
  
  // 创建新 Set 避免无限循环
  const effectsToRun = new Set();
  
  dep.forEach(effect => {
    // 避免无限递归
    if (effect !== activeEffect) {
      effectsToRun.add(effect);
    }
  });
  
  effectsToRun.forEach(effect => {
    // 调度器
    if (effect.scheduler) {
      effect.scheduler(effect);
    } else {
      effect.run();
    }
  });
}

effect 实现

javascript
function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn, options.scheduler);
  
  // 立即执行一次
  if (!options.lazy) {
    _effect.run();
  }
  
  // 返回 runner
  const runner = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}

class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn;
    this.scheduler = scheduler;
    this.deps = [];
    this.active = true;
  }
  
  run() {
    if (!this.active) {
      return this.fn();
    }
    
    // 清理旧依赖
    cleanup(this);
    
    // 设置当前 effect
    activeEffect = this;
    
    // 执行函数,触发依赖收集
    const result = this.fn();
    
    // 重置
    activeEffect = null;
    
    return result;
  }
  
  stop() {
    if (this.active) {
      cleanup(this);
      this.active = false;
    }
  }
}

function cleanup(effect) {
  effect.deps.forEach(dep => {
    dep.delete(effect);
  });
  effect.deps.length = 0;
}

五、computed 实现

javascript
function computed(getterOrOptions) {
  let getter, setter;
  
  if (typeof getterOrOptions === 'function') {
    getter = getterOrOptions;
    setter = () => console.warn('computed is readonly');
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  
  return new ComputedRefImpl(getter, setter);
}

class ComputedRefImpl {
  constructor(getter, setter) {
    this._dirty = true; // 脏值标记
    this._value = undefined;
    this._setter = setter;
    
    // 创建 effect,使用 scheduler
    this.effect = new ReactiveEffect(getter, () => {
      // 依赖变化时,标记为脏
      if (!this._dirty) {
        this._dirty = true;
        trigger(this, 'value');
      }
    });
  }
  
  get value() {
    track(this, 'value');
    
    // 脏值时重新计算
    if (this._dirty) {
      this._value = this.effect.run();
      this._dirty = false;
    }
    
    return this._value;
  }
  
  set value(newValue) {
    this._setter(newValue);
  }
}

computed 特性

javascript
const count = ref(0);
const double = computed(() => {
  console.log('computed');
  return count.value * 2;
});

// 1. 惰性计算
console.log(double.value); // 'computed', 0
console.log(double.value); // 0 (使用缓存)

// 2. 依赖变化后重新计算
count.value = 1;
console.log(double.value); // 'computed', 2

六、watch 实现

javascript
function watch(source, cb, options = {}) {
  let getter;
  
  if (typeof source === 'function') {
    getter = source;
  } else if (isRef(source)) {
    getter = () => source.value;
  } else if (isReactive(source)) {
    getter = () => traverse(source); // 深度遍历
  }
  
  let oldValue;
  
  const job = () => {
    const newValue = effect.run();
    if (newValue !== oldValue) {
      cb(newValue, oldValue);
      oldValue = newValue;
    }
  };
  
  const effect = new ReactiveEffect(getter, job);
  
  if (options.immediate) {
    job();
  } else {
    oldValue = effect.run();
  }
  
  // 返回 stop 函数
  return () => effect.stop();
}

// 深度遍历
function traverse(value, seen = new Set()) {
  if (typeof value !== 'object' || value === null || seen.has(value)) {
    return value;
  }
  
  seen.add(value);
  
  for (const key in value) {
    traverse(value[key], seen);
  }
  
  return value;
}

七、响应式边界情况

数组处理

javascript
const arr = reactive([1, 2, 3]);

// 索引访问 - 响应式
arr[0] = 10;

// length 变化 - 响应式
arr.length = 2;

// 数组方法 - 需要特殊处理
arr.push(4); // 会触发 get('push'), get('length'), set('3')

集合类型处理

javascript
// Map
const map = reactive(new Map());
map.set('key', 'value'); // 需要拦截 set 方法

// Set
const set = reactive(new Set());
set.add(1); // 需要拦截 add 方法

// 实现要点:拦截 get 方法,返回绑定的函数

面试高频题

Q1: Vue 3 为什么用 Proxy 替代 defineProperty?

:

  1. 数组支持: Proxy 可以监听数组索引和 length 变化
  2. 新增/删除属性: 无需 $set/$delete
  3. 性能: 惰性代理,不需要初始化递归
  4. 更多拦截: has, ownKeys, deleteProperty 等

Q2: ref 和 reactive 的区别?

:

refreactive
包装基本类型只接受对象
需要 .value直接访问
可以重新赋值整体不能重新赋值整体
返回 RefImpl返回 Proxy

Q3: computed 和 watch 的区别?

:

computedwatch
有返回值无返回值
缓存结果不缓存
同步计算可异步
不能有副作用专门处理副作用

Q4: 为什么要用 WeakMap 存储依赖?

: WeakMap 的 key 是弱引用,当 target 对象被回收时,对应的依赖也会被自动回收,避免内存泄漏。

前端面试知识库