04 — JavaScript 基础回顾
你说你有 JavaScript 基础。本章不重复教基础语法,而是把项目里频繁用到的、可能你不熟的现代 JS 语法挑出来讲。
这一章对读懂 React 代码非常关键。
1. 解构赋值(Destructuring) — 项目里到处都用
1.1 对象解构
1 2 3 4 5 6 7
| const props = someObject; const name = props.name; const age = props.age;
const { name, age } = someObject;
|
项目里到处都这么用:
1 2 3 4 5
| export function ChatItem({ chat, active, onSelect, onDelete }: Props) {
return <div onClick={() => onSelect(chat.id)}>...</div>; }
|
1.2 嵌套解构 + 重命名
1 2 3
| const { address: { city }, name: userName } = user;
|
1.3 默认值
1 2 3
| const { collapsed = false } = someProps;
|
项目里的例子:
1 2 3 4
| const [collapsed, setCollapsed] = useState<boolean>(() => { });
|
1.4 数组解构
1 2
| const [first, second] = [1, 2, 3]; const [matches, setMatches] = useState(false);
|
为什么 useState 返回的是数组而不是对象?
因为如果返回对象,你必须知道字段名:
1
| const { value, setValue } = useState();
|
返回数组可以随便命名:
1
| const [collapsed, setCollapsed] = useState(false);
|
2. 展开运算符(Spread)... — 项目里常用
2.1 展开数组
1 2 3 4 5
| const a = [1, 2]; const b = [...a, 3, 4];
const newList = [...oldList, newItem];
|
2.2 展开对象
1 2
| const user = { name: 'Alice', age: 30 }; const updated = { ...user, age: 31 };
|
2.3 复制对象
1 2 3 4 5 6
| const a = { x: 1 }; const b = { ...a };
set({ chatList: [...chatList] });
|
2.4 项目里出现的地方
1 2 3 4 5 6 7 8 9
| className={`${styles.item} ${active ? styles.active : ''}`}
<MyComponent {...someObject} />
chatList: [...get().chatList]
|
3. 模板字符串(Template Literals)
1 2 3 4 5 6 7 8 9
| const name = '豆豆'; const greeting = `你好,${name}!`;
const html = ` <div> <p>${name}</p> </div> `;
|
项目里:
1 2 3 4 5 6 7 8
| <div className={`${styles.item} ${active ? styles.active : ''}`}>
className={`glass-panel ${styles.sidebar} ${collapsed ? styles.collapsed : ''} ${ isDrawerOpen ? styles.drawerOpen : '' }`}
|
4. 可选链 ?. 与空值合并 ??
4.1 可选链 ?.
项目里频繁出现。
1 2 3 4 5 6
| const title = user && user.profile && user.profile.title;
const title = user?.profile?.title;
|
项目里的例子:
1 2 3 4 5 6 7 8
| {message.timeline && message.timeline.length > 0 && ( <Timeline items={message.timeline} open={!!message.showTimeline} onToggle={...} /> )}
<button onClick={() => fileInputRef.current?.click()}>
|
4.2 空值合并 ??
1 2 3 4 5 6
| const name = input ?? '默认值';
const a = 0 || 'default'; const b = 0 ?? 'default';
|
5. 箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function add(a, b) { return a + b; }
const add = (a, b) => a + b;
const add = (a, b) => { console.log('计算中'); return a + b; };
const double = x => x * 2;
|
项目里几乎全是箭头函数:
1 2 3 4 5 6 7 8 9 10
| onClick={() => selectAgent(a.id)} onChange={(e) => setInput(e.target.value)}
chatList.map((c) => <ChatItem key={c.id} chat={c} ... />) chatList.find((c) => c.id === id)
const canSend = !isSending && !isUploading && (inputMessage.trim().length > 0 || selectedFile);
|
6. 数组方法(必须熟练)
6.1 .map() — 转换每个元素,返回新数组
1
| [1, 2, 3].map(x => x * 2)
|
项目里 90% 的列表渲染都用 map:
1 2 3 4
| {chatList.map((c) => ( <ChatItem key={c.id} chat={c} active={c.id === currentChatId} onSelect={selectChat} onDelete={deleteChat} /> ))}
|
⚠️ 列表中每个元素必须有 key 属性(看 11-事件处理与条件列表渲染)
6.2 .filter() — 过滤
1 2 3 4
| [1, 2, 3, 4].filter(x => x > 2)
const remaining = chatList.filter(c => c.id !== id);
|
6.3 .find() — 找一个(找不到返回 undefined)
1 2 3 4
| [1, 2, 3].find(x => x > 1)
const chat = chatList.find(c => c.id === currentChatId);
|
6.4 .findIndex() — 找索引(找不到返回 -1)
1 2 3 4 5 6 7
| [1, 2, 3].findIndex(x => x > 1)
const idx = aiMsg.timeline.findIndex( (t) => t.type === 'tool' && t.toolCallId === event.toolCallId && t.status === 'running' ); if (idx >= 0) { ... }
|
6.5 .some() / .every() — 判断
1 2 3 4 5
| [1, 2, 3].some(x => x > 2) [1, 2, 3].every(x => x > 0)
const hasError = aiMsg.timeline.some(t => t.type === 'error');
|
6.6 .reduce() — 累积
项目里没怎么用,但你可能会碰到。
6.7 .includes() — 包含
1 2 3 4
| [1, 2, 3].includes(2)
'hello'.includes('ell')
|
7. 对象方法(常用)
7.1 Object.keys() / Object.values() / Object.entries()
1 2 3 4
| const obj = { a: 1, b: 2, c: 3 }; Object.keys(obj) Object.values(obj) Object.entries(obj)
|
7.2 ?. 可选链调用
1
| const result = obj.method?.();
|
7.3 in 操作符
1 2 3 4 5 6
| if ('data' in someObj) { ... }
if (data && typeof data === 'object' && 'data' in data) { }
|
8. 异步:Promise、async/await、fetch
8.1 Promise 三种状态
1 2 3 4 5
| const p = new Promise((resolve, reject) => { if (成功) resolve(data); else reject(error); });
|
8.2 async/await — 项目里到处都是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fetch(url) .then(res => res.json()) .then(data => render(data)) .catch(err => console.error(err));
async function loadData() { try { const res = await fetch(url); const data = await res.json(); render(data); } catch (err) { console.error(err); } }
|
项目里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| useEffect(() => { (async () => { await testConnection(); await loadChats(); createNewChat(); })(); }, [...]);
async loadChats() { try { const data = await sessionApi.list(); set({ chatList: data.records.map(mapSessionListItem) }); } catch (e) { console.error('loadChats failed', e); set({ chatList: [] }); } }
|
8.3 fetch — 调用后端 API
1 2 3 4 5 6 7 8
| const res = await fetch('https://example.com/api/data', { method: 'GET', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ... }), });
if (!res.ok) throw new Error('请求失败'); const json = await res.json();
|
项目里的封装:
1 2 3 4 5 6 7 8
| async function httpGet<T>(path: string): Promise<T> { const res = await fetch(`${API_BASE}${path}`, { method: 'GET', headers: { Accept: 'application/json' }, }); return parseOrThrow<T>(res); }
|
8.4 AbortController — 取消请求
1 2 3 4 5 6 7 8 9 10 11 12
| const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try { const res = await fetch(url, { signal: controller.signal }); } catch (e) { if (e.name === 'AbortError') { console.log('请求被取消'); } }
|
项目里:
1 2 3 4 5 6 7 8 9 10 11
| abortController = new AbortController(); await parseSseStream({ url, signal: abortController.signal, ... });
async stopMessage() { abortController?.abort(); }
|
9. Set / Map 数据结构
9.1 Set — 值的集合(无重复)
1 2 3 4 5
| const set = new Set(); set.add(1); set.has(1); set.delete(1); set.size;
|
9.2 Map — 键值对(键可以是任意类型)
1 2 3 4 5
| const map = new Map(); map.set('key', 'value'); map.get('key'); map.has('key'); map.delete('key');
|
项目里没怎么用,但 React 生态中常见。
10. typeof / instanceof / Array.isArray()
1 2 3 4 5 6 7 8 9 10
| typeof 'hello' typeof 123 typeof undefined typeof null
if (typeof data === 'object' && 'data' in data) { ... }
Array.isArray([1, 2]) Array.isArray('hello')
|
11. JSON.parse / JSON.stringify
1 2
| JSON.parse('{"name":"Alice"}') JSON.stringify({ name: 'A' })
|
项目里频繁用到:
1 2 3 4 5 6
| const event = JSON.parse(data);
const params = new URLSearchParams({ agentType: 'chat', conversationId: 'abc' });
|
12. 类的简写语法
1 2 3 4 5 6 7
| class ApiError extends Error { constructor(message, code) { super(message); this.name = 'ApiError'; this.code = code; } }
|
项目里:
1 2 3 4 5 6 7
| export class ApiError extends Error { constructor(message: string, public code?: number) { super(message); this.name = 'ApiError'; } }
|
注意 public code?: number 是 TypeScript 简写,等价于在构造函数中赋值 this.code = code。
13. 模块系统:import / export
13.1 命名导出/导入
1 2 3 4 5 6
| export function add(a, b) { return a + b; } export const PI = 3.14;
import { add, PI } from './utils';
|
13.2 默认导出
1 2 3 4 5
| export default function MyComponent() { ... }
import MyComp from './MyComponent';
|
13.3 路径别名 @/
项目 vite.config.ts 配置了 @ 指向 src 目录:
1 2 3 4 5
| import { useChatStore } from '@/store/chatStore'; import type { ChatMessage } from '@/store/chatStore';
import { useChatStore } from '../../store/chatStore';
|
不用 ../.../... 一路数层级,统一用 @/ 从 src 根开始算。
13.4 import type — TypeScript 专用
1 2
| import { useChatStore } from '@/store/chatStore'; import type { ChatMessage } from '@/store/chatStore';
|
好处:编译后 import type 整行被删除,打包体积更小。
14. 一段话总结
本项目里 JS 的现代语法(解构、展开、箭头、map/filter、可选链、async/await)几乎每个文件都在用。
重点掌握:解构、展开运算符、可选链、数组方法、async/await、模块导入。
接下来
JS 语法已对齐。继续阅读 05-TypeScript快速入门.md,理解 TypeScript 给 JS 加了什么。