@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.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +285 -0
- package/.agent/agents/backend-specialist.md +268 -0
- package/.agent/agents/code-archaeologist.md +106 -0
- package/.agent/agents/database-architect.md +225 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +618 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +382 -0
- package/.agent/agents/orchestrator.md +438 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/product-manager.md +112 -0
- package/.agent/agents/product-owner.md +95 -0
- package/.agent/agents/project-planner.md +405 -0
- package/.agent/agents/qa-automation-engineer.md +103 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/mcp_config.json +12 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +84 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +74 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +57 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +201 -0
- package/.agent/skills/behavioral-modes/SKILL.md +264 -0
- package/.agent/skills/brainstorming/SKILL.md +164 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
- package/.agent/skills/clean-code/SKILL.md +200 -0
- package/.agent/skills/code-review-checklist/SKILL.md +125 -0
- package/.agent/skills/database-design/SKILL.md +54 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +50 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +307 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/intelligent-routing/SKILL.md +335 -0
- package/.agent/skills/lint-and-validate/SKILL.md +44 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +432 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
- package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +194 -0
- package/.agent/skills/performance-profiling/SKILL.md +149 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +166 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/red-team-tactics/SKILL.md +203 -0
- package/.agent/skills/rust-pro/SKILL.md +190 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +114 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +242 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/restore-localize-compat.md +525 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +295 -0
- package/AGENT_FLOW.md +609 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/adapters/base.js +63 -0
- package/bin/adapters/codex.js +391 -0
- package/bin/adapters/gemini.js +137 -0
- package/bin/ag-kit.js +1336 -0
- package/bin/core/builder.js +80 -0
- package/bin/core/generator.js +59 -0
- package/bin/core/resource-loader.js +64 -0
- package/bin/core/transformer.js +208 -0
- package/bin/interactive.js +65 -0
- package/bin/utils/atomic-writer.js +97 -0
- package/bin/utils/git-helper.js +68 -0
- package/bin/utils/managed-block.js +65 -0
- package/bin/utils/manifest.js +241 -0
- package/bin/utils.js +82 -0
- package/docs/codex-rules-template.md +36 -0
- package/docs/mapping-spec.md +68 -0
- package/docs/multi-target-adapter.md +80 -0
- package/docs/official/README.md +53 -0
- package/docs/official/antigravity/agent-modes-settings.md +64 -0
- package/docs/official/antigravity/rules-workflows.md +96 -0
- package/docs/official/antigravity/skills.md +147 -0
- package/docs/official/codex/agents-md.md +119 -0
- package/docs/official/codex/config-advanced.md +358 -0
- package/docs/official/codex/config-basic.md +141 -0
- package/docs/official/codex/config-reference.md +223 -0
- package/docs/official/codex/config-sample.md +216 -0
- package/docs/official/codex/mcp.md +107 -0
- package/docs/official/codex/rules.md +79 -0
- package/docs/official/codex/skills.md +114 -0
- package/docs/official/sources-index.md +32 -0
- package/docs/operations.md +145 -0
- package/docs/terminology-style-guide.md +69 -0
- package/package.json +51 -0
- 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()`:不可变元素替换
|