開發者在使用 jQuery 連續給一組 input 元素賦值時,發現陣列的第 22 項(array[22])明明有資料,賦值語句卻一直寫入空值。更詭異的是,若在該行前插入一個 alert() 做「延遲」,賦值反而成功了。這真的是「JS 執行太快」導致的嗎?
JavaScript 是單執行緒、非阻塞的語言。當你用迴圈批量操作 DOM 時,瀏覽器不會在每次賦值後立即重繪,而是將所有同步代碼推入呼叫堆疊一次性執行完畢,才會在下一個微任務/宏任務階段進行佈局與繪製。若後續邏輯(如讀取 array[22])依賴 DOM 已經真正更新完畢,卻在同一個同步區塊裡直接執行,就會拿到舊狀態——這就是典型的「競態條件」。
alert() 之所以能「緩解」,是因為它會阻塞主執行緒並強制瀏覽器進入一個新的事件迴圈迭代,給渲染引擎留出了繪製時間,讓 DOM 狀態趕上來。
requestAnimationFrame 或 setTimeout(fn, 0) 推延到下一幀// 錯誤示範
for (let i = 0; i < inputs.length; i++) {
$(inputs[i]).val(data[i]); // 同步批量寫入
}
console.log($(inputs[22]).val()); // 可能仍為空
// 正確寫法:分批或推延讀取
function batchAssign(inputs, data, idx = 0) {
const chunk = 50; // 每幀處理 50 個
for (let i = 0; i < chunk && idx < inputs.length; i++, idx++) {
$(inputs[idx]).val(data[idx]);
}
if (idx < inputs.length) {
requestAnimationFrame(() => batchAssign(inputs, data, idx));
} else {
// 全部寫入完成,安全讀取
console.log($(inputs[22]).val());
}
}
batchAssign(inputs, data);
await 後再操作 DOMasync function loadAndFill() {
const res = await fetch('/api/data');
const arr = await res.json();
// 這時 arr 已就緒,同步寫入即可
inputs.forEach((el, i) => $(el).val(arr[i]));
}
MutationObserver 監聽特定元素屬性變化若必須在「第 22 個 input 真正顯示新值」的瞬間觸發後續邏輯,可掛載觀察器:
const target = inputs[22];
const observer = new MutationObserver(() => {
if (target.value) {
console.log('第 22 項已更新:', target.value);
observer.disconnect();
}
});
observer.observe(target, { attributes: true, attributeFilter: ['value'] });
alert()、debugger 當生產環境延遲手段,會嚴重阻塞主執行緒並破壞使用者體驗。requestAnimationFrame 或 requestIdleCallback。innerHTML 或 DocumentFragment 寫入,減少重排重繪。掌握事件迴圈機制,才能從根源上避免「速度太快賦不上值」這類看似玄學、實則邏輯時序錯誤的坑。