最近在使用antv/G6画一些拓扑图,用到了一些自定义元素扩展功能,也参考了一些源代码的实现逻辑,看到这样一段代码让我眼前一亮:
typescript
// 源代码地址:https://github.com/antvis/G6/blob/v5/packages/g6/src/elements/combos/base-combo.ts
public animate(keyframes: Keyframe[], options?: number | KeyframeAnimationOptions) {
const animation = super.animate(
this.attributes.collapsed
? keyframes
: keyframes.map(({ x, y, z, transform, ...keyframe }: any) => keyframe),
options,
);
if (!animation) return animation;
return new Proxy(animation, {
set: (target, propKey, value) => {
if (propKey === 'currentTime') Promise.resolve().then(() => this.onframe());
return Reflect.set(target, propKey, value);
},
});
}
它不仅仅是语法简洁,更巧妙地融合了:
- ✅
Proxy
的拦截机制 - ✅
Promise
微任务的时序特性
本文将从这段代码出发,展开关于 Proxy
与微任务控制的深入分析和工程思考。
🧩 一、Proxy 的设计巧思:拦截赋值 & 保持透明
🧐 为什么 G6 要拦截 currentTime
?
在 G6 的 animate
方法中,动画对象的 currentTime
被赋值时,通过 Proxy
进行拦截,一旦赋值,立即安排一次 this.onframe()
的异步调用,用于刷新或更新帧逻辑。
✅ 作用分析:
currentTime
是动画播放进度的核心属性,手动设置它会跳帧。- 通过监听这个属性变化,可以捕捉用户或系统对动画时间的直接干预。
- 而不是立即执行帧更新,而是通过
Promise.resolve().then(...)
推入微任务队列,确保它在当前事件循环结束后、下一帧绘制前准确触发。
⚙️ 二、Proxy 核心机制简析
javascript
const obj = new Proxy(target, {
set(target, prop, value) {
console.log(`Set ${prop} = ${value}`);
return Reflect.set(target, prop, value);
}
});
Proxy 应用优势:
- 解耦封装逻辑:无需侵入原对象定义即可扩展行为。
- 拦截能力强:支持 get、set、has、delete 等多种操作。
- 透明行为保留 :通过
Reflect.*
方法保持行为一致性。
G6 的代码中正是通过 Proxy 把外部赋值行为"包了一层钩子",实现逻辑注入而不破坏原有动画对象。
⏱ 三、微任务的引入与意义
javascript
Promise.resolve().then(() => this.onframe());
为什么这里使用 Promise.then
而不是直接调用 this.onframe()
或者 setTimeout(..., 0)
?
🎯 原因是:微任务的时序控制更精确。
方法 | 时序阶段 | 精度 |
---|---|---|
直接调用 | 同步执行 | 立即执行 |
setTimeout |
宏任务,下一轮事件循环 | 有最小延迟 |
Promise.then |
微任务,本轮尾部 | 几乎无延迟 |
微任务能保证:当前 JS 事件执行完后立即调度,不等待下一轮 Event Loop,这对于保证动画帧精度至关重要。
💡 延展思考:为什么不用 setTimeout?
setTimeout(fn, 0)
虽然也能异步执行,但它归属于宏任务,会被插入到下一轮事件循环之后,而不是本轮最后。
这意味着如果当前帧中有多个相关逻辑要串联,使用 Promise.then
更能保证顺序性和时间一致性。
🧠 总结:从 G6 的这段代码中,我们学到了什么?
技术点 | 应用价值 |
---|---|
Proxy | 拦截动画属性修改,实现"透明逻辑注入" |
Reflect | 保证代理行为一致,避免副作用 |
Promise 微任务 | 更精准的帧控制、更合理的事件时序调度 |
AntV/G6 的这段设计背后,展示了优秀框架对异步时序的精妙把控能力,也提醒我们写动画/状态驱动代码时:"赋值操作" 也可以成为强大的控制入口。