前言:响应式系统的全景图

在开始整合之前,让我们先回顾一下整个响应式系统的架构: Vue3 中的响应式系统的核心思想可以概括为:在读取时收集依赖,在修改时触发更新。

串联所有组件:完整的响应式系统

1. 基础工具函数

// ============ 工具函数 ============
function isObject(value) {
    return value !== null && typeof value === 'object';
}

function isFunction(value) {
    return typeof value === 'function';
}

function isArray(value) {
    return Array.isArray(value);
}

function isRef(r) {
    return !!(r && r.__v_isRef === true);
}

function isReactive(value) {
    return !!(value && value.__v_isReactive === true);
}

function isArrayIndex(key) {
    if (typeof key !== 'string') return false;
    const keyAsNumber = Number(key);
    return Number.isInteger(keyAsNumber) &&
           keyAsNumber >= 0 &&
           keyAsNumber < Number.MAX_SAFE_INTEGER;
}

2. 依赖管理核心

// ============ 依赖管理 ============
const targetMap = new WeakMap();
let activeEffect = null;

// 操作类型枚举
const TrackOpTypes = {
    GET: 'get',
    HAS: 'has',
    ITERATE: 'iterate'
};

const TriggerOpTypes = {
    SET: 'set',
    ADD: 'add',
    DELETE: 'delete',
    CLEAR: 'clear'
};

// 特殊标识
const ITERATE_KEY = Symbol('iterate');

class ReactiveEffect {
    constructor(fn, scheduler = null) {
        this.fn = fn;
        this.scheduler = scheduler;
        this.deps = [];
        this.active = true;
        this.runDepth = 0;
    }
    
    run() {
        if (!this.active) {
            return this.fn();
        }
        
        try {
            this.runDepth++;
            if (this.runDepth > 1000) {
                console.warn('检测到可能的无限循环');
                return;
            }
            
            activeEffect = this;
            return this.fn();
        } finally {
            this.runDepth--;
            activeEffect = null;
        }
    }
    
    stop() {
        if (this.active) {
            this.active = false;
            this.deps.forEach(dep => dep.delete(this));
            this.deps.length = 0;
        }
    }
}

function track(target, type, key) {
    if (!activeEffect) return;
    
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    
    // 处理迭代操作
    let depKey = key;
    if (type === TrackOpTypes.ITERATE) {
        depKey = ITERATE_KEY;
    }
    
    let dep = depsMap.get(depKey);
    if (!dep) {
        depsMap.set(depKey, (dep = new Set()));
    }
    
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
    }
}

function trigger(target, type, key, newValue, oldValue) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    
    const effectsToRun = new Set();
    
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                if (effect !== activeEffect) {
                    effectsToRun.add(effect);
                }
            });
        }
    };
    
    // 处理普通 key
    if (key !== undefined) {
        add(depsMap.get(key));
    }
    
    // 处理数组特殊情况
    if (Array.isArray(target)) {
        if (key === 'length') {
            // length 变化需要触发所有索引 >= 新值的依赖
            const newLength = Number(newValue);
            depsMap.forEach((dep, key) => {
                if (isArrayIndex(key) && Number(key) >= newLength) {
                    add(dep);
                }
            });
        } else if (type === TriggerOpTypes.ADD && isArrayIndex(key)) {
            // 添加数组元素触发 length
            add(depsMap.get('length'));
        }
    } else {
        // 处理迭代操作
        if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
            add(depsMap.get(ITERATE_KEY));
        }
    }
    
    // 执行 effects
    effectsToRun.forEach(effect => {
        if (effect.scheduler) {
            effect.scheduler();
        } else {
            effect.run();
        }
    });
}

function effect(fn) {
    const _effect = new ReactiveEffect(fn);
    _effect.run();
    
    const runner = _effect.run.bind(_effect);
    runner.effect = _effect;
    return runner;
}

3. Reactive 实现

// ============ reactive ============
const reactiveHandlers = {
    get(target, key, receiver) {
        // 内部标记
        if (key === '__v_isReactive') return true;
        
        const value = Reflect.get(target, key, receiver);
        
        // 依赖收集
        track(target, TrackOpTypes.GET, key);
        
        // 嵌套响应式
        if (isObject(value)) {
            return reactive(value);
        }
        
        return value;
    },
    
    set(target, key, value, receiver) {
        const oldValue = target[key];
        const hadKey = target.hasOwnProperty(key);
        const oldLength = Array.isArray(target) ? target.length : undefined;
        
        const result = Reflect.set(target, key, value, receiver);
        
        if (!hadKey) {
            // 新增属性
            trigger(target, TriggerOpTypes.ADD, key, value);
        } else if (oldValue !== value) {
            // 修改属性
            trigger(target, TriggerOpTypes.SET, key, value, oldValue);
        }
        
        // 数组 length 隐式变化
        if (Array.isArray(target) && oldLength !== target.length) {
            trigger(target, TriggerOpTypes.SET, 'length', target.length);
        }
        
        return result;
    },
    
    deleteProperty(target, key) {
        const hadKey = target.hasOwnProperty(key);
        const oldValue = target[key];
        
        const result = Reflect.deleteProperty(target, key);
        
        if (result && hadKey) {
            trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
        }
        
        return result;
    },
    
    has(target, key) {
        track(target, TrackOpTypes.HAS, key);
        return Reflect.has(target, key);
    },
    
    ownKeys(target) {
        track(target, TrackOpTypes.ITERATE, ITERATE_KEY);
        return Reflect.ownKeys(target);
    }
};

function reactive(target) {
    if (!isObject(target)) return target;
    if (target.__v_isReactive) return target;
    
    return new Proxy(target, reactiveHandlers);
}

4. Ref 实现

// ============ ref ============
class RefImpl {
    constructor(value, isShallow = false) {
        this._rawValue = value;
        this._value = isShallow ? value : toReactive(value);
        this.__v_isRef = true;
        this._isShallow = isShallow;
    }
    
    get value() {
        track(this, TrackOpTypes.GET, 'value');
        return this._value;
    }
    
    set value(newValue) {
        if (newValue !== this._rawValue) {
            this._rawValue = newValue;
            this._value = this._isShallow ? newValue : toReactive(newValue);
            trigger(this, TriggerOpTypes.SET, 'value', newValue);
        }
    }
}

function toReactive(value) {
    return isObject(value) ? reactive(value) : value;
}

function ref(value) {
    if (isRef(value)) return value;
    return new RefImpl(value);
}

function shallowRef(value) {
    return new RefImpl(value, true);
}

function isRef(r) {
    return !!(r && r.__v_isRef === true);
}

function unref(ref) {
    return isRef(ref) ? ref.value : ref;
}

function toReactive(value) {
    return isObject(value) ? reactive(value) : value;
}

class ObjectRefImpl {
    constructor(_object, _key) {
        this._object = _object;
        this._key = _key;
        this.__v_isRef = true;
    }
    
    get value() {
        return this._object[this._key];
    }
    
    set value(newValue) {
        this._object[this._key] = newValue;
    }
}

function toRef(object, key) {
    return new ObjectRefImpl(object, key);
}

function toRefs(object) {
    const result = {};
    for (const key in object) {
        if (object.hasOwnProperty(key)) {
            result[key] = toRef(object, key);
        }
    }
    return result;
}

5. Computed 实现

// ============ computed ============
class ComputedRefImpl {
    constructor(getter, setter) {
        this.getter = getter;
        this.setter = setter;
        this._dirty = true;
        this._value = undefined;
        this.__v_isRef = true;
        
        this.effect = new ReactiveEffect(getter, () => {
            if (!this._dirty) {
                this._dirty = true;
                trigger(this, TriggerOpTypes.SET, 'value');
            }
        });
    }
    
    get value() {
        track(this, TrackOpTypes.GET, 'value');
        
        if (this._dirty) {
            this._dirty = false;
            this._value = this.effect.run();
        }
        
        return this._value;
    }
    
    set value(newValue) {
        if (this.setter) {
            this.setter(newValue);
        } else {
            console.warn('计算属性是只读的');
        }
    }
}

function computed(getterOrOptions) {
    let getter, setter;
    
    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions;
        setter = null;
    } else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    
    return new ComputedRefImpl(getter, setter);
}

6. Watch 实现

// ============ watch ============
function traverse(value, seen = new Set()) {
    if (!isObject(value) || seen.has(value)) return value;
    
    seen.add(value);
    
    if (Array.isArray(value)) {
        value.forEach(item => traverse(item, seen));
    } else if (value instanceof Map) {
        value.forEach((v, k) => {
            traverse(v, seen);
            traverse(k, seen);
        });
    } else if (value instanceof Set) {
        value.forEach(v => traverse(v, seen));
    } else {
        Object.keys(value).forEach(key => traverse(value[key], seen));
    }
    
    return value;
}

function watch(source, cb, options = {}) {
    let getter;
    
    if (isRef(source)) {
        getter = () => source.value;
    } else if (isReactive(source)) {
        getter = () => source;
        options.deep = options.deep ?? true;
    } else if (Array.isArray(source)) {
        getter = () => source.map(s => {
            if (isRef(s)) return s.value;
            if (isReactive(s)) return traverse(s);
            if (isFunction(s)) return s();
            return s;
        });
    } else if (isFunction(source)) {
        if (cb) {
            getter = source;
        } else {
            return watchEffect(source, options);
        }
    }
    
    if (options.deep) {
        const baseGetter = getter;
        getter = () => traverse(baseGetter());
    }
    
    let oldValue;
    let cleanup;
    
    function onInvalidate(fn) {
        cleanup = fn;
    }
    
    const scheduler = () => {
        if (cleanup) cleanup();
        
        const newValue = getter();
        
        if (newValue !== oldValue) {
            cb(newValue, oldValue, onInvalidate);
        }
        
        oldValue = newValue;
    };
    
    const _effect = new ReactiveEffect(getter, scheduler);
    
    if (options.immediate) {
        scheduler();
    } else {
        oldValue = _effect.run();
    }
    
    return () => {
        _effect.stop();
        if (cleanup) cleanup();
    };
}

function watchEffect(effect, options = {}) {
    const scheduler = () => {
        if (options.flush === 'post') {
            Promise.resolve().then(() => _effect.run());
        } else {
            _effect.run();
        }
    };
    
    const _effect = new ReactiveEffect(effect, scheduler);
    _effect.run();
    
    return () => {
        _effect.stop();
    };
}

常见面试题解析

面试题 1:Vue3 的响应式原理是什么?

核心原理:Proxy + 依赖收集:

  1. 通过 Proxy 代理对象的所有操作
  2. 在 get 中通过 track 收集依赖
  3. 在 set 中通过 trigger 触发更新
  4. 使用 WeakMap + Map + Set 三层结构存储依赖
  5. 通过 effect 管理系统中的副作用
┌─────────────────────────────────────────────────────────────┐
│                    1. Proxy代理对象                          │
│                                                             │
│   ┌──────────────┐         ┌──────────────────────┐         │
│   │   原始对象     │        │     Proxy代理         │         │
│   │  {            │  代理  │   get: track收集      │         │
│   │    count: 0,  │◄───────┤   set: trigger触发    │        │
│   │    name: 'vue'│        │   deleteProperty     │         │
│   │  }            │        │   has...             │         │
│   └──────────────┘         └──────────────────────┘         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│             2. 依赖收集 (track)                              │
│                                                             │
│   get操作触发 ──→ track函数 ──→ 查找依赖存储                    │
│                                                             │
│   ┌──────────────────────────────────────────┐              │
│   │        3. 三层依赖存储结构                  │              │
│   │                                          │              │
│   │   WeakMap          Map          Set      │              │
│   │   ┌─────┐        ┌─────┐      ┌─────┐    │              │
│   │   │target│──────►│key1 │─────►│effect1│  │              │
│   │   └─────┘        ├─────┤      ├─────┤    │              │
│   │                  │key2 │─┐    │effect2│  │              │
│   │                  └─────┘ │    └─────┘    │              │
│   │                           └───►┌─────┐   │              │
│   │                                │effect3│ │              │
│   │                                └─────┘   │              │
│   └──────────────────────────────────────────┘              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│             4. 副作用管理 (effect)                            │
│                                                             │
│   ┌────────────────────────────────────────┐                │
│   │  effect(() => {                        │                │
│   │    console.log(obj.count)  // 依赖收集  │                │
│   │  })                                    │                │
│   └────────────────────────────────────────┘                │
│                                                             │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐              │
│   │  effect1 │    │  effect2 │    │  effect3 │              │
│   │ (更新UI) │     │(计算属性)│     │ (watch)  │              │
│   └──────────┘    └──────────┘    └──────────┘              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────┐
│             5. 触发更新 (trigger)                             │
│                                                              │
│   set操作触发 ──→ trigger函数 ──→ 从存储结构中查找依赖            │
│                                          │                   │
│                                          ▼                   │
│   ┌──────────────────────────────────────────────────┐       │
│   │           执行所有相关的副作用函数                   │       │
│   │                                                  │       │
│   │   obj.count = 1                                  │       │
│   │        │                                         │       │
│   │        ▼                                         │       │
│   │   触发更新 ──→ 执行effect1 ──→ 更新UI               │       │
│   │            └─→ 执行effect2 ──→ 重新计算            │       │
│   └──────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────────┘

面试题 2:Vue2 的 Object.defineProperty 和 Vue3 的 Proxy 有什么区别?

  1. Proxy 可以新增/删除属性;defineProperty 不能
  2. Proxy 可以数组索引和 length;defineProperty 不行
  3. Proxy 需要递归代理;defineProperty 初始化递归
  4. Proxy 性能更好,拦截操作更丰富

面试题 3:为什么 ref 需要 .value 而 reactive 不需要?

因为 Proxy 无法代理原始值,对于原始值的代理需要通过 value 包裹成对象:

let count = 0; // 原始值,无法代理
// ref 包装成对象
const countRef = {
    value: 0
};
// 现在可以对 countRef 进行代理

面试题 4:computed 和 watch 有什么区别?

对比维度computedwatch
概念计算属性,基于依赖缓存的计算值侦听器,执行副作用操作
缓存机制有缓存,只有依赖变化时才重新计算无缓存,每次到变化都执行
返回值必须返回一个值,模板中可直接使用通常不返回值,用于执行逻辑操作
依赖追踪自动追踪响应式依赖手动指定要侦听的数据源
执行时机懒执行,只有访问时才重新计算立即执行(可配置)或数据变化时执行
异步操作不支持异步支持异步操作
性能特点适合衍生状态,避免重复计算适合处理开销大的操作或异步逻辑
使用场景1. 模板中复杂表达式
2. 依赖其他数据的衍生值
3. 需要缓存的场景
1. 数据变化时执行异步操作
2. 操作DOM
3. 执行开销大的操作
访问方式作为属性访问:state.count通过回调函数执行
深度自动深度追踪依赖需要手动配置 deep: true
立即执行自动计算需要配置 immediate: true

面试题 5:Vue3 的响应式系统如何避免循环依赖?

  1. activeEffect 守卫:
function trigger(target, key) {
    const effects = depsMap.get(key);
    effects.forEach(effect => {
        // 跳过当前正在执行的 effect
        if (effect !== activeEffect) {
            effect.run();
        }
    });
}
  1. 使用 Set 避免重复收集
dep.add(activeEffect); // 自动去重
  1. 递归深度限制
class ReactiveEffect {
    run() {
        this.runDepth++;
        if (this.runDepth > 1000) {
            console.warn('检测到无限循环');
            return;
        }
        // ... 执行逻辑
        this.runDepth--;
    }
}

性能分析:Vue3 响应式比 Vue2 快在哪里?

1. 初始化性能对比

// Vue2:递归遍历所有属性
function vue2Init(obj) {
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key);
    });
    return obj;
}

// Vue3:代理整个对象,懒递归
function vue3Init(obj) {
    return new Proxy(obj, handlers); // 不递归
    // 只有在访问嵌套对象时才递归转换
}

性能差异:

  • Vue2: O(n) 初始化时间,n 为所有属性数量
  • Vue3: O(1) 初始化时间,只创建代理

2. 内存占用对比

// Vue2:为每个属性创建闭包
function defineReactive(obj, key) {
    let value = obj[key];
    const dep = new Dep(); // 每个属性一个 dep
    
    Object.defineProperty(obj, key, {
        get() {
            dep.depend(); // 闭包引用
            return value;
        },
        set(newVal) {
            dep.notify();
        }
    });
}

// Vue3:共享 handlers,使用 WeakMap 存储依赖
const targetMap = new WeakMap(); // 依赖统一存储
const handlers = {}; // 单例,不重复创建

性能差异:

  • Vue2: 每个属性都有独立的 getter/setter 和闭包
  • Vue3: 所有对象共享 handlers,依赖集中存储

3. 数组操作性能

// Vue2:重写数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift'];
arrayMethods.forEach(method => {
    const original = Array.prototype[method];
    Object.defineProperty(array, method, {
        value: function(...args) {
            const result = original.apply(this, args);
            // 额外触发更新
            return result;
        }
    });
});

// Vue3:Proxy 直接拦截
const arr = new Proxy([], {
    set(target, key, value) {
        target[key] = value;
        // 统一处理更新
        return true;
    }
});

性能差异:

  • Vue2: 需要拦截每个方法,有额外开销
  • Vue3: 统一通过 set 拦截,更高效

4. 编译时优化

Vue3 性能提升:

  • 静态节点只创建一次
  • 更新时只比较动态部分
  • 减少了不必要的 VNode 创建

5. 批量更新机制

// Vue2:同步更新
state.count++;
state.name = '张三'; // 触发两次更新

// Vue3:异步批量更新
state.count++;
state.name = '张三';
// 只触发一次更新

性能差异:

  • Vue2: 多次同步更新导致多次渲染
  • Vue3: 批量处理,减少渲染次数

结语

经过多篇文章的深入探索,我们完成了 Vue3 响应式系统的完整学习。响应式系统的设计思想,不仅适用于 Vue,也为我们理解和构建响应式应用提供了宝贵的参考。从底层原理到上层 API,每一层都是精心设计的结果,共同构成了这个优雅而强大的系统。

对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com