> **本文回答**：`mousemove / move / render` 等高频事件的节流策略 —— throttle / debounce / RAF 合并。

## 一、哪些事件是高频

| 事件 | 典型 fire 频率 |
|---|---|
| `map.on('mousemove', ...)` | 每秒 60+ |
| `map.on('move', ...)` | 拖动中每帧 |
| `map.on('zoom', ...)` | 缩放中每帧 |
| `map.on('render', ...)` | 仅地图重绘的帧（动画 / 交互中每帧，静止时不触发） |
| `eventBus.on('entity:mouseenter', ...)` | 每帧 hover 的实体 |
| `fx.on('render', ...)` | 每一帧 |

**不**高频：`click / moveend / zoomend / command:ended / styledata / load`。

## 二、throttle（固定时间间隔）

```js
function throttle(fn, ms) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last > ms) { last = now; fn(...args); }
  };
}

map.on('mousemove', throttle(e => {
  // 贵的查询：queryRenderedFeatures、HTTP 请求
}, 100));
```

**适合：** 节奏稳定的 UI 更新（状态栏、tooltip）。

## 三、debounce（静止后触发）

```js
function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

map.on('moveend', debounce(() => {
  fetchDataInBounds(map.getBounds());
}, 300));
```

**适合：** 用户操作停止后的一次性响应（搜索请求、持久化）。

## 四、requestAnimationFrame 合并

```js
let pending = false;
map.on('mousemove', () => {
  if (pending) return;
  pending = true;
  requestAnimationFrame(() => {
    // 最多每帧执行一次
    update();
    pending = false;
  });
});
```

**适合：** UI 渲染（DOM 更新、Canvas 绘制）。优势：**对齐浏览器渲染时序**，不卡动画。

## 五、三种方案对比

| 方案 | 频率 | 适合 |
|---|---|---|
| throttle | 固定（如 100ms） | 查询、面板更新 |
| debounce | 静止后 1 次 | 网络请求、保存 |
| RAF | 每帧最多 1 次 | DOM / Canvas 渲染 |

## 六、实际场景

### 鼠标坐标显示

```js
const displayCoord = throttle(e => {
  coordHUD.textContent = `${e.lngLat.lng.toFixed(6)}, ${e.lngLat.lat.toFixed(6)}`;
}, 16);           // ~60fps，肉眼流畅就够

map.on('mousemove', displayCoord);
```

### 视口内数据查询

```js
const fetchInView = debounce(async () => {
  const b = map.getBounds();
  const data = await api.get(`/features?bbox=${b.toArray()}`);
  source.setData(data);
}, 500);

map.on('moveend', fetchInView);
```

### 跨组件状态同步

```js
let pending = false;
map.on('move', () => {
  if (pending) return;
  pending = true;
  requestAnimationFrame(() => {
    syncMiniMap(map.getCenter(), map.getZoom(), map.getBearing());
    pending = false;
  });
});
```

## 七、eventBus 高频 handler

```js
const heavy = throttle(({ entity }) => {
  updatePropertyPanel(entity);
}, 100);

mapcadLayer.eventBus.on(VjmapextEvents.EntityMouseEnter, heavy);
```

enter/leave 也可能每帧 fire（hover 在相邻实体间跳转）。

## 八、组合：throttle + 跳过重复

```js
let lastId = null;
map.on('mousemove', throttle(e => {
  const feats = map.queryRenderedFeatures(e.point, { layers: ['pois'] });
  if (feats.length === 0) return;
  const id = feats[0].id;
  if (id === lastId) return;     // 没变，跳过
  lastId = id;
  tooltip.show(feats[0]);
}, 50));
```

## 九、性能采样里 FPS 统计

```js
let frameCount = 0;
let lastTs = performance.now();
map.on('render', () => {
  frameCount++;
  const now = performance.now();
  if (now - lastTs >= 1000) {
    console.log(`FPS: ${frameCount}`);
    frameCount = 0;
    lastTs = now;
  }
});
```

⚠️ 引擎按需渲染，`render` 只在地图重绘时 fire，静止时不触发——这里统计的是**地图重绘帧率**（静止时读数趋近 0 并不代表卡），页面 FPS 用独立的 `requestAnimationFrame` 计数或 DevTools FPS meter。handler 里只 `++` 无 IO → 便宜。**不要**在 handler 里做 DOM 操作。

## 十、常见坑

1. **throttle 内闭包共享太重**：每次 handler new 大对象 → GC 压力；提前声明。
2. **debounce 延迟后组件卸载**：组件已销毁但定时器还没跑；卸载时 `clearTimeout`。
3. **RAF 嵌 RAF**：每一层延迟一帧；避免。
4. **mousemove 里 queryRenderedFeatures 没节流**：每秒 60 次查询，明显卡；必 throttle。
5. **`eventBus.off` 时 handler 是 throttle 包装后的函数**：对，要 off 这个包装的引用，不是原函数。

## 相关

- 交互事件 → [event/map-interaction](../event/map-interaction.md)
- 视图事件 → [event/map-view](../event/map-view.md)
- 清理 → [event/cleanup](../event/cleanup.md)
