Vue 的双向绑定原理

68 阅读2分钟

1. Vue 3 的响应式系统

// 1. 响应式核心 - Proxy 实现
function reactive(target) {
    // 防止重复代理
    if (isProxy(target)) {
        return target;
    }
    
    const handler = {
        get(target, key, receiver) {
            const result = Reflect.get(target, key, receiver);
            // 依赖收集
            track(target, key);
            // 如果是对象,继续代理
            return isObject(result) ? reactive(result) : result;
        },
        
        set(target, key, value, receiver) {
            const oldValue = target[key];
            const result = Reflect.set(target, key, value, receiver);
            // 如果值发生变化,触发更新
            if (hasChanged(value, oldValue)) {
                trigger(target, key);
            }
            return result;
        },
        
        deleteProperty(target, key) {
            const hadKey = hasOwn(target, key);
            const result = Reflect.deleteProperty(target, key);
            if (hadKey && result) {
                // 触发更新
                trigger(target, key);
            }
            return result;
        }
    };
    
    return new Proxy(target, handler);
}

// 2. 依赖收集
const targetMap = new WeakMap();

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);
}

// 3. 触发更新
function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    
    const dep = depsMap.get(key);
    if (dep) {
        dep.forEach(effect => {
            if (effect.scheduler) {
                effect.scheduler();
            } else {
                effect();
            }
        });
    }
}

// 4. 副作用函数
let activeEffect = null;

function effect(fn, options = {}) {
    const effectFn = () => {
        try {
            activeEffect = effectFn;
            return fn();
        } finally {
            activeEffect = null;
        }
    };
    
    if (!options.lazy) {
        effectFn();
    }
    
    if (options.scheduler) {
        effectFn.scheduler = options.scheduler;
    }
    
    return effectFn;
}

2. ref 的实现

class RefImpl {
    private _value: any;
    public dep: Set<any>;
    public __v_isRef = true;
    
    constructor(value) {
        this._value = isObject(value) ? reactive(value) : value;
        this.dep = new Set();
    }
    
    get value() {
        track(this, 'value');
        return this._value;
    }
    
    set value(newValue) {
        if (hasChanged(newValue, this._value)) {
            this._value = isObject(newValue) ? reactive(newValue) : newValue;
            trigger(this, 'value');
        }
    }
}

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

3. computed 的实现

class ComputedRefImpl {
    private _getter: () => any;
    private _dirty = true;
    private _value: any;
    private effect: any;
    
    constructor(getter) {
        this._getter = getter;
        this.effect = effect(getter, {
            lazy: true,
            scheduler: () => {
                if (!this._dirty) {
                    this._dirty = true;
                    trigger(this, 'value');
                }
            }
        });
    }
    
    get value() {
        if (this._dirty) {
            this._value = this.effect();
            this._dirty = false;
        }
        track(this, 'value');
        return this._value;
    }
}

function computed(getter) {
    return new ComputedRefImpl(getter);
}

4. 实际应用示例

<template>
  <div>
    <input v-model="message" />
    <p>Message: {{ message }}</p>
    <p>Reversed: {{ reversedMessage }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 响应式数据
const message = ref('Hello Vue!')

// 计算属性
const reversedMessage = computed(() => {
    return message.value.split('').reverse().join('')
})

// 监听变化
watch(message, (newValue, oldValue) => {
    console.log('Message changed:', newValue)
})
</script>

5. v-model 的实现原理

// 简化版的 v-model 指令实现
const vModelDirective = {
    mounted(el, binding, vnode) {
        el.addEventListener('input', e => {
            binding.value = e.target.value
        })
        
        effect(() => {
            el.value = binding.value
        })
    }
}

// 编译后的模板
function render() {
    return h('input', {
        value: message.value,
        onInput: e => (message.value = e.target.value)
    })
}

6. 批量更新机制

// 异步更新队列
const queue = new Set()
let isFlushing = false
const p = Promise.resolve()

function queueJob(job) {
    queue.add(job)
    if (!isFlushing) {
        isFlushing = true
        p.then(() => {
            try {
                queue.forEach(job => job())
            } finally {
                isFlushing = false
                queue.clear()
            }
        })
    }
}

// 在 trigger 中使用
function trigger(target, key) {
    const deps = getDeps(target, key)
    const effects = new Set()
    
    deps.forEach(dep => {
        dep.forEach(effect => {
            if (effect !== activeEffect) {
                effects.add(effect)
            }
        })
    })
    
    effects.forEach(effect => {
        if (effect.scheduler) {
            effect.scheduler()
        } else {
            queueJob(effect)
        }
    })
}

7. 调试和性能优化

// 开发环境的调试辅助
function createReactiveObject(target, handler) {
    if (process.env.NODE_ENV !== 'production') {
        const proxy = new Proxy(target, {
            ...handler,
            get(target, key, receiver) {
                console.log(`Getting property "${key}"`)
                return handler.get(target, key, receiver)
            },
            set(target, key, value, receiver) {
                console.log(`Setting property "${key}" = ${value}`)
                return handler.set(target, key, value, receiver)
            }
        })
        return proxy
    }
    return new Proxy(target, handler)
}

// 性能优化:避免不必要的代理
function reactive(target) {
    // 基础类型直接返回
    if (!isObject(target)) {
        return target
    }
    
    // 避免重复代理
    if (isProxy(target)) {
        return target
    }
    
    // 缓存已创建的代理
    if (reactiveMap.has(target)) {
        return reactiveMap.get(target)
    }
    
    const proxy = createReactiveObject(target, handler)
    reactiveMap.set(target, proxy)
    return proxy
}

要点:

  1. 响应式原理:
  • Vue 3 使用 Proxy 实现数据劫持

  • 通过 track 收集依赖

  • 通过 trigger 触发更新

  • 支持深层响应式

  1. 依赖收集:
  • 使用 WeakMap + Map + Set 的数据结构

  • 在 getter 中收集依赖

  • 建立数据和副作用的对应关系

  1. 更新机制:
  • 异步批量更新

  • 使用微任务队列

  • 避免重复更新

  1. 性能优化:
  • 缓存代理对象

  • 避免不必要的依赖收集

  • 使用 WeakMap 防止内存泄漏

  1. 实际应用:
  • v-model 的实现

  • computed 的实现

  • watch 的实现