戰(zhàn)解析)
1. 作用域變量存取的規(guī)則手冊(cè)在JavaScript中作用域就像一套嚴(yán)格的訪問(wèn)控制規(guī)則決定了變量在哪里可以被訪問(wèn)在哪里會(huì)被隱藏。理解作用域是避免變量沖突和內(nèi)存泄漏的基礎(chǔ)。1.1 全局作用域與局部作用域全局作用域就像公共場(chǎng)所任何代碼都可以訪問(wèn)這里的變量。在瀏覽器環(huán)境中全局作用域是window對(duì)象var globalVar 我在全局; console.log(window.globalVar); // 輸出我在全局函數(shù)作用域則是私人空間只有函數(shù)內(nèi)部才能訪問(wèn)function testScope() { var localVar 我是局部變量; console.log(localVar); // 正常輸出 } console.log(localVar); // ReferenceError: localVar is not defined1.2 let/const帶來(lái)的塊級(jí)作用域ES6引入的let和const帶來(lái)了塊級(jí)作用域由{}界定這是與var最顯著的區(qū)別if (true) { var varVariable var聲明; let letVariable let聲明; } console.log(varVariable); // 輸出var聲明 console.log(letVariable); // ReferenceError塊級(jí)作用域在循環(huán)中特別有用能解決經(jīng)典的閉包陷阱// 使用var的問(wèn)題 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)訪問(wèn)一個(gè)變量時(shí)JavaScript會(huì)沿著作用域鏈向上查找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)前作用域 → 父作用域 → ... → 全局作用域。如果到全局作用域還沒找到就會(huì)拋出ReferenceError。2. 閉包跨越作用域的魔法橋梁閉包是JavaScript中最強(qiáng)大的特性之一也是面試中最常被深挖的話題。簡(jiǎn)單說(shuō)閉包讓函數(shù)可以記住并訪問(wèn)其詞法作用域即使函數(shù)在其作用域外執(zhí)行。2.1 閉包的形成條件一個(gè)典型的閉包需要三個(gè)要素外層函數(shù)包裹內(nèi)層函數(shù)內(nèi)層函數(shù)引用外層函數(shù)的變量?jī)?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)用場(chǎ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)存管理閉包會(huì)導(dǎo)致外層函數(shù)的變量無(wú)法被垃圾回收可能引發(fā)內(nèi)存泄漏。常見的內(nèi)存泄漏場(chǎng)景// 意外的全局變量 function leak1() { leakedVar 我泄漏了; // 忘記聲明變成全局變量 } // 定時(shí)器未清理 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不會(huì)被回收 }解決方案使用嚴(yán)格模式避免意外全局變量清除不再需要的定時(shí)器、事件監(jiān)聽器對(duì)于大型數(shù)據(jù)使用后手動(dòng)解除引用3. 立即執(zhí)行函數(shù)(IIFE)創(chuàng)建獨(dú)立作用域的利器立即執(zhí)行函數(shù)表達(dá)式(Immediately Invoked Function Expression)是定義后立即執(zhí)行的函數(shù)主要用于創(chuàng)建獨(dú)立作用域。3.1 IIFE的基本語(yǔ)法兩種常見寫法// 寫法1括號(hào)包裹整個(gè)函數(shù) (function() { console.log(IIFE執(zhí)行); })(); // 寫法2括號(hào)包裹函數(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模塊和塊級(jí)作用域的普及IIFE的使用減少了// 使用塊級(jí)作用域替代 { 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會(huì)提升到函數(shù)頂部相當(dāng)于function foo() { var a; console.log(a); a 2; }題目2let和var的區(qū)別關(guān)鍵區(qū)別var有函數(shù)作用域let有塊級(jí)作用域var會(huì)變量提升let存在暫時(shí)性死區(qū)(TDZ)var可以重復(fù)聲明let不可以var在全局聲明會(huì)成為window屬性let不會(huì)4.2 閉包相關(guān)題目題目1實(shí)現(xiàn)一個(gè)計(jì)數(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í)行時(shí)循環(huán)已結(jié)束i的值為3。解決方案使用IIFE捕獲i使用let替代var使用setTimeout的第三個(gè)參數(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)一個(gè)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); // 無(wú)輸出5. 實(shí)戰(zhàn)中的經(jīng)驗(yàn)與陷阱5.1 作用域相關(guān)的常見錯(cuò)誤意外全局變量忘記聲明變量導(dǎo)致污染全局function oops() { accidentalGlobal 糟糕; // 應(yīng)該是let/const/var }提示始終使用嚴(yán)格模式(use strict)可以避免這種錯(cuò)誤循環(huán)中的閉包陷阱使用var在循環(huán)中創(chuàng)建閉包for (var i 0; i 5; i) { setTimeout(() console.log(i), 100); // 輸出5個(gè)5 }解決方案使用let替代var使用IIFE捕獲i使用Array.prototype.forEach5.2 閉包性能優(yōu)化技巧減少閉包捕獲的變量數(shù)量只保留必要的變量// 不推薦捕獲了整個(gè)data對(duì)象 function process() { const data getLargeData(); return function() { console.log(data.id); }; } // 推薦只捕獲需要的屬性 function process() { const data getLargeData(); const { id } data; return function() { console.log(id); }; }及時(shí)釋放閉包引用function setup() { const data getLargeData(); const listener () console.log(data); element.addEventListener(click, listener); // 不再需要時(shí)移除 return () element.removeEventListener(click, listener); }5.3 IIFE的現(xiàn)代替代方案使用塊級(jí)作用域?qū)τ诤?jiǎn)單的隔離需求{ let temp 臨時(shí)變量; // 這里使用temp } // temp在這里不可訪問(wèn)使用ES模塊對(duì)于代碼組織// module.js let privateVar 私有; export function publicFn() { /*...*/ } // main.js import { publicFn } from ./module.js;使用類私有字段對(duì)于面向?qū)ο髨?chǎng)景class Counter { #count 0; // 私有字段 increment() { this.#count; } }