戰(zhàn)解析)
1. 作用域變量存取的規(guī)則手冊在JavaScript中作用域就像一套嚴(yán)格的訪問控制規(guī)則決定了變量在哪里可以被訪問在哪里會被隱藏。理解作用域是避免變量沖突和內(nèi)存泄漏的基礎(chǔ)。1.1 全局作用域與局部作用域全局作用域就像公共場所任何代碼都可以訪問這里的變量。在瀏覽器環(huán)境中全局作用域是window對象var globalVar 我在全局; console.log(window.globalVar); // 輸出我在全局函數(shù)作用域則是私人空間只有函數(shù)內(nèi)部才能訪問function testScope() { var localVar 我是局部變量; console.log(localVar); // 正常輸出 } console.log(localVar); // ReferenceError: localVar is not defined1.2 let/const帶來的塊級作用域ES6引入的let和const帶來了塊級作用域由{}界定這是與var最顯著的區(qū)別if (true) { var varVariable var聲明; let letVariable let聲明; } console.log(varVariable); // 輸出var聲明 console.log(letVariable); // ReferenceError塊級作用域在循環(huán)中特別有用能解決經(jīng)典的閉包陷阱// 使用var的問題 for (var i 0; i 3; i) { setTimeout(() console.log(i), 100); // 輸出3,3,3 } // 使用let的解決方案 for (let j 0; j 3; j) { setTimeout(() console.log(j), 100); // 輸出0,1,2 }1.3 作用域鏈的查找機(jī)制當(dāng)訪問一個變量時JavaScript會沿著作用域鏈向上查找let outer 外層; function parent() { let middle 中間; function child() { let inner 內(nèi)層; console.log(inner); // 內(nèi)層 console.log(middle); // 中間 console.log(outer); // 外層 console.log(global); // ReferenceError } child(); } parent();查找順序是當(dāng)前作用域 → 父作用域 → ... → 全局作用域。如果到全局作用域還沒找到就會拋出ReferenceError。2. 閉包跨越作用域的魔法橋梁閉包是JavaScript中最強(qiáng)大的特性之一也是面試中最常被深挖的話題。簡單說閉包讓函數(shù)可以記住并訪問其詞法作用域即使函數(shù)在其作用域外執(zhí)行。2.1 閉包的形成條件一個典型的閉包需要三個要素外層函數(shù)包裹內(nèi)層函數(shù)內(nèi)層函數(shù)引用外層函數(shù)的變量內(nèi)層函數(shù)被外部引用function createCounter() { let count 0; // 外層變量 return function() { return count; // 內(nèi)層函數(shù)引用外層變量 }; } const counter createCounter(); // 外部引用 console.log(counter()); // 1 console.log(counter()); // 22.2 閉包的實(shí)際應(yīng)用場景2.2.1 數(shù)據(jù)封裝與私有變量在沒有class私有字段(#)的年代閉包是實(shí)現(xiàn)私有變量的主要方式function createPerson(name) { let _age 0; // 私有變量 return { getName: () name, getAge: () _age, birthday: () { _age; } }; } const person createPerson(Alice); console.log(person._age); // undefined person.birthday(); console.log(person.getAge()); // 12.2.2 函數(shù)工廠與柯里化閉包可以實(shí)現(xiàn)函數(shù)工廠和參數(shù)預(yù)置柯里化function multiplyFactory(x) { return function(y) { return x * y; }; } const double multiplyFactory(2); const triple multiplyFactory(3); console.log(double(5)); // 10 console.log(triple(5)); // 152.2.3 事件處理與回調(diào)在事件處理中閉包可以保存狀態(tài)function setupButtons() { const buttons document.querySelectorAll(button); for (let i 0; i buttons.length; i) { buttons[i].addEventListener(click, function() { console.log(按鈕${i}被點(diǎn)擊); }); } }2.3 閉包的內(nèi)存管理閉包會導(dǎo)致外層函數(shù)的變量無法被垃圾回收可能引發(fā)內(nèi)存泄漏。常見的內(nèi)存泄漏場景// 意外的全局變量 function leak1() { leakedVar 我泄漏了; // 忘記聲明變成全局變量 } // 定時器未清理 function leak2() { const data getHugeData(); setInterval(() { console.log(data.length); }, 1000); } // DOM引用未清除 function leak3() { const element document.getElementById(big-element); element.addEventListener(click, () { console.log(點(diǎn)擊); }); // 即使移除DOM由于事件監(jiān)聽器引用element不會被回收 }解決方案使用嚴(yán)格模式避免意外全局變量清除不再需要的定時器、事件監(jiān)聽器對于大型數(shù)據(jù)使用后手動解除引用3. 立即執(zhí)行函數(shù)(IIFE)創(chuàng)建獨(dú)立作用域的利器立即執(zhí)行函數(shù)表達(dá)式(Immediately Invoked Function Expression)是定義后立即執(zhí)行的函數(shù)主要用于創(chuàng)建獨(dú)立作用域。3.1 IIFE的基本語法兩種常見寫法// 寫法1括號包裹整個函數(shù) (function() { console.log(IIFE執(zhí)行); })(); // 寫法2括號包裹函數(shù)體 (function() { console.log(IIFE執(zhí)行); }());3.2 IIFE的實(shí)際應(yīng)用3.2.1 避免變量污染全局在模塊化之前IIFE是隔離作用域的主要方式// 全局命名空間污染 var count 0; function add() { count; } // 使用IIFE隔離 (function() { var count 0; function add() { count; } window.myModule { add }; })(); console.log(count); // 0不受IIFE內(nèi)部影響3.2.2 捕獲瞬間狀態(tài)在循環(huán)中IIFE可以捕獲每次迭代的狀態(tài)for (var i 0; i 3; i) { (function(currentI) { setTimeout(function() { console.log(currentI); }, 100); })(i); } // 輸出0,1,23.2.3 模塊化開發(fā)的基礎(chǔ)早期模塊化方案如UMD基于IIFEvar MyModule (function() { var privateVar 私有; function privateMethod() { /*...*/ } return { publicMethod: function() { privateMethod(); console.log(privateVar); } }; })();3.3 現(xiàn)代替代方案隨著ES6模塊和塊級作用域的普及IIFE的使用減少了// 使用塊級作用域替代 { let count 0; const add () count; window.myModule { add }; } // 使用模塊化 // module.js let count 0; export function add() { count; } // main.js import { add } from ./module.js;4. 高頻面試題深度解析4.1 作用域相關(guān)題目題目1以下代碼輸出什么var a 1; function foo() { console.log(a); var a 2; } foo();解析輸出undefined。這是因?yàn)樽兞刻嵘?hoisting)函數(shù)內(nèi)的var a會提升到函數(shù)頂部相當(dāng)于function foo() { var a; console.log(a); a 2; }題目2let和var的區(qū)別關(guān)鍵區(qū)別var有函數(shù)作用域let有塊級作用域var會變量提升let存在暫時性死區(qū)(TDZ)var可以重復(fù)聲明let不可以var在全局聲明會成為window屬性let不會4.2 閉包相關(guān)題目題目1實(shí)現(xiàn)一個計數(shù)器工廠function createCounter() { let count 0; return { increment: () count, decrement: () --count, getCount: () count }; } const counter createCounter();題目2以下代碼的輸出順序是什么for (var i 0; i 3; i) { setTimeout(function() { console.log(i); }, 100); }輸出3,3,3。因?yàn)閟etTimeout回調(diào)執(zhí)行時循環(huán)已結(jié)束i的值為3。解決方案使用IIFE捕獲i使用let替代var使用setTimeout的第三個參數(shù)4.3 IIFE相關(guān)題目題目1以下IIFE的輸出是什么var result (function(x) { return x * 2; })(5); console.log(result); // 10題目2如何用IIFE實(shí)現(xiàn)模塊模式var calculator (function() { let memory 0; function add(x) { memory x; } function get() { return memory; } return { add, get }; })();4.4 綜合應(yīng)用題題目實(shí)現(xiàn)一個once函數(shù)確保傳入函數(shù)只執(zhí)行一次function once(fn) { let called false; let result; return function(...args) { if (!called) { called true; result fn.apply(this, args); } return result; }; } const logOnce once(console.log); logOnce(hello); // 輸出hello logOnce(world); // 無輸出5. 實(shí)戰(zhàn)中的經(jīng)驗(yàn)與陷阱5.1 作用域相關(guān)的常見錯誤意外全局變量忘記聲明變量導(dǎo)致污染全局function oops() { accidentalGlobal 糟糕; // 應(yīng)該是let/const/var }提示始終使用嚴(yán)格模式(use strict)可以避免這種錯誤循環(huán)中的閉包陷阱使用var在循環(huán)中創(chuàng)建閉包for (var i 0; i 5; i) { setTimeout(() console.log(i), 100); // 輸出5個5 }解決方案使用let替代var使用IIFE捕獲i使用Array.prototype.forEach5.2 閉包性能優(yōu)化技巧減少閉包捕獲的變量數(shù)量只保留必要的變量// 不推薦捕獲了整個data對象 function process() { const data getLargeData(); return function() { console.log(data.id); }; } // 推薦只捕獲需要的屬性 function process() { const data getLargeData(); const { id } data; return function() { console.log(id); }; }及時釋放閉包引用function setup() { const data getLargeData(); const listener () console.log(data); element.addEventListener(click, listener); // 不再需要時移除 return () element.removeEventListener(click, listener); }5.3 IIFE的現(xiàn)代替代方案使用塊級作用域?qū)τ诤唵蔚母綦x需求{ let temp 臨時變量; // 這里使用temp } // temp在這里不可訪問使用ES模塊對于代碼組織// module.js let privateVar 私有; export function publicFn() { /*...*/ } // main.js import { publicFn } from ./module.js;使用類私有字段對于面向?qū)ο髨鼍癱lass Counter { #count 0; // 私有字段 increment() { this.#count; } }