以下是前端面試中 JavaScript 基礎易錯問題的詳解,結合常見考點和易混淆概念進行解析:
?? 一、變量作用域與提升
- var vs let/const
? 變量提升:var 聲明的變量會提升到作用域頂部(值為 undefined),而 let/const 存在暫時性死區(聲明前訪問報錯)。
? 循環陷阱:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 輸出 3,3,3(共享同一作用域)
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 輸出 0,1,2(塊級作用域)
}
解決:使用 let 或 IIFE(立即執行函數)創建獨立作用域。
- 全局變量污染
未使用聲明關鍵字(如 x = 10)會創建全局變量,嚴格模式(“use strict”)可避免此問題。
🔄 二、異步與事件循環
- setTimeout 延遲問題
? 即使延遲為 0,回調仍會進入任務隊列,等待同步代碼執行完畢。
? 循環中 var 聲明的變量會在異步回調執行時變為最終值(如循環結束后的 i)。
- 事件隊列示例
document.addEventListener(“click”, () => console.log(“Click”)); // 異步事件
function sleep() { /* 同步阻塞 2 秒 */ }
sleep(); // 阻塞期間點擊不會觸發事件,直到同步代碼完成3。
🔗 三、閉包與內存管理
- 閉包原理
函數保留其詞法作用域的引用:
function outer() {
let count = 0;
return () => count++; // 閉包持有 count 的引用
}
const counter = outer();
counter(); // 1(變量不會被回收)6,7。
- 內存泄漏風險
? 閉包引用大對象時,即使未使用也可能阻止垃圾回收:
function createHandler() {
const data = new Array(1000000);
return () => console.log(data[0]); // data 被閉包持有
}
// 解決:不再需要時手動解除引用(如 data = null
)6,7。
? 循環中閉包共享變量需通過參數傳遞值(非引用)。
🔍 四、類型轉換與比較
- == 與 ===
? == 隱式類型轉換規則:
[] == ![] // true: ![] → false → 0, [] → “” → 0
0 == “0” // true: 字符串轉數字
null == undefined // true(特殊規則)1,4。
? 優先使用 === 避免意外轉換。
- typeof 與 null
typeof null === “object”(歷史遺留 bug),判斷數組用 Array.isArray()。
🧠 五、this 綁定規則
this 由調用方式決定:
const obj = {
name: “A”,
log() {
console.log(this.name); // “A”(obj 調用)
function inner() {
console.log(this); // window(默認綁定)
}
inner();
}
};
obj.log();
? 解決:箭頭函數繼承外層 this,或使用 bind/call 顯式綁定。
📦 六、對象與引用
- 深淺拷貝
? 直接賦值是淺拷貝(共享引用):
let a = [1, 2];
let b = a;
b.push(3); // a 也變為 [1,2,3]1,5。
? 深拷貝方法:JSON.parse(JSON.stringify(obj))(不支持函數)或遞歸復制。
- 重復屬性名
const obj = { a: “one”, b: “two”, a: “three” };
console.log(obj); // {a: “three”, b: “two”}(后聲明的覆蓋前者)1。
? 七、其他易錯點
- 浮點數精度
0.1 + 0.2 !== 0.3(二進制浮點數問題),需用 toFixed(2) 處理。
- 稀疏數組
const arr = [1, , 3]; // 中間為空
arr.map(x => x * 2); // [2, empty, 6](跳過空位)4。
- switch 嚴格比較
switch (new String(“A”)) {
case “A”: console.log(“Case A”); // 不執行(對象 vs 字符串)
default: console.log(“Unknown”); // 輸出此4。
}
💎 總結
理解這些問題的核心在于:
? 作用域(塊級 vs 函數級)
? 異步機制(事件隊列、微任務/宏任務)
? 引用類型(對象、數組的共享特性)
? 隱式轉換規則(避免用 ==)
面試時不僅要答對,更要解釋背后的原理(如閉包的內存管理、this 的動態綁定),展現深度思考能力。
——
以下是前端面試中 JavaScript 基礎易錯問題的深度解析,結合高頻考點和開發者常見誤區,從原理到解決方案進行系統梳理:
?? 一、作用域與變量聲明
- 變量提升與暫時性死區
? var 聲明的變量會提升至作用域頂部(值為 undefined),而 let/const 雖提升但存在暫時性死區(聲明前訪問報錯)。
? 典型陷阱:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 輸出 3 個 3(共享同一作用域)
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 輸出 0,1,2(塊級作用域隔離)1
}
解決:優先使用 let 或通過 IIFE 創建獨立作用域。
- 全局變量污染
未聲明直接賦值(如 x = 10)會創建全局變量,嚴格模式(“use strict”)可避免此問題。
🔄 二、閉包核心原理與陷阱
- 閉包的本質
內部函數持有外部函數作用域的引用,即使外部函數已執行完畢:
function createCounter() {
let count = 0; // 被閉包引用的變量
return () => count++;
}
const counter = createCounter();
counter(); // 1(count 未被回收)6,7
- 循環中的閉包陷阱
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i)); // 輸出 5 個 5
}
原因:所有回調共享同一變量 i,循環結束后 i 值為 5。
解決:
? 使用 let 聲明 i(塊級作用域)
? 或通過 IIFE 捕獲瞬時值:
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => console.log(j)); // 輸出 0,1,2,3,4
})(i);
}
- 內存泄漏風險
閉包引用大對象時可能阻止垃圾回收:
function createHeavyClosure() {
const bigData = new Array(1000000);
return () => console.log(bigData[0]);
}
const leakyFunc = createHeavyClosure();
// 不再需要時手動解除引用:leakyFunc = null6
? 三、異步與事件循環
- setTimeout 的延遲問題
? 延遲參數為 0 仍屬異步任務,需等待同步代碼執行完畢:
setTimeout(() => console.log(“Timeout”), 0);
console.log(“Sync”); // 先輸出 “Sync”,再輸出 “Timeout”
? 事件隊列阻塞:長時間同步任務(如循環)會延遲異步回調執行,導致事件響應滯后。
- Promise 錯誤處理遺漏
未捕獲的 Promise 錯誤會觸發 unhandledrejection 事件:
fetch(url).then(res => res.json()); // 缺少 .catch()
// 正確做法:
fetch(url)
.then(res => res.json())
.catch(err => console.error(“請求失敗:”, err)); // 顯式捕獲3
🔍 四、this 動態綁定
this 值由調用方式決定:
const obj = {
name: “A”,
log() {
console.log(this.name); // “A”(obj 調用)
const inner = () => console.log(this.name); // “A”(箭頭函數繼承外層 this)
inner();
}
};
const fn = obj.log;
fn(); // undefined(默認綁定到全局)4,5
關鍵規則:
? 普通函數調用:this 指向全局對象(非嚴格模式)
? 方法調用:this 指向調用對象
? 箭頭函數:繼承定義時的外層 this
解決:使用 bind 顯式綁定或箭頭函數。
🔄 五、類型轉換與比較
- == 的隱式轉換陷阱
[] == ![] // true(![] → false → 0, [] → “” → 0)
0 == “0” // true(字符串轉數字)
null == undefined // true(特殊規則)3,5
建議:始終使用 === 避免意外轉換。
- typeof 與 null 的遺留問題
typeof null === “object”(歷史 Bug),判斷數組用 Array.isArray()。
?? 六、其他高頻易錯點
- 深淺拷貝混淆
? 直接賦值是淺拷貝(共享引用):
const a = [1, 2];
const b = a;
b.push(3); // a 變為 [1,2,3]2,5
? 深拷貝方案:JSON.parse(JSON.stringify(obj))(忽略函數/Symbol)或遞歸克隆。
- 浮點數精度問題
0.1 + 0.2 !== 0.3(IEEE 754 雙精度浮點限制),需用 toFixed(2) 處理顯示。
- 數組遍歷的坑
for…in 會遍歷原型鏈屬性且不保證順序,應使用 for 循環或 forEach:
Array.prototype.foo = 1;
const arr = [2, 3];
for (let i in arr) console.log(i); // 輸出 0, 1, “foo”(污染!)
💎 總結與應對策略
? 作用域與閉包:理解詞法作用域鏈,避免循環引用泄漏(及時解除引用)
? 異步機制:掌握事件循環(宏任務/微任務),Promise 錯誤必捕獲
? 類型安全:禁用 ==,善用可選鏈(?.)與空值合并(??)
? 代碼健壯性:使用 ESLint 靜態檢查,單元測試覆蓋邊界條件(如 null、空數組)
更多實戰案例可參考:閉包深度解析(http://www.ppmy.cn/news/1669535.html) | 異步錯誤處理指南(https://www.toutiao.com/article/7486748618485957171/) | this 綁定剖析(https://blog.csdn.net/weixin_42429220/article/details/136002209)