闭包可实现分层渲染控制,关键在于每层独立闭包仅存必要上下文与状态、draw()无副作用且可独立调用、资源生命周期由闭包管理、调度器显式编排绘制顺序。

直接用闭包捕获 CanvasContext 就能实现分层渲染控制,关键不是“能不能封装”,而是怎么封装才不破坏图层职责、不干扰绘制时序。
闭包里只存必要上下文和状态
每个图层对应一个独立闭包,里面只保留该层专用的 context、尺寸信息和少量运行时状态(比如当前帧号、是否启用、脏区域标记),避免混入跨层逻辑或 DOM 引用。例如:
- 背景层闭包:只存 ctx、image、isLoaded;不关心角色坐标
- 角色层闭包:存 ctx、playerX/playerY、lastRect(用于脏矩形清除);不读取 UI 按钮状态
- UI 层闭包:存 ctx、scale、fontCache;不参与 requestAnimationFrame 节奏同步
绘制函数必须可独立调用且无副作用
每个闭包暴露的 draw() 方法应满足:输入确定、输出只作用于自身 canvas、不修改外部变量、不依赖其他层执行顺序。例如:
- 角色层 draw() 内部用 clearRect 清局部区域,再画新位置,不调用 background.draw()
- 所有 draw() 都接受 timestamp 参数,便于未来接入不同更新频率(如 UI 层每秒重绘 10 次,动画层 60fps)
- 禁止在 draw() 中触发图片加载、字体测量等异步操作;这些应在初始化闭包时完成
用闭包隔离资源生命周期
图层资源(如离屏 canvas、图像对象、路径缓存)随闭包创建而分配,随闭包释放而清理。例如:
- 构造角色层闭包时 new OffscreenCanvas(width, height),并缓存其 2D 上下文
- 提供 destroy() 方法,在其中将 offscreenCanvas.width = offscreenCanvas.height = 0,主动释放内存
- UI 层闭包中预加载 icon 字体,但只在 draw() 中调用 ctx.fillText,不重复 loadFont
组合调用需显式编排,不隐式耦合
主渲染循环不直接调用各层 draw(),而是通过一个调度器统一管理。这个调度器本身也是闭包,持有对各层绘制函数的引用,并控制执行条件:
- 根据帧时间差决定是否跳过某层(如 UI 层 lastDrawTime + 100ms < now 才重绘)
- 按 Z 序依次调用 draw(),但每一层 draw() 不知道它前面或后面是谁
- 支持临时禁用某层:layer.enabled = false,调度器自动跳过,无需修改闭包内部逻辑


















