@mison/ag-kit-cn 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  28. package/.agent/ARCHITECTURE.md +285 -0
  29. package/.agent/agents/backend-specialist.md +268 -0
  30. package/.agent/agents/code-archaeologist.md +106 -0
  31. package/.agent/agents/database-architect.md +225 -0
  32. package/.agent/agents/debugger.md +225 -0
  33. package/.agent/agents/devops-engineer.md +242 -0
  34. package/.agent/agents/documentation-writer.md +104 -0
  35. package/.agent/agents/explorer-agent.md +73 -0
  36. package/.agent/agents/frontend-specialist.md +618 -0
  37. package/.agent/agents/game-developer.md +162 -0
  38. package/.agent/agents/mobile-developer.md +382 -0
  39. package/.agent/agents/orchestrator.md +438 -0
  40. package/.agent/agents/penetration-tester.md +188 -0
  41. package/.agent/agents/performance-optimizer.md +187 -0
  42. package/.agent/agents/product-manager.md +112 -0
  43. package/.agent/agents/product-owner.md +95 -0
  44. package/.agent/agents/project-planner.md +405 -0
  45. package/.agent/agents/qa-automation-engineer.md +103 -0
  46. package/.agent/agents/security-auditor.md +170 -0
  47. package/.agent/agents/seo-specialist.md +111 -0
  48. package/.agent/agents/test-engineer.md +158 -0
  49. package/.agent/mcp_config.json +12 -0
  50. package/.agent/rules/GEMINI.md +273 -0
  51. package/.agent/scripts/auto_preview.py +148 -0
  52. package/.agent/scripts/checklist.py +217 -0
  53. package/.agent/scripts/session_manager.py +120 -0
  54. package/.agent/scripts/verify_all.py +327 -0
  55. package/.agent/skills/api-patterns/SKILL.md +84 -0
  56. package/.agent/skills/api-patterns/api-style.md +42 -0
  57. package/.agent/skills/api-patterns/auth.md +24 -0
  58. package/.agent/skills/api-patterns/documentation.md +26 -0
  59. package/.agent/skills/api-patterns/graphql.md +41 -0
  60. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  61. package/.agent/skills/api-patterns/response.md +37 -0
  62. package/.agent/skills/api-patterns/rest.md +40 -0
  63. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  64. package/.agent/skills/api-patterns/security-testing.md +122 -0
  65. package/.agent/skills/api-patterns/trpc.md +41 -0
  66. package/.agent/skills/api-patterns/versioning.md +22 -0
  67. package/.agent/skills/app-builder/SKILL.md +75 -0
  68. package/.agent/skills/app-builder/agent-coordination.md +74 -0
  69. package/.agent/skills/app-builder/feature-building.md +53 -0
  70. package/.agent/skills/app-builder/project-detection.md +34 -0
  71. package/.agent/skills/app-builder/scaffolding.md +118 -0
  72. package/.agent/skills/app-builder/tech-stack.md +40 -0
  73. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  74. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  75. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  76. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  77. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  78. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  79. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  80. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  81. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  82. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  83. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  84. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  85. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  86. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  87. package/.agent/skills/architecture/SKILL.md +57 -0
  88. package/.agent/skills/architecture/context-discovery.md +43 -0
  89. package/.agent/skills/architecture/examples.md +94 -0
  90. package/.agent/skills/architecture/pattern-selection.md +68 -0
  91. package/.agent/skills/architecture/patterns-reference.md +50 -0
  92. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  93. package/.agent/skills/bash-linux/SKILL.md +201 -0
  94. package/.agent/skills/behavioral-modes/SKILL.md +264 -0
  95. package/.agent/skills/brainstorming/SKILL.md +164 -0
  96. package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
  97. package/.agent/skills/clean-code/SKILL.md +200 -0
  98. package/.agent/skills/code-review-checklist/SKILL.md +125 -0
  99. package/.agent/skills/database-design/SKILL.md +54 -0
  100. package/.agent/skills/database-design/database-selection.md +43 -0
  101. package/.agent/skills/database-design/indexing.md +39 -0
  102. package/.agent/skills/database-design/migrations.md +50 -0
  103. package/.agent/skills/database-design/optimization.md +36 -0
  104. package/.agent/skills/database-design/orm-selection.md +30 -0
  105. package/.agent/skills/database-design/schema-design.md +56 -0
  106. package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
  107. package/.agent/skills/deployment-procedures/SKILL.md +241 -0
  108. package/.agent/skills/doc.md +177 -0
  109. package/.agent/skills/documentation-templates/SKILL.md +194 -0
  110. package/.agent/skills/frontend-design/SKILL.md +418 -0
  111. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  112. package/.agent/skills/frontend-design/color-system.md +307 -0
  113. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  114. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  115. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  116. package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
  117. package/.agent/skills/frontend-design/typography-system.md +345 -0
  118. package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
  119. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  120. package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
  121. package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
  122. package/.agent/skills/game-development/SKILL.md +167 -0
  123. package/.agent/skills/game-development/game-art/SKILL.md +185 -0
  124. package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
  125. package/.agent/skills/game-development/game-design/SKILL.md +129 -0
  126. package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
  127. package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
  128. package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
  129. package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
  130. package/.agent/skills/game-development/web-games/SKILL.md +150 -0
  131. package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
  132. package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
  133. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  134. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  135. package/.agent/skills/intelligent-routing/SKILL.md +335 -0
  136. package/.agent/skills/lint-and-validate/SKILL.md +44 -0
  137. package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
  138. package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
  139. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  140. package/.agent/skills/mobile-design/SKILL.md +394 -0
  141. package/.agent/skills/mobile-design/decision-trees.md +516 -0
  142. package/.agent/skills/mobile-design/mobile-backend.md +491 -0
  143. package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
  144. package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
  145. package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
  146. package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
  147. package/.agent/skills/mobile-design/mobile-performance.md +767 -0
  148. package/.agent/skills/mobile-design/mobile-testing.md +356 -0
  149. package/.agent/skills/mobile-design/mobile-typography.md +432 -0
  150. package/.agent/skills/mobile-design/platform-android.md +666 -0
  151. package/.agent/skills/mobile-design/platform-ios.md +561 -0
  152. package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
  153. package/.agent/skills/mobile-design/touch-psychology.md +537 -0
  154. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
  155. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
  156. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
  157. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
  158. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
  159. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
  160. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
  161. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
  162. package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
  163. package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
  164. package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
  165. package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
  166. package/.agent/skills/parallel-agents/SKILL.md +194 -0
  167. package/.agent/skills/performance-profiling/SKILL.md +149 -0
  168. package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
  169. package/.agent/skills/plan-writing/SKILL.md +152 -0
  170. package/.agent/skills/powershell-windows/SKILL.md +166 -0
  171. package/.agent/skills/python-patterns/SKILL.md +441 -0
  172. package/.agent/skills/red-team-tactics/SKILL.md +203 -0
  173. package/.agent/skills/rust-pro/SKILL.md +190 -0
  174. package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
  175. package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
  176. package/.agent/skills/server-management/SKILL.md +161 -0
  177. package/.agent/skills/systematic-debugging/SKILL.md +114 -0
  178. package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
  179. package/.agent/skills/tdd-workflow/SKILL.md +149 -0
  180. package/.agent/skills/testing-patterns/SKILL.md +178 -0
  181. package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
  182. package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
  183. package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
  184. package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
  185. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  186. package/.agent/skills/webapp-testing/SKILL.md +187 -0
  187. package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
  188. package/.agent/workflows/brainstorm.md +113 -0
  189. package/.agent/workflows/create.md +59 -0
  190. package/.agent/workflows/debug.md +103 -0
  191. package/.agent/workflows/deploy.md +176 -0
  192. package/.agent/workflows/enhance.md +63 -0
  193. package/.agent/workflows/orchestrate.md +242 -0
  194. package/.agent/workflows/plan.md +89 -0
  195. package/.agent/workflows/preview.md +80 -0
  196. package/.agent/workflows/restore-localize-compat.md +525 -0
  197. package/.agent/workflows/status.md +86 -0
  198. package/.agent/workflows/test.md +144 -0
  199. package/.agent/workflows/ui-ux-pro-max.md +295 -0
  200. package/AGENT_FLOW.md +609 -0
  201. package/CHANGELOG.md +68 -0
  202. package/LICENSE +21 -0
  203. package/README.md +260 -0
  204. package/bin/adapters/base.js +63 -0
  205. package/bin/adapters/codex.js +391 -0
  206. package/bin/adapters/gemini.js +137 -0
  207. package/bin/ag-kit.js +1336 -0
  208. package/bin/core/builder.js +80 -0
  209. package/bin/core/generator.js +59 -0
  210. package/bin/core/resource-loader.js +64 -0
  211. package/bin/core/transformer.js +208 -0
  212. package/bin/interactive.js +65 -0
  213. package/bin/utils/atomic-writer.js +97 -0
  214. package/bin/utils/git-helper.js +68 -0
  215. package/bin/utils/managed-block.js +65 -0
  216. package/bin/utils/manifest.js +241 -0
  217. package/bin/utils.js +82 -0
  218. package/docs/codex-rules-template.md +36 -0
  219. package/docs/mapping-spec.md +68 -0
  220. package/docs/multi-target-adapter.md +80 -0
  221. package/docs/official/README.md +53 -0
  222. package/docs/official/antigravity/agent-modes-settings.md +64 -0
  223. package/docs/official/antigravity/rules-workflows.md +96 -0
  224. package/docs/official/antigravity/skills.md +147 -0
  225. package/docs/official/codex/agents-md.md +119 -0
  226. package/docs/official/codex/config-advanced.md +358 -0
  227. package/docs/official/codex/config-basic.md +141 -0
  228. package/docs/official/codex/config-reference.md +223 -0
  229. package/docs/official/codex/config-sample.md +216 -0
  230. package/docs/official/codex/mcp.md +107 -0
  231. package/docs/official/codex/rules.md +79 -0
  232. package/docs/official/codex/skills.md +114 -0
  233. package/docs/official/sources-index.md +32 -0
  234. package/docs/operations.md +145 -0
  235. package/docs/terminology-style-guide.md +69 -0
  236. package/package.json +51 -0
  237. package/scripts/postinstall-check.js +112 -0
@@ -0,0 +1,683 @@
1
+ # 7. JavaScript 性能 (JavaScript Performance)
2
+
3
+ > **影响:** 低到中 (LOW-MEDIUM)
4
+ > **重点:** 对热点路径进行微优化,累积后可带来可观性能提升。
5
+
6
+ ---
7
+
8
+ ## 概览
9
+
10
+ 本节包含 **12 条规则**,聚焦 JavaScript 性能。
11
+
12
+ ---
13
+
14
+ ## 规则 7.1:避免布局抖动(Layout Thrashing)
15
+
16
+ **影响:** 中 (MEDIUM)
17
+ **标签:** javascript, dom, css, performance, reflow, layout-thrashing
18
+
19
+ ## 避免布局抖动(Layout Thrashing)
20
+
21
+ 避免把样式写入与布局读取交错执行。当你在样式变更之间读取布局属性(如 `offsetWidth`、`getBoundingClientRect()`、`getComputedStyle()`)时,浏览器会被迫触发同步回流(reflow)。
22
+
23
+ **可接受示例(浏览器可批处理样式变更):**
24
+ ```typescript
25
+ function updateElementStyles(element: HTMLElement) {
26
+ // 每行都会使样式失效,但浏览器可批量重计算
27
+ element.style.width = '100px'
28
+ element.style.height = '200px'
29
+ element.style.backgroundColor = 'blue'
30
+ element.style.border = '1px solid black'
31
+ }
32
+ ```
33
+
34
+ **错误示例(读写交错会强制回流):**
35
+ ```typescript
36
+ function layoutThrashing(element: HTMLElement) {
37
+ element.style.width = '100px'
38
+ const width = element.offsetWidth // 强制回流
39
+ element.style.height = '200px'
40
+ const height = element.offsetHeight // 再次强制回流
41
+ }
42
+ ```
43
+
44
+ **正确示例(先批量写,再统一读):**
45
+ ```typescript
46
+ function updateElementStyles(element: HTMLElement) {
47
+ // 先批量写入
48
+ element.style.width = '100px'
49
+ element.style.height = '200px'
50
+ element.style.backgroundColor = 'blue'
51
+ element.style.border = '1px solid black'
52
+
53
+ // 全部写完后再读取(单次回流)
54
+ const { width, height } = element.getBoundingClientRect()
55
+ }
56
+ ```
57
+
58
+ **正确示例(先批量读,再批量写):**
59
+ ```typescript
60
+ function avoidThrashing(element: HTMLElement) {
61
+ // 读取阶段:先做所有布局查询
62
+ const rect1 = element.getBoundingClientRect()
63
+ const offsetWidth = element.offsetWidth
64
+ const offsetHeight = element.offsetHeight
65
+
66
+ // 写入阶段:再做所有样式变更
67
+ element.style.width = '100px'
68
+ element.style.height = '200px'
69
+ }
70
+ ```
71
+
72
+ **更推荐:使用 CSS class**
73
+ ```css
74
+ .highlighted-box {
75
+ width: 100px;
76
+ height: 200px;
77
+ background-color: blue;
78
+ border: 1px solid black;
79
+ }
80
+ ```
81
+ ```typescript
82
+ function updateElementStyles(element: HTMLElement) {
83
+ element.classList.add('highlighted-box')
84
+
85
+ const { width, height } = element.getBoundingClientRect()
86
+ }
87
+ ```
88
+
89
+ **React example:**
90
+ ```tsx
91
+ // 错误示例:样式变更与布局查询交错
92
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
93
+ const ref = useRef<HTMLDivElement>(null)
94
+
95
+ useEffect(() => {
96
+ if (ref.current && isHighlighted) {
97
+ ref.current.style.width = '100px'
98
+ const width = ref.current.offsetWidth // 强制布局计算
99
+ ref.current.style.height = '200px'
100
+ }
101
+ }, [isHighlighted])
102
+
103
+ return <div ref={ref}>Content</div>
104
+ }
105
+
106
+ // 正确示例:切换 class
107
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
108
+ return (
109
+ <div className={isHighlighted ? 'highlighted-box' : ''}>
110
+ Content
111
+ </div>
112
+ )
113
+ }
114
+ ```
115
+
116
+ 在可行时优先 CSS class 而非内联样式。CSS 文件可被浏览器缓存,class 也更利于关注点分离与维护。
117
+
118
+ 关于会触发布局计算的操作,可参考 [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) 与 [CSS Triggers](https://csstriggers.com/)。
119
+
120
+ ---
121
+
122
+ ## 规则 7.2:重复查询应建立索引 Map
123
+
124
+ **影响:** 低到中 (LOW-MEDIUM)
125
+ **标签:** javascript, map, indexing, optimization, performance
126
+
127
+ ## 重复查询应建立索引 Map
128
+
129
+ 针对同一 key 的多次 `.find()` 查询,应改用 Map。
130
+
131
+ **错误示例(每次查询 O(n)):**
132
+
133
+ ```typescript
134
+ function processOrders(orders: Order[], users: User[]) {
135
+ return orders.map(order => ({
136
+ ...order,
137
+ user: users.find(u => u.id === order.userId)
138
+ }))
139
+ }
140
+ ```
141
+
142
+ **正确示例(每次查询 O(1)):**
143
+
144
+ ```typescript
145
+ function processOrders(orders: Order[], users: User[]) {
146
+ const userById = new Map(users.map(u => [u.id, u]))
147
+
148
+ return orders.map(order => ({
149
+ ...order,
150
+ user: userById.get(order.userId)
151
+ }))
152
+ }
153
+ ```
154
+
155
+ 先建一次 Map(O(n)),之后每次查询均为 O(1)。
156
+ 以 1000 orders × 1000 users 为例:约 100 万次操作可降到约 2000 次。
157
+
158
+ ---
159
+
160
+ ## 规则 7.3:循环中缓存属性访问
161
+
162
+ **影响:** 低到中 (LOW-MEDIUM)
163
+ **标签:** javascript, loops, optimization, caching
164
+
165
+ ## 循环中缓存属性访问
166
+
167
+ 在热点路径中,缓存对象属性读取结果。
168
+
169
+ **错误示例(3 次属性读取 × N 次循环):**
170
+
171
+ ```typescript
172
+ for (let i = 0; i < arr.length; i++) {
173
+ process(obj.config.settings.value)
174
+ }
175
+ ```
176
+
177
+ **正确示例(总共只读 1 次):**
178
+
179
+ ```typescript
180
+ const value = obj.config.settings.value
181
+ const len = arr.length
182
+ for (let i = 0; i < len; i++) {
183
+ process(value)
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## 规则 7.4:缓存重复函数调用
190
+
191
+ **影响:** 中 (MEDIUM)
192
+ **标签:** javascript, cache, memoization, performance
193
+
194
+ ## 缓存重复函数调用
195
+
196
+ 当渲染期间同一函数会以相同输入被重复调用时,使用模块级 Map 缓存结果。
197
+
198
+ **错误示例(重复计算):**
199
+
200
+ ```typescript
201
+ function ProjectList({ projects }: { projects: Project[] }) {
202
+ return (
203
+ <div>
204
+ {projects.map(project => {
205
+ // 对同名项目会重复调用 slugify() 100+ 次
206
+ const slug = slugify(project.name)
207
+
208
+ return <ProjectCard key={project.id} slug={slug} />
209
+ })}
210
+ </div>
211
+ )
212
+ }
213
+ ```
214
+
215
+ **正确示例(缓存结果):**
216
+
217
+ ```typescript
218
+ // 模块级缓存
219
+ const slugifyCache = new Map<string, string>()
220
+
221
+ function cachedSlugify(text: string): string {
222
+ if (slugifyCache.has(text)) {
223
+ return slugifyCache.get(text)!
224
+ }
225
+ const result = slugify(text)
226
+ slugifyCache.set(text, result)
227
+ return result
228
+ }
229
+
230
+ function ProjectList({ projects }: { projects: Project[] }) {
231
+ return (
232
+ <div>
233
+ {projects.map(project => {
234
+ // 每个唯一项目名只计算一次
235
+ const slug = cachedSlugify(project.name)
236
+
237
+ return <ProjectCard key={project.id} slug={slug} />
238
+ })}
239
+ </div>
240
+ )
241
+ }
242
+ ```
243
+
244
+ **单值函数可用更简单写法:**
245
+
246
+ ```typescript
247
+ let isLoggedInCache: boolean | null = null
248
+
249
+ function isLoggedIn(): boolean {
250
+ if (isLoggedInCache !== null) {
251
+ return isLoggedInCache
252
+ }
253
+
254
+ isLoggedInCache = document.cookie.includes('auth=')
255
+ return isLoggedInCache
256
+ }
257
+
258
+ // 认证状态变化时清空缓存
259
+ function onAuthChange() {
260
+ isLoggedInCache = null
261
+ }
262
+ ```
263
+
264
+ 使用 Map(而非 hook)可以在任意上下文复用:工具函数、事件处理器,不只 React 组件。
265
+
266
+ 参考: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
267
+
268
+ ---
269
+
270
+ ## 规则 7.5:缓存 Storage API 调用
271
+
272
+ **影响:** 低到中 (LOW-MEDIUM)
273
+ **标签:** javascript, localStorage, storage, caching, performance
274
+
275
+ ## 缓存 Storage API 调用
276
+
277
+ `localStorage`、`sessionStorage` 与 `document.cookie` 都是同步且开销较高的 API。应把读取结果缓存在内存中。
278
+
279
+ **错误示例(每次调用都读 storage):**
280
+
281
+ ```typescript
282
+ function getTheme() {
283
+ return localStorage.getItem('theme') ?? 'light'
284
+ }
285
+ // 调用 10 次 = 读取 storage 10 次
286
+ ```
287
+
288
+ **正确示例(Map 缓存):**
289
+
290
+ ```typescript
291
+ const storageCache = new Map<string, string | null>()
292
+
293
+ function getLocalStorage(key: string) {
294
+ if (!storageCache.has(key)) {
295
+ storageCache.set(key, localStorage.getItem(key))
296
+ }
297
+ return storageCache.get(key)
298
+ }
299
+
300
+ function setLocalStorage(key: string, value: string) {
301
+ localStorage.setItem(key, value)
302
+ storageCache.set(key, value) // 保持缓存同步
303
+ }
304
+ ```
305
+
306
+ 使用 Map(而非 hook)可以在任意上下文复用:工具函数、事件处理器,不只 React 组件。
307
+
308
+ **Cookie 缓存:**
309
+
310
+ ```typescript
311
+ let cookieCache: Record<string, string> | null = null
312
+
313
+ function getCookie(name: string) {
314
+ if (!cookieCache) {
315
+ cookieCache = Object.fromEntries(
316
+ document.cookie.split('; ').map(c => c.split('='))
317
+ )
318
+ }
319
+ return cookieCache[name]
320
+ }
321
+ ```
322
+
323
+ **重要(外部变化时要失效缓存):**
324
+
325
+ 若 storage 可能被外部改变(其他标签页、服务端写入 cookie),需要使缓存失效:
326
+
327
+ ```typescript
328
+ window.addEventListener('storage', (e) => {
329
+ if (e.key) storageCache.delete(e.key)
330
+ })
331
+
332
+ document.addEventListener('visibilitychange', () => {
333
+ if (document.visibilityState === 'visible') {
334
+ storageCache.clear()
335
+ }
336
+ })
337
+ ```
338
+
339
+ ---
340
+
341
+ ## 规则 7.6:合并多次数组遍历
342
+
343
+ **影响:** 低到中 (LOW-MEDIUM)
344
+ **标签:** javascript, arrays, loops, performance
345
+
346
+ ## 合并多次数组遍历
347
+
348
+ 多次 `.filter()` / `.map()` 会重复遍历同一数组。可合并为一次循环。
349
+
350
+ **错误示例(3 次遍历):**
351
+
352
+ ```typescript
353
+ const admins = users.filter(u => u.isAdmin)
354
+ const testers = users.filter(u => u.isTester)
355
+ const inactive = users.filter(u => !u.isActive)
356
+ ```
357
+
358
+ **正确示例(1 次遍历):**
359
+
360
+ ```typescript
361
+ const admins: User[] = []
362
+ const testers: User[] = []
363
+ const inactive: User[] = []
364
+
365
+ for (const user of users) {
366
+ if (user.isAdmin) admins.push(user)
367
+ if (user.isTester) testers.push(user)
368
+ if (!user.isActive) inactive.push(user)
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## 规则 7.7:数组比较先做长度短路判断
375
+
376
+ **影响:** 中到高 (MEDIUM-HIGH)
377
+ **标签:** javascript, arrays, performance, optimization, comparison
378
+
379
+ ## 数组比较先做长度短路判断
380
+
381
+ 当数组比较需要昂贵操作(排序、深比较、序列化)时,先比较长度。长度不同就不可能相等。
382
+
383
+ 在真实业务里,若比较发生在热点路径(事件处理器、渲染循环)中,这个优化尤其有价值。
384
+
385
+ **错误示例(总会执行昂贵比较):**
386
+
387
+ ```typescript
388
+ function hasChanges(current: string[], original: string[]) {
389
+ // 即使长度不同也会执行排序和 join
390
+ return current.sort().join() !== original.sort().join()
391
+ }
392
+ ```
393
+
394
+ 即使 `current.length` 为 5、`original.length` 为 100,也会执行两次 O(n log n) 排序;同时还会有 join 和字符串比较开销。
395
+
396
+ **正确示例(先做 O(1) 长度检查):**
397
+
398
+ ```typescript
399
+ function hasChanges(current: string[], original: string[]) {
400
+ // 长度不同直接返回
401
+ if (current.length !== original.length) {
402
+ return true
403
+ }
404
+ // 仅在长度相同才排序
405
+ const currentSorted = current.toSorted()
406
+ const originalSorted = original.toSorted()
407
+ for (let i = 0; i < currentSorted.length; i++) {
408
+ if (currentSorted[i] !== originalSorted[i]) {
409
+ return true
410
+ }
411
+ }
412
+ return false
413
+ }
414
+ ```
415
+
416
+ 这种方式更高效,因为:
417
+ - 长度不同场景下可避免排序与 join 的额外开销
418
+ - 避免为 join 后字符串分配额外内存(大数组下尤为重要)
419
+ - 不会修改原数组
420
+ - 一旦发现差异可提前返回
421
+
422
+ ---
423
+
424
+ ## 规则 7.8:函数中尽早返回
425
+
426
+ **影响:** 低到中 (LOW-MEDIUM)
427
+ **标签:** javascript, functions, optimization, early-return
428
+
429
+ ## 函数中尽早返回
430
+
431
+ 当结果已确定时尽早返回,跳过不必要处理。
432
+
433
+ **错误示例(找到答案后仍处理全部项):**
434
+
435
+ ```typescript
436
+ function validateUsers(users: User[]) {
437
+ let hasError = false
438
+ let errorMessage = ''
439
+
440
+ for (const user of users) {
441
+ if (!user.email) {
442
+ hasError = true
443
+ errorMessage = 'Email required'
444
+ }
445
+ if (!user.name) {
446
+ hasError = true
447
+ errorMessage = 'Name required'
448
+ }
449
+ // 即使已发现错误仍继续检查所有用户
450
+ }
451
+
452
+ return hasError ? { valid: false, error: errorMessage } : { valid: true }
453
+ }
454
+ ```
455
+
456
+ **正确示例(首次错误即返回):**
457
+
458
+ ```typescript
459
+ function validateUsers(users: User[]) {
460
+ for (const user of users) {
461
+ if (!user.email) {
462
+ return { valid: false, error: 'Email required' }
463
+ }
464
+ if (!user.name) {
465
+ return { valid: false, error: 'Name required' }
466
+ }
467
+ }
468
+
469
+ return { valid: true }
470
+ }
471
+ ```
472
+
473
+ ---
474
+
475
+ ## 规则 7.9:提升(Hoist)RegExp 创建位置
476
+
477
+ **影响:** 低到中 (LOW-MEDIUM)
478
+ **标签:** javascript, regexp, optimization, memoization
479
+
480
+ ## 提升(Hoist)RegExp 创建位置
481
+
482
+ 不要在渲染期间创建 RegExp。应提升到模块作用域,或用 `useMemo()` 缓存。
483
+
484
+ **错误示例(每次渲染都创建新 RegExp):**
485
+
486
+ ```tsx
487
+ function Highlighter({ text, query }: Props) {
488
+ const regex = new RegExp(`(${query})`, 'gi')
489
+ const parts = text.split(regex)
490
+ return <>{parts.map((part, i) => ...)}</>
491
+ }
492
+ ```
493
+
494
+ **正确示例(memoize 或 hoist):**
495
+
496
+ ```tsx
497
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
498
+
499
+ function Highlighter({ text, query }: Props) {
500
+ const regex = useMemo(
501
+ () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
502
+ [query]
503
+ )
504
+ const parts = text.split(regex)
505
+ return <>{parts.map((part, i) => ...)}</>
506
+ }
507
+ ```
508
+
509
+ **警告(全局正则包含可变状态):**
510
+
511
+ 全局正则(`/g`)的 `lastIndex` 是可变状态:
512
+
513
+ ```typescript
514
+ const regex = /foo/g
515
+ regex.test('foo') // true, lastIndex = 3
516
+ regex.test('foo') // false, lastIndex = 0
517
+ ```
518
+
519
+ ---
520
+
521
+ ## 规则 7.10:求最值用循环,不要用排序
522
+
523
+ **影响:** 低 (LOW)
524
+ **标签:** javascript, arrays, performance, sorting, algorithms
525
+
526
+ ## 求最值用循环,不要用排序
527
+
528
+ 查找最小/最大值只需一次遍历。排序既浪费又更慢。
529
+
530
+ **错误示例(O(n log n),为找最大值先排序):**
531
+
532
+ ```typescript
533
+ interface Project {
534
+ id: string
535
+ name: string
536
+ updatedAt: number
537
+ }
538
+
539
+ function getLatestProject(projects: Project[]) {
540
+ const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
541
+ return sorted[0]
542
+ }
543
+ ```
544
+
545
+ 为了找最大值却把整个数组排序,开销过大。
546
+
547
+ **错误示例(O(n log n),为最旧/最新都先排序):**
548
+
549
+ ```typescript
550
+ function getOldestAndNewest(projects: Project[]) {
551
+ const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
552
+ return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
553
+ }
554
+ ```
555
+
556
+ 仅需 min/max 时仍排序,属于不必要开销。
557
+
558
+ **正确示例(O(n),单次循环):**
559
+
560
+ ```typescript
561
+ function getLatestProject(projects: Project[]) {
562
+ if (projects.length === 0) return null
563
+
564
+ let latest = projects[0]
565
+
566
+ for (let i = 1; i < projects.length; i++) {
567
+ if (projects[i].updatedAt > latest.updatedAt) {
568
+ latest = projects[i]
569
+ }
570
+ }
571
+
572
+ return latest
573
+ }
574
+
575
+ function getOldestAndNewest(projects: Project[]) {
576
+ if (projects.length === 0) return { oldest: null, newest: null }
577
+
578
+ let oldest = projects[0]
579
+ let newest = projects[0]
580
+
581
+ for (let i = 1; i < projects.length; i++) {
582
+ if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
583
+ if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
584
+ }
585
+
586
+ return { oldest, newest }
587
+ }
588
+ ```
589
+
590
+ 单次遍历,无需复制数组,也无需排序。
591
+
592
+ **替代方案(小数组可用 Math.min/Math.max):**
593
+
594
+ ```typescript
595
+ const numbers = [5, 2, 8, 1, 9]
596
+ const min = Math.min(...numbers)
597
+ const max = Math.max(...numbers)
598
+ ```
599
+
600
+ 该方式适合小数组,但在超大数组下受展开运算符限制,可能更慢甚至报错。Chrome 143 约 124000、Safari 18 约 638000(具体会波动),可见 [the fiddle](https://jsfiddle.net/qw1jabsx/4/);为稳健性建议使用循环方案。
601
+
602
+ ---
603
+
604
+ ## 规则 7.11:使用 Set/Map 做 O(1) 查询
605
+
606
+ **影响:** 低到中 (LOW-MEDIUM)
607
+ **标签:** javascript, set, map, data-structures, performance
608
+
609
+ ## 使用 Set/Map 做 O(1) 查询
610
+
611
+ 重复成员判断时,应将数组转为 Set/Map。
612
+
613
+ **错误示例(每次查询 O(n)):**
614
+
615
+ ```typescript
616
+ const allowedIds = ['a', 'b', 'c', ...]
617
+ items.filter(item => allowedIds.includes(item.id))
618
+ ```
619
+
620
+ **正确示例(每次查询 O(1)):**
621
+
622
+ ```typescript
623
+ const allowedIds = new Set(['a', 'b', 'c', ...])
624
+ items.filter(item => allowedIds.has(item.id))
625
+ ```
626
+
627
+ ---
628
+
629
+ ## 规则 7.12:保持不可变性时用 toSorted() 替代 sort()
630
+
631
+ **影响:** 中到高 (MEDIUM-HIGH)
632
+ **标签:** javascript, arrays, immutability, react, state, mutation
633
+
634
+ ## 保持不可变性时用 toSorted() 替代 sort()
635
+
636
+ `.sort()` 会原地修改数组,这在 React 的 state/props 场景里容易引发问题。应使用 `.toSorted()` 生成新数组并保持不可变。
637
+
638
+ **错误示例(会修改原数组):**
639
+
640
+ ```typescript
641
+ function UserList({ users }: { users: User[] }) {
642
+ // 会修改 users 这个 props 数组
643
+ const sorted = useMemo(
644
+ () => users.sort((a, b) => a.name.localeCompare(b.name)),
645
+ [users]
646
+ )
647
+ return <div>{sorted.map(renderUser)}</div>
648
+ }
649
+ ```
650
+
651
+ **正确示例(创建新数组):**
652
+
653
+ ```typescript
654
+ function UserList({ users }: { users: User[] }) {
655
+ // 创建新排序数组,原数组保持不变
656
+ const sorted = useMemo(
657
+ () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
658
+ [users]
659
+ )
660
+ return <div>{sorted.map(renderUser)}</div>
661
+ }
662
+ ```
663
+
664
+ **为何在 React 中很重要:**
665
+
666
+ 1. 修改 props/state 会破坏 React 的不可变模型,React 期望它们是只读输入
667
+ 2. 容易触发过期闭包问题,在回调/effect 中修改数组会带来不可预期行为
668
+
669
+ **浏览器支持(旧环境回退):**
670
+
671
+ `.toSorted()` 在现代浏览器均可用(Chrome 110+、Safari 16+、Firefox 115+、Node.js 20+)。旧环境可用展开运算符回退:
672
+
673
+ ```typescript
674
+ // 旧浏览器回退方案
675
+ const sorted = [...items].sort((a, b) => a.value - b.value)
676
+ ```
677
+
678
+ **其他不可变数组方法:**
679
+
680
+ - `.toSorted()`:不可变排序
681
+ - `.toReversed()`:不可变反转
682
+ - `.toSpliced()`:不可变 splice
683
+ - `.with()`:不可变元素替换