角色 | 类比 |
---|---|
调用栈 Call Stack | 烤串师傅(一次只能烤一串) |
宏任务队列 Macrotask Queue | 外卖订单(setTimeout、I/O、UI 渲染) |
微任务队列 Microtask Queue | 加急小票(Promise、queueMicrotask、MutationObserver) |
document.getElementById('saveButton').addEventListener('click', async () => { updateLoadingState(true); // 1. 立刻转菊花 try { await saveUserData(userData); // 2. 异步保存 showSuccessMessage('保存成功'); // 3. 微任务里更新 UI const data = await fetchUser(); // 4. 再拿新数据 updateUI(data); // 5. 再更新 UI } catch (e) { showErrorMessage(e.message); // 6. 出错兜底 } finally { updateLoadingState(false); // 7. 关闭菊花 } });菊花之所以能立刻转起来,是因为 updateLoadingState(true) 是同步的。
queueMicrotask(() => { console.log('我是加急,宏任务都靠边!'); });与 setTimeout(fn, 0) 的区别:setTimeout 是宏任务,得等渲染;queueMicrotask 是微任务,马上跑。
class ThemeManager { constructor() { this.theme = { darkMode: false, contrast: 'normal', fontSize: 16 }; this.isRendering = false; } // 堆代码 duidaima.com updateTheme(updates) { // 1. 改数据 this.theme = { ...this.theme, ...updates }; // 2. 只排一次队,避免多次重排 if (!this.isRendering) { this.isRendering = true; queueMicrotask(() => { this.applyThemeToDOM(); this.isRendering = false; }); } } applyThemeToDOM() { document.documentElement.style.setProperty( '--bg', this.theme.darkMode ? '#1a1a1a' : '#fff' ); } } const tm = new ThemeManager(); tm.updateTheme({ darkMode: true }); tm.updateTheme({ contrast: 'high' }); tm.updateTheme({ fontSize: 20 }); // 最终只调用一次 applyThemeToDOM,性能拉满!FormController:表单提交不抖屏
class FormController { constructor(formEl) { this.form = formEl; this.isSubmitting = false; this.form.addEventListener('submit', async (e) => { e.preventDefault(); if (this.isSubmitting) return; this.setSubmitting(true); try { const res = await fetch('/api/submit', { method: 'POST', body: new FormData(this.form) }); if (!res.ok) throw new Error(res.statusText); // 用微任务确保状态更新后再提示用户 queueMicrotask(() => { this.showSuccess(); this.resetForm(); }); } catch (err) { this.showError(err); } finally { this.setSubmitting(false); } }); } setSubmitting(flag) { this.isSubmitting = flag; this.form.querySelector('button[type="submit"]').disabled = flag; } }微任务 vs Promise:谁先谁后?
console.log('Script start'); setTimeout(() => console.log('setTimeout'), 0); queueMicrotask(() => { console.log('Microtask 1'); queueMicrotask(() => console.log('Nested Microtask 1')); }); Promise.resolve() .then(() => { console.log('Promise 1'); queueMicrotask(() => console.log('Nested Microtask 2')); }) .then(() => console.log('Promise 2')); queueMicrotask(() => console.log('Microtask 2')); console.log('Script end'); /* 输出顺序: Script start Script end Microtask 1 Promise 1 Microtask 2 Nested Microtask 1 Promise 2 Nested Microtask 2 setTimeout */口诀:“清完微任务,才轮到宏任务”。
function infiniteMicrotask() { queueMicrotask(infiniteMicrotask); // 卡死循环,浏览器直接罢工 }2. 微任务里干重活 → 阻塞渲染
queueMicrotask(() => { // 千万别算大斐波那契,UI 会冻成狗 });3. 与宏任务混用 → 顺序谜之混乱
特性 | JS 微任务 | Go defer |
---|---|---|
触发时机 | 当前同步栈清空后 | 函数 return 前 |
执行顺序 | FIFO(队列) | LIFO(栈) |
异步? | ✅ 是 | ❌ 否 |
典型用途 | UI 批处理/状态同步 | 文件关闭/锁释放 |
3.断点:直接在 .then 或 queueMicrotask 回调里打断点,看闭包变量。
.时间敏感动画(交给 requestAnimationFrame)