响应式系统是现代前端框架的核心,它实现了数据变化自动驱动视图更新的机制。本章将深入探讨响应式系统的实现原理。
const reactivityConcept = {
definition: '响应式是指数据变化时自动触发相关依赖更新的机制',
coreElements: {
data: '响应式数据源',
dependency: '依赖数据的计算或副作用',
trigger: '数据变化时触发依赖更新'
},
workflow: [
'1. 数据劫持 - 拦截数据读写操作',
'2. 依赖收集 - 记录数据的使用者',
'3. 触发更新 - 数据变化时通知依赖'
]
};
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;
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());
}
}
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();
}
});
}
}
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);
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}`);
}
);
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++;
🔄 响应式系统要点
- 数据劫持:Proxy比defineProperty更强大
- 依赖收集:准确追踪数据使用
- 触发更新:高效通知相关依赖
- 调度执行:控制更新时机
⚠️ 注意事项
- 避免在响应式数据中存储不可代理的对象
- 注意循环依赖问题
- 合理使用computed缓存
下一章将探讨 Node.js入门,学习服务端JavaScript开发。