詳解JavaScript中的作用域
什么是作用域
JS中的作用域是一個(gè)存儲(chǔ)變量、函數(shù)以及對(duì)象的位置,每個(gè)變量、函數(shù)和對(duì)象都被存儲(chǔ)在一個(gè)特定的作用域中,它是指變量和函數(shù)在代碼中的可訪問(wèn)范圍,作用域決定了代碼中哪些部分可以訪問(wèn)特定的變量和函數(shù),通過(guò)作用域,我們可以將變量和函數(shù)封裝在不同的作用域中,使其在合適的范圍內(nèi)可訪問(wèn)
就像上圖中的示例,F(xiàn)oo1無(wú)法獲取Foo2中的var4變量
作用域類型
常見(jiàn)的作用域類型包括全局作用域(Global Scope)、局部作用域(Local Scope)和塊級(jí)作用域(Block Scope),JS也不例外
全局作用域
全局作用域是在整個(gè)代碼中都可訪問(wèn)的作用域。在瀏覽器環(huán)境中,全局作用域通常是指window對(duì)象;在node環(huán)境下,則是globalThis或global
在全局作用域中聲明的變量或函數(shù)是全局變量或全局函數(shù),在代碼任何地方都可以訪問(wèn)和使用
比如上面圖示中的Foo4可以訪問(wèn)到全局作用域的變量及函數(shù)
const var1 = "var1"; const var2 = "var2"; function Foo1() { const var3 = "var3"; } function Foo2() { const var4 = "var4"; function Foo3() {} } function Foo4() { console.log(var1, var2, Foo1, Foo2); // var1 var2 [Function: Foo1] [Function: Foo2] } Foo4();
局部作用域
JS中的局部作用域一般代指函數(shù)作用域(Function Scope),它是在函數(shù)內(nèi)部聲明的作用域,函數(shù)內(nèi)部的變量和函數(shù)只能在函數(shù)內(nèi)部訪問(wèn),外部無(wú)法直接訪問(wèn)
就像上述Foo3的效果,可以訪問(wèn)到全局以及當(dāng)前所在函數(shù)的作用域的變量及函數(shù)
const var1 = "var1"; const var2 = "var2"; function Foo1() { const var3 = "var3"; } function Foo2() { const var4 = "var4"; function Foo3() { console.log(var4, Foo3); // var4 [Function: Foo3] } Foo3(); } Foo2();
塊級(jí)作用域
塊級(jí)作用域是在代碼塊(通常是由大括號(hào){}包裹起來(lái)的部分)內(nèi)聲明的作用域。比如if(){...}、for(...){...}、try{...}等
ES6之前
在ES6之前,由于變量都是使用var聲明的,所以沒(méi)有塊級(jí)作用域此類概念,只有全局作用域和函數(shù)(局部)作用域。那么需要模擬塊級(jí)作用域如何怎么操作呢?
答案是使用立即執(zhí)行函數(shù)表達(dá)式(IIFE):通過(guò)將代碼包裝在匿名函數(shù)中并立即執(zhí)行該函數(shù),可以創(chuàng)建一個(gè)獨(dú)立的作用域,使得內(nèi)部聲明的變量在函數(shù)外部不可訪問(wèn)
(function () { var var5 = "var5"; console.log(var5); // var5 })(); console.log(var5); // var5 is not defined
ES6以后
在es6以后,官方推出了塊級(jí)作用域的概念,使用let和const關(guān)鍵字聲明的變量具有塊級(jí)作用域,它們只能在聲明的代碼塊內(nèi)部訪問(wèn)。
if (true) { let var5 = "var5"; console.log(var5); // var5 } console.log(var5); // var5 is not defined
作用域鏈
作用域鏈(Scope Chain)是JS用于解析標(biāo)識(shí)符(變量和函數(shù))的機(jī)制,它是由多個(gè)嵌套的作用域組成的,它決定了變量和函數(shù)的查找順序。
上面我們說(shuō)到局部作用域時(shí)談到的Foo3可以訪問(wèn)全局以及當(dāng)前函數(shù)作用域中的標(biāo)識(shí)符這個(gè)特點(diǎn)就歸功于作用域鏈這個(gè)概念。當(dāng)訪問(wèn)一個(gè)變量時(shí),JS引擎會(huì)先從當(dāng)前作用域開(kāi)始查找,如果找不到這個(gè)名稱的標(biāo)識(shí)符則繼續(xù)向上一級(jí)作用域查找,直到找到變量或達(dá)到全局作用域?yàn)橹?,如果在全局作用域中仍然找不到,則認(rèn)為該標(biāo)識(shí)符未定義。
變量提升
變量提升的概念出現(xiàn)在面試題中的頻率十分高,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)也是不可忽略的重要知識(shí)點(diǎn);
基礎(chǔ)概念
變量提升是JS在代碼執(zhí)行前將變量和函數(shù)聲明提升到作用域頂部的行為,它由JavaScript引擎在代碼執(zhí)行前的編譯階段處理。變量提升影響了整個(gè)作用域范圍內(nèi)的代碼,它允許我們?cè)诼暶髦笆褂米兞?,但是需要注意一點(diǎn):只有變量聲明被提升,賦值不會(huì)提升。了解了上述概念后,我們思考下面的代碼:
console.log(var6); // undefined var var6 = 10;
上面的代碼相當(dāng)于
var var6; console.log(var6); // undefined var6 = 10;
優(yōu)先級(jí)問(wèn)題
當(dāng)同一個(gè)作用域中同時(shí)出現(xiàn)同名的函數(shù)和變量時(shí),函數(shù)提升的優(yōu)先級(jí)更高,也就是說(shuō)函數(shù)會(huì)在變量之上聲明,參考下面的代碼
var a = 10; function a() {} console.log(a); // 10
可以看成是
var a; // 函數(shù)a var a; // 變量a a = function () {};// 使用function聲明函數(shù)可以看成是聲明+賦值 a = 10; console.log(a); // 10
何以見(jiàn)得?思考以下代碼
function a() {} var a; console.log(a); // [Function: a]
當(dāng)把a(bǔ)的賦值去除時(shí),函數(shù)的賦值順序就可以得到驗(yàn)證,相當(dāng)于:
var a; // 函數(shù)a var a; // 變量a a = function () {}; console.log(a); // [Function: a]
閉包
定義
當(dāng)函數(shù)開(kāi)始執(zhí)行時(shí),函數(shù)中的變量以及函數(shù)會(huì)壓入棧中,那么此時(shí)如果當(dāng)前的作用域中有另一個(gè)函數(shù)正在使用該作用域的變量,該變量占用的內(nèi)存也不會(huì)被垃圾回收機(jī)制回收,這個(gè)現(xiàn)象就是閉包
換句話說(shuō),閉包是指函數(shù)能夠"記住"并訪問(wèn)其創(chuàng)建時(shí)的詞法環(huán)境,在函數(shù)定義的詞法作用域之外執(zhí)行同樣適用
思考以下代碼
const foo = (function iife() { const num = 10; function foo() { return num; } return foo; })(); console.log(foo()); // 10
上述代碼中使用立即執(zhí)行函數(shù)iife作為外部函數(shù)的作用域,它返回內(nèi)部函數(shù)foo,而foo函數(shù)使用了iife函數(shù)中的num變量,形成了閉包,最后在iife函數(shù)的外部使用foo時(shí)依然可以訪問(wèn)num變量
特點(diǎn)
- 即使外部函數(shù)已經(jīng)執(zhí)行完畢,內(nèi)部函數(shù)依然可以訪問(wèn)外部函數(shù)作用域中的變量(當(dāng)棧將函數(shù)彈出時(shí),變量依然處于內(nèi)存中)
- 閉包可以持有對(duì)外部變量的引用,使得外部變量的值在內(nèi)部函數(shù)中保持活動(dòng)狀態(tài)(不被垃圾回收機(jī)制回收)
- 閉包中的內(nèi)部函數(shù)可以修改并更新外部變量的值
- 閉包的函數(shù)可以獲取到創(chuàng)建時(shí)的整個(gè)作用域鏈的標(biāo)識(shí)符
- 閉包可能會(huì)導(dǎo)致內(nèi)存泄漏,被閉包引用的變量無(wú)法被垃圾回收機(jī)制處理
使用場(chǎng)景
封裝私有變量
JS中沒(méi)有TS的private關(guān)鍵詞,無(wú)法直接定義私有變量,但是可以通過(guò)閉包產(chǎn)生私有環(huán)境作用域(ES2022后引入了#關(guān)鍵字,用于定義私有變量,相比于使用閉包,更直觀和方便)
const Animal = (function () { const name = "dog"; function Animal() {} Animal.prototype.getName = function () { return name; }; return Animal; })(); console.log(new Animal().getName()); // dog
延長(zhǎng)變量周期
延長(zhǎng)變量的生命周期也是閉包的特性之一,該效果通過(guò)內(nèi)部函數(shù)對(duì)外部作用域的可訪問(wèn)性實(shí)現(xiàn)
function delayMessage(msg) { return function () { return msg; }; } const msg = delayMessage("msg"); console.log(msg()); // msg
模塊化、命名空間
我們使用JS中的立即執(zhí)行函數(shù)實(shí)現(xiàn)了命名空間功能,達(dá)到模塊化的效果
var moduleA = (function () { var privateVariable = "Hello"; // 私有函數(shù) function privateMethod() { console.log(privateVariable); } return { // 公共函數(shù) publicMethod: function () { privateMethod(); }, }; })(); moduleA.publicMethod(); // Hello console.log(moduleA.privateVariable); // undefined
緩存
閉包還可以用于創(chuàng)建緩存函數(shù),以提高函數(shù)的執(zhí)行效率。緩存函數(shù)可以將輸入?yún)?shù)與其對(duì)應(yīng)的結(jié)果保存在內(nèi)部,避免重復(fù)計(jì)算
function createCache() { var cache = {}; return function (key, val) { if (!cache[key]) { cache[key] = val; console.log("保存"); } return cache[key]; }; } var cacheModule = createCache(); cacheModule("num1", "123"); // 保存 cacheModule("num2", "456"); // 保存 cacheModule("num1", "123"); cacheModule("num2", "456");
ES6的作用域
在ES6以后引入了const、let和箭頭函數(shù),這三者對(duì)作用域分別有什么影響呢?
const、let
塊級(jí)作用域
上面說(shuō)到const、let的出現(xiàn)奠定了JS中的塊級(jí)作用域的概念,使if、for等語(yǔ)句中也存在作用域這一功能;然而不僅僅是條件語(yǔ)句、循環(huán)語(yǔ)句,使用{...}定義的范圍都是塊級(jí)作用域,這意味著在塊級(jí)作用域內(nèi)部聲明的變量只在該作用域內(nèi)有效,并且在作用域外部無(wú)法訪問(wèn)
{ let msg = "hello"; console.log(msg); // hello } console.log(msg); // msg is not defined
變量提升
使用let和const聲明的變量不會(huì)被提升到作用域的頂部,它們只能在聲明后才能被訪問(wèn);這點(diǎn)與var不太一樣
暫時(shí)性死區(qū)
暫時(shí)性死區(qū)指的是在變量聲明前訪問(wèn)變量會(huì)拋出錯(cuò)誤。只有在變量聲明語(yǔ)句執(zhí)行完成之后,變量才會(huì)進(jìn)入有效狀態(tài),才能被訪問(wèn)和使用。這點(diǎn)效果與上面的變量提升效果一樣
不可重復(fù)聲明
在同一個(gè)作用域中不能被重復(fù)聲明,否則會(huì)報(bào)錯(cuò);而使用var定義變量時(shí)后聲明的會(huì)覆蓋先聲明的
這三個(gè)特點(diǎn)可以參考下面的代碼:
console.log(var1); // 在賦值前使用了變量“var1” const var1 = 11; let var2 = 22; // 無(wú)法重新聲明塊范圍變量“var2” let var2 = 22;
箭頭函數(shù)
箭頭函數(shù)在JavaScript中仍然與普通函數(shù)一樣有函數(shù)作用域的概念
題外話
動(dòng)態(tài)作用域與詞法作用域
作用域的種類有兩種:分別是動(dòng)態(tài)作用域和詞法作用域(靜態(tài)作用域)。上述我們介紹的是詞法作用域,什么是詞法作用域?
詞法作用域
詞法作用域是基于代碼的靜態(tài)結(jié)構(gòu)來(lái)確定變量的訪問(wèn)規(guī)則。也就是說(shuō)它由變量和函數(shù)在代碼中的聲明位置而不是調(diào)用的位置來(lái)確定,思考下面的代碼
function foo1() { var var1 = 10; // foo2可訪問(wèn)的作用域 // 聲明foo2的作用域 return function foo2() { console.log(var1);// 10 }; } var foo2 = foo1(); function foo3() { var var1 = 20; // foo2不可訪問(wèn)的作用域 // 執(zhí)行foo2的作用域 foo2(); } foo3();
在foo1中聲明了函數(shù)foo2,在foo3中執(zhí)行foo2,可以看到,foo2取的var1是聲明foo2的作用域中的變量(10)。這個(gè)現(xiàn)象說(shuō)明JS采用的是詞法作用域。
動(dòng)態(tài)作用域
那么反之,如果還是上述代碼,foo2取的var1是執(zhí)行foo2的作用域中的變量(20),就說(shuō)明語(yǔ)言采用的是動(dòng)態(tài)作用域
總結(jié)
JavaScript作用域與變量提升是編寫高質(zhì)量代碼所必須掌握的重要概念。本文介紹了作用域的定義、作用和目的,以及JavaScript中的不同作用域類型,包括全局作用域、函數(shù)作用域和塊級(jí)作用域。我們還討論了變量提升的概念、原理和影響范圍,以及作用域鏈和閉包的關(guān)系。此外,還探討了ES6的作用域,并對(duì)比了其與早期作用域的區(qū)別。最后針對(duì)動(dòng)態(tài)作用域與詞法作用域作了一個(gè)簡(jiǎn)單的說(shuō)明。
以上就是詳解JavaScript中的作用域的詳細(xì)內(nèi)容,更多關(guān)于JavaScript作用域的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
自己編寫的支持Ajax驗(yàn)證的JS表單驗(yàn)證插件
創(chuàng)建一個(gè)JavaScript表單驗(yàn)證插件,可以說(shuō)是一個(gè)繁瑣的過(guò)程,涉及到初期設(shè)計(jì)、開(kāi)發(fā)與測(cè)試等等環(huán)節(jié)。實(shí)際上一個(gè)優(yōu)秀的程序員不僅是技術(shù)高手,也應(yīng)該是善假于外物的。本文介紹的這個(gè)不錯(cuò)的JavaScript表單驗(yàn)證插件,支持ajax驗(yàn)證,有需要的小伙伴可以參考下2015-05-05前端高頻面試題之JS中堆和棧的區(qū)別和瀏覽器的垃圾回收機(jī)制
本文給大家分享前端高頻面試題JS中堆和棧的區(qū)別和瀏覽器的垃圾回收機(jī)制,本文分文別類給大家介紹了棧(stack)和堆(heap)的區(qū)別基本類型和引用類型的相關(guān)知識(shí),瀏覽器垃圾回收機(jī)制包括基本概念給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-10-10javascript高仿熱血傳奇游戲?qū)崿F(xiàn)代碼
這篇文章主要介紹了javascript高仿熱血傳奇游戲的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-02-02JavaScript實(shí)現(xiàn)簡(jiǎn)單驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)單驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08javascript 判斷頁(yè)面訪問(wèn)方式電腦或者移動(dòng)端
這篇文章主要介紹了 判斷頁(yè)面訪問(wèn)方式電腦或者移動(dòng)端的相關(guān)資料,這里提供了三種方法,需要的朋友可以參考下2016-09-09