> **本文回答**：`entity.styleChanged()` —— 只改 `color` / `lineWidth` / `effect_*` 时的快路径，比 `changed()` 快 5~20 倍（`visible`/fill/lineType 不在快路径）。

## 一、两种 API 对比

| 方法 | 内部路径 | 代价 |
|---|---|---|
| `entity.changed()` | 进 `_changedIds` → 从桶摘除 → 重跑 `toGeoJSON()` + CAD→经纬度变换 → 重插桶 | 重编码 + 所在桶 `setData` 整体重传 |
| `entity.styleChanged()` | 进 `_styleOnlyIds` → `renderCache.updateStyleProps(e)` → 对缓存的每个 feature 调 `e.updateFeatureStyleProps(feature)` **原地 patch 属性** | 不重编码；所在桶照样 `setData` 重传 |

省的是**重编码 + 坐标变换**，不是 GPU 上传——桶的 GeoJSON 两种路径都要重传。实体越少动，差距越大。

## 二、何时必须 `changed()`

| 改了什么 | 必须用 `changed()` |
|---|---|
| 起点 / 终点 / center / radius / points | ✅ |
| rotation / transform | ✅ |
| insertionPoint / scale | ✅ |
| text 内容（文字尺寸不变情况可能 style 够用，但保守用 changed） | ✅ |

## 三、何时可以 `styleChanged()`

**覆盖范围只有 3 组属性**（`EntityBase.updateFeatureStyleProps` 唯一定义处，且无子类重写）：

| 改了什么 | `styleChanged()` 生效？ | 原因 |
|---|---|---|
| `color` | ✅ | feature `properties.color` 被原地 patch |
| `lineWidth` | ✅（文本实体除外；改回 0 除外） | patch `line_width`；`_role` 为 `text_fill`/`text_stroke` 的 feature 被跳过。只在 `lineWidth > 0` 时写、从不删——改回 `0` 后旧值残留，须 `changed()` |
| `effectColor` / `effectWidth` / `effectOpacity` / `effectBlur` | ✅ | patch `effect_*` 四个属性（`effectColor=-1` 时改为删除） |
| `fillColor` / `fillOpacity` / `borderOpacity` | ❌ 用 `changed()` | 不在 patch 清单内，缓存 feature 保留旧值 |
| `lineType` / `ltScale` | ❌ 用 `changed()` | 虚线是** tessellate 进几何坐标**的（`tessellateWithLinetype`），不是样式属性 |
| `visible` | ❌ 用 `changed()` | 可见性在桶重建时过滤，styleOnly 路径不重查 |
| `layerId` | ❌ 用 `changed()` | 图层归属不在 patch 清单 |
| `minDisplayLevel` / `maxDisplayLevel` | ❌ 用 `changed()` | display-level 过滤在重建时做 |

> `opacity` 字段**不存在**于 `EntityBase`（有的是 `fillOpacity`/`borderOpacity`/`effectOpacity`）。

## 四、对比示例

```js
// 慢：1 万实体每条 changed()
const t0 = performance.now();
for (const e of mapcadLayer.getEntities()) {
  e.color = 0xff0000;
  e.changed();                // ← 全量
}
console.log(performance.now() - t0);   // 比如 2500ms

// 快：1 万实体每条 styleChanged()
const t1 = performance.now();
for (const e of mapcadLayer.getEntities()) {
  e.color = 0xff0000;
  e.styleChanged();           // ← 快路径
}
console.log(performance.now() - t1);   // 比如 180ms
```

## 五、批量修改

```js
for (const e of entities) {
  e.color = 0xff0000;
  e.styleChanged();
}
// 标脏后所在桶在下一帧合并 setData 一次（RAF 调度，无需手动 bulk）
```

## 六、bus 事件差异

```js
bus.on(VjmapextEvents.EntityModified, ({ entityId, styleOnly }) => {
  // payload 是 { entityId, styleOnly } —— 是 id 不是实体对象
  if (styleOnly) {
    // 来自 styleChanged()
  } else {
    // 来自 changed()
  }
});
```

下游可根据 styleOnly 决定是否重建自己的索引。

## 七、feature-state（vjmap 底座版）

对 GeoJSON 图层，底座有类似的快路径 —— **feature-state**：

```js
map.setFeatureState({ source: 'src', id: 123 }, { hover: true });

map.addLineLayer('lyr', 'src', {
  lineColor: ['case',
    ['boolean', ['feature-state', 'hover'], false], '#f00', '#888'
  ]
});
```

不需要 setData，只改 state → 自动重绘（毫秒级）。配合表达式覆盖大范围 hover。

## 八、自定义实体支持快路径

没有 `encode`/`encodeStyle`/`encodeGeometry` 这套 API——快路径的真实挂钩是 `updateFeatureStyleProps(feature)`：基类实现只 patch `color` / `line_width` / `effect_*`。自定义实体若在 `toGeoJSON()` 里往 `properties` 塞了额外的样式字段（如 `fill_color`、自定义角色 `_role`），想享受快路径就重写它：

```js
class StarEnt extends vjmapext.EntityBase {
  updateFeatureStyleProps(feature) {
    super.updateFeatureStyleProps(feature);      // color / line_width / effect_*
    const p = feature.properties;
    if (p) p.fill_color = '#' + this.fillColor.toString(16).padStart(6, '0');
  }
}
```

不重写也能用——只是 `fillColor` 等自定义样式字段必须走 `changed()` 全量。

## 九、批量换色最佳实践

```js
function setBatchColor(ents, color) {
  for (const e of ents) {
    if (e.color === color) continue;        // 跳过无变化
    e.color = color;
    e.styleChanged();
  }
}
```

- 同一帧内多次 `styleChanged()` 合并成一次桶 `setData`（rAF 调度）
- 跳过无变化避免无谓标脏与上传

## 十、常见坑

1. **改了几何还调 styleChanged**：画面没变；用 `changed()`。
2. **改了 fillColor / lineType / visible / layerId 用 styleChanged**：同样不生效——这些不在 `updateFeatureStyleProps` 的 patch 清单里，必须 `changed()`。
3. **自定义实体 styleChanged 不生效**：自有样式字段需重写 `updateFeatureStyleProps`（见 §八）。
4. **循环里每条 changed + styleChanged**：changed 已包含 style，别再 styleChanged——而且**先 `changed()` 后 `styleChanged()` 会吞掉几何更新**：`styleChanged` 把 id 重新放回 styleOnlyIds，本帧只原地 patch 旧缓存的样式并清掉 `isDirty`，新几何不会重算。只有反过来（先 styleChanged 后 changed，changed 会把 id 从 styleOnlyIds 摘除）才走全量。
5. **map 的 feature-state 不生效**：source 没设 `generateId: true` 或 `promoteId`，feature 没 id。
6. **styleChanged 后事件里又 changed**：变成全量；监听 styleOnly 再决定。

## 相关

- 渲染桶 → [render-buckets](render-buckets.md)
- 批量样式 → [batch/bulk-style-update](../batch/bulk-style-update.md)
- feature-state → [batch/geojson-bulk](../../../vjmap/references/batch/geojson-bulk.md)
