Tabula相册整理工具2026
27.5MB · 2026-04-07
声明式框架
采用虚拟 DOM
编译时和运行时
编译时: 工程化中使用 @vue/compiler-sfc 调用 @vue/compiler-dom 模块,将 SFC 中的模板编译为渲染函数。
运行时:(@vue/runtime-core)负责创建组件实例、执行渲染函数、生成虚拟 DOM、对比并更新真实 DOM。
数组的变异方法(push, pop, shift, unshift 等)
修改 length 属性 数组的 length 属性默认是 不可配置(configurable: false) 且 不可枚举,因此无法通过 Object.defineProperty 重新定义它的 getter/setter
动态新增的索引
删除属性(delete arr[0])
target[key] 和 receiver[key] 要用 Reflectconst obj = {
a: 1,
get b() {
return this.a;
},
};
target[key] 取 b,this 指向原对象 obj,内部访问 this.a 会绕过代理,可能导致依赖收集不完整。receiver[key] 时,会再次触发当前 Proxy 的 get 陷阱,导致无限递归,最终栈溢出。Reflect.get(..., receiver),内部实现区分了“读取属性”和“调用 getter”这两个步骤,this 绑定到代理对象 receiver,this.a 会再次走代理 get,依赖才能正确追踪。export function reactive(target) {
return createReactiveObject(target);
}
function createReactiveObject(target) {
// 检测target是否为对象
if (!isObject(target)) {
return target;
}
// 放置代理过的对象重复代理
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// 优化:同一个对象只能代理一次
const existProxy = reactiveMap.get(target);
if (existProxy) {
return existProxy;
}
let proxy = new Proxy(target, mutableHandlers);
reactiveMap.set(target, proxy);
return proxy;
}
// in mutableHandlers
export const mutableHandlers: ProxyHandler<any> = {
/**
*
* @param target 代理目标对象
* @param key 获取的哪个属性
* @param recevier 返回的代理对象
* @returns
*/
get(target, key, recevier) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true; // 响应式 get 的结果
}
// Reflect 让this指向Proxy对象(recevier),避免重复触发get,导致死循环。
let res = Reflect.get(target, key, recevier);
// *当取得的值也是对象的时候,对这个对象进行递归代理
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, recevier) {
let result = Reflect.set(target, key, value, recevier);
return result;
}
}
数据变化后 可以让 effect 重新执行,组件,watch、computed、都是基于 effect 来实现的// state 为响应式数据
// effect1
effect(() => {
app.innerHTML = `姓名${state.name} 年龄${state.age}`;
});
// effect2
effect(() => {
main.innerHTML = `姓名${state.age}`;
});
state.age++;
state.name 触发 name 的 get。完成依赖收集器 dep(name) 对 依赖(effect1) 的收集。// 依赖收集的数据结构(三 Map 结构)
targetMap (WeakMap) : {
// 原始对象
'{name: '', age: ''}' : {
// 依赖收集器 dep(name)
'name':{
effect1: effect1._trackId
},
// 依赖收集器 dep(age)
'age': {
effect1: effect1._trackId,
effect2: effect2._trackId
}
}
}
effect.deps[effect._depsLength++] = dep;
state.age++ 后,触发代理对象 age 的 set,并执行 trigger,将 age 的依赖(effect1、effect2)取出依次执行。// 触发更新
export function triggerEffects(dep) {
// 将映射表中的effect拿出来依次执行
for (const effect of dep.keys()) {
if (effect.scheduler && effect._runner === 0) {
effect.scheduler(); // -> _effect.run() -> 重新执行 fn
}
}
}
// state 为响应式数据 flag = true
effect(() => {
app.innerHTML = state.flag ? state.name : state.age;
});
state.flag = false;
function preCleanEffect(effect) {
effect._depsLength = 0; // 身上的依赖收集器数组的长度置空
effect._trackId++; // 每次执行前 trackId 都加1,如果同一个 effect 执行,trackId 就是相同的
}
export function trackEffect(effect, dep) {
// 相同 trackId 则跳过收集
if (dep.get(effect) !== effect._trackId) {
// 收集到相同的依赖,只更新 trackId 的次数
dep.set(effect, effect._trackId);
let oldDep = effect.deps[effect._depsLength]; // 获取上次的旧 dep
if (oldDep !== dep) {
if (oldDep) {
// 删除老的
cleanDepEffect(oldDep, effect);
}
effect.deps[effect._depsLength++] = dep; // 永远按照本次**最新**的来存
} else {
effect._depsLength++;
}
}
}
function cleanDepEffect(dep, effect) {
dep.delete(effect);
if (dep.size === 0) {
dep.cleanup(); // 如果map为空,则删除这个属性
}
}
_depsLength 为准,清理掉多余的 dep。function postCleanEffect(effect) {
if (effect.deps.length > effect._depsLength) {
for (let i = effect._depsLength; i < effect.deps.length; i++) {
cleanDepEffect(effect.deps[i], effect); // 删除映射表中对应的effect
}
effect.deps.length = effect._depsLength; // 更新依赖列表的长度
}
}
// 实例:effect1
effect(() => {
effect(() => {}); // effect2
});
// -------------------
// 全局上保存当前执行的 effect
let activeEffect;
// run方法
run() {
let lastEffect = activeEffect; // *
try {
this._runner ++;
activeEffect = this;
preCleanEffect(this);
return this.fn();
} finally {
postCleanEffect(this);
activeEffect = lastEffect;
this._runner --;
}
}
栈来实现,执行 effect1 进栈,执行 effect2 进栈,收集完毕挨个出栈,栈顶则是当前的 activeEffect。立即执行 fn 还是走 自定义调度(如 watch 的 flush)// 做法
const runner = effect(
() => {
app.innerHTML = `姓名${state.name} 年龄${state.age}`;
},
{
scheduler: () => {
console.log("触发了更新,暂时不做处理"); // 切片编程思想,首先覆盖掉默认的 scheduler 执行,加上自己逻辑
runner(); // 拿到暴露出来的runner后,某个时刻触发更新
},
},
);
// in effect
export function effect (fn, options?) {
// 创建一个effect 实例,只要依赖的属性发生变化就要执行回调scheduler,就是 run() 方法
const _effect = new ReactiveEffect(fn, () => {
// 默认 scheduler 调度器,run 方法中执行 fn()
_effect.run();
});
_effect.run();
if (options) {
Object.assign(_effect, options); // 将用户定义的scheduler覆盖掉内置的
}
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner; // 外面可以拿到调度执行 effect 的方法。
}