响应式原理

响应式系统是现代前端框架的核心,它实现了数据变化自动驱动视图更新的机制。本章将深入探讨响应式系统的实现原理。

响应式概念

什么是响应式

const reactivityConcept = {
    definition: '响应式是指数据变化时自动触发相关依赖更新的机制',
    
    coreElements: {
        data: '响应式数据源',
        dependency: '依赖数据的计算或副作用',
        trigger: '数据变化时触发依赖更新'
    },
    
    workflow: [
        '1. 数据劫持 - 拦截数据读写操作',
        '2. 依赖收集 - 记录数据的使用者',
        '3. 触发更新 - 数据变化时通知依赖'
    ]
};

数据劫持

Object.defineProperty

function defineReactive(obj, key, val) {
    const dep = new Dep();
    
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        
        get() {
            if (Dep.target) {
                dep.addSub(Dep.target);
            }
            return val;
        },
        
        set(newVal) {
            if (newVal === val) return;
            val = newVal;
            dep.notify();
        }
    });
}

function observe(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return;
    }
    
    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key]);
    });
}

class Dep {
    constructor() {
        this.subs = [];
    }
    
    addSub(sub) {
        this.subs.push(sub);
    }
    
    notify() {
        this.subs.forEach(sub => sub.update());
    }
}

Dep.target = null;

Proxy实现

function reactive(target) {
    return new Proxy(target, {
        get(obj, key, receiver) {
            track(obj, key);
            const result = Reflect.get(obj, key, receiver);
            
            if (typeof result === 'object' && result !== null) {
                return reactive(result);
            }
            
            return result;
        },
        
        set(obj, key, value, receiver) {
            const oldValue = obj[key];
            const result = Reflect.set(obj, key, value, receiver);
            
            if (oldValue !== value) {
                trigger(obj, key);
            }
            
            return result;
        },
        
        deleteProperty(obj, key) {
            const result = Reflect.deleteProperty(obj, key);
            trigger(obj, key);
            return result;
        }
    });
}

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

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

function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    
    const dep = depsMap.get(key);
    if (dep) {
        dep.forEach(effect => effect());
    }
}

副作用系统

effect函数

function effect(fn, options = {}) {
    const effectFn = () => {
        cleanup(effectFn);
        activeEffect = effectFn;
        const result = fn();
        activeEffect = null;
        return result;
    };
    
    effectFn.deps = [];
    effectFn.options = options;
    
    if (!options.lazy) {
        effectFn();
    }
    
    return effectFn;
}

function cleanup(effectFn) {
    effectFn.deps.forEach(dep => {
        dep.delete(effectFn);
    });
    effectFn.deps = [];
}

调度执行

const jobQueue = new Set();
let isFlushing = false;

function queueJob(job) {
    jobQueue.add(job);
    
    if (!isFlushing) {
        isFlushing = true;
        Promise.resolve().then(() => {
            jobQueue.forEach(job => job());
            jobQueue.clear();
            isFlushing = false;
        });
    }
}

function effect(fn, options = {}) {
    const effectFn = () => {
        cleanup(effectFn);
        activeEffect = effectFn;
        const result = fn();
        activeEffect = null;
        return result;
    };
    
    effectFn.deps = [];
    effectFn.scheduler = options.scheduler;
    
    if (!options.lazy) {
        effectFn();
    }
    
    return effectFn;
}

function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    
    const dep = depsMap.get(key);
    if (dep) {
        const effects = [...dep];
        effects.forEach(effectFn => {
            if (effectFn.scheduler) {
                effectFn.scheduler(effectFn);
            } else {
                effectFn();
            }
        });
    }
}

计算属性

computed实现

function computed(getter) {
    let cachedValue;
    let dirty = true;
    
    const effectFn = effect(getter, {
        lazy: true,
        scheduler() {
            dirty = true;
            trigger(obj, 'value');
        }
    });
    
    const obj = {
        get value() {
            track(obj, 'value');
            if (dirty) {
                cachedValue = effectFn();
                dirty = false;
            }
            return cachedValue;
        }
    };
    
    return obj;
}

const state = reactive({ firstName: '张', lastName: '三' });

const fullName = computed(() => {
    console.log('计算');
    return state.firstName + state.lastName;
});

console.log(fullName.value);
console.log(fullName.value);

watch实现

function watch(source, callback, options = {}) {
    let getter;
    
    if (typeof source === 'function') {
        getter = source;
    } else {
        getter = () => traverse(source);
    }
    
    let oldValue, newValue;
    
    const job = () => {
        newValue = effectFn();
        callback(newValue, oldValue);
        oldValue = newValue;
    };
    
    const effectFn = effect(getter, {
        lazy: true,
        scheduler: () => {
            if (options.flush === 'post') {
                Promise.resolve().then(job);
            } else {
                job();
            }
        }
    });
    
    if (options.immediate) {
        job();
    } else {
        oldValue = effectFn();
    }
    
    return () => {
        cleanup(effectFn);
    };
}

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

const state = reactive({ count: 0 });

watch(
    () => state.count,
    (newVal, oldVal) => {
        console.log(`count从${oldVal}变为${newVal}`);
    }
);

ref实现

function ref(value) {
    const wrapper = {
        _value: value,
        get value() {
            track(wrapper, 'value');
            return this._value;
        },
        set value(newVal) {
            if (newVal !== this._value) {
                this._value = newVal;
                trigger(wrapper, 'value');
            }
        }
    };
    
    Object.defineProperty(wrapper, '__v_isRef', { value: true });
    
    return wrapper;
}

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

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

function toRef(obj, key) {
    const wrapper = {
        get value() {
            return obj[key];
        },
        set value(newVal) {
            obj[key] = newVal;
        }
    };
    
    Object.defineProperty(wrapper, '__v_isRef', { value: true });
    
    return wrapper;
}

function toRefs(obj) {
    const result = {};
    
    for (const key in obj) {
        result[key] = toRef(obj, key);
    }
    
    return result;
}

完整响应式系统

class ReactiveSystem {
    constructor() {
        this.targetMap = new WeakMap();
        this.activeEffect = null;
        this.effectStack = [];
    }
    
    reactive(target) {
        return new Proxy(target, {
            get: (obj, key, receiver) => {
                this.track(obj, key);
                const result = Reflect.get(obj, key, receiver);
                
                if (typeof result === 'object' && result !== null) {
                    return this.reactive(result);
                }
                
                return result;
            },
            
            set: (obj, key, value, receiver) => {
                const oldValue = obj[key];
                const result = Reflect.set(obj, key, value, receiver);
                
                if (oldValue !== value) {
                    this.trigger(obj, key);
                }
                
                return result;
            }
        });
    }
    
    track(target, key) {
        if (!this.activeEffect) return;
        
        let depsMap = this.targetMap.get(target);
        if (!depsMap) {
            this.targetMap.set(target, (depsMap = new Map()));
        }
        
        let dep = depsMap.get(key);
        if (!dep) {
            depsMap.set(key, (dep = new Set()));
        }
        
        dep.add(this.activeEffect);
        this.activeEffect.deps.push(dep);
    }
    
    trigger(target, key) {
        const depsMap = this.targetMap.get(target);
        if (!depsMap) return;
        
        const dep = depsMap.get(key);
        if (!dep) return;
        
        const effects = new Set(dep);
        
        effects.forEach(effect => {
            if (effect !== this.activeEffect) {
                if (effect.scheduler) {
                    effect.scheduler();
                } else {
                    effect();
                }
            }
        });
    }
    
    effect(fn, options = {}) {
        const effectFn = () => {
            this.cleanup(effectFn);
            
            this.effectStack.push(effectFn);
            this.activeEffect = effectFn;
            
            const result = fn();
            
            this.effectStack.pop();
            this.activeEffect = this.effectStack[this.effectStack.length - 1];
            
            return result;
        };
        
        effectFn.deps = [];
        effectFn.scheduler = options.scheduler;
        
        if (!options.lazy) {
            effectFn();
        }
        
        return effectFn;
    }
    
    cleanup(effectFn) {
        effectFn.deps.forEach(dep => {
            dep.delete(effectFn);
        });
        effectFn.deps = [];
    }
    
    computed(getter) {
        let cachedValue;
        let dirty = true;
        
        const effectFn = this.effect(getter, {
            lazy: true,
            scheduler: () => {
                dirty = true;
                this.trigger(computedRef, 'value');
            }
        });
        
        const computedRef = {
            get value() {
                this.track(computedRef, 'value');
                if (dirty) {
                    cachedValue = effectFn();
                    dirty = false;
                }
                return cachedValue;
            }
        };
        
        return computedRef;
    }
    
    watch(source, callback, options = {}) {
        let getter = typeof source === 'function' 
            ? source 
            : () => this.traverse(source);
        
        let oldValue;
        
        const job = () => {
            const newValue = effectFn();
            callback(newValue, oldValue);
            oldValue = newValue;
        };
        
        const effectFn = this.effect(getter, {
            lazy: true,
            scheduler: job
        });
        
        if (options.immediate) {
            job();
        } else {
            oldValue = effectFn();
        }
    }
    
    traverse(value, seen = new Set()) {
        if (typeof value !== 'object' || value === null || seen.has(value)) {
            return value;
        }
        seen.add(value);
        for (const key in value) {
            this.traverse(value[key], seen);
        }
        return value;
    }
}

const system = new ReactiveSystem();
const state = system.reactive({ count: 0 });

system.effect(() => {
    console.log('count:', state.count);
});

system.watch(() => state.count, (newVal, oldVal) => {
    console.log('watch:', oldVal, '->', newVal);
});

state.count++;

东巴文小贴士

🔄 响应式系统要点

  1. 数据劫持:Proxy比defineProperty更强大
  2. 依赖收集:准确追踪数据使用
  3. 触发更新:高效通知相关依赖
  4. 调度执行:控制更新时机

⚠️ 注意事项

  • 避免在响应式数据中存储不可代理的对象
  • 注意循环依赖问题
  • 合理使用computed缓存

下一步

下一章将探讨 Node.js入门,学习服务端JavaScript开发。