詳解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 definedES6以后
在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è)诼暶髦笆褂米兞浚切枰⒁庖稽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作用域與變量提升是編寫(xiě)高質(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)文章
JavaScript?Generator異步過(guò)度的實(shí)現(xiàn)詳解
生成器Generator是JavaScript?ES6引入的特性,它讓我們可以分段執(zhí)行一個(gè)函數(shù)。但是在談?wù)撋善鳎℅enerator)之前,我們要先了解迭代器Iterator2022-08-08
JS實(shí)現(xiàn)勻加速與勻減速運(yùn)動(dòng)的方法示例
這篇文章主要介紹了JS實(shí)現(xiàn)勻加速與勻減速運(yùn)動(dòng)的方法,涉及javascript結(jié)合時(shí)間函數(shù)與數(shù)學(xué)運(yùn)算動(dòng)態(tài)操作頁(yè)面元素樣式的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-09-09
js獲取當(dāng)前年月日詳細(xì)教程(看這一篇就夠了)
這篇文章主要給大家介紹了關(guān)于js獲取當(dāng)前年月日的相關(guān)資料,JavaScript內(nèi)置的Date對(duì)象是獲取當(dāng)前日期最常用的工具之一,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
終于解決了IE8不支持?jǐn)?shù)組的indexOf方法
今天,測(cè)試報(bào)過(guò)來(lái)一個(gè)js bug, 在IE8下有個(gè)js錯(cuò)誤,但是在其它瀏覽器下(Firefox, Chrome, IE9)下面都很正常。后來(lái)調(diào)試發(fā)現(xiàn)原因是在IE8下,js數(shù)組沒(méi)有indexOf方法。2013-04-04
Bootstrap 表單驗(yàn)證formValidation 實(shí)現(xiàn)表單動(dòng)態(tài)驗(yàn)證功能
這篇文章主要介紹了Bootstrap 表單驗(yàn)證formValidation 實(shí)現(xiàn)表單動(dòng)態(tài)驗(yàn)證功能,需要的朋友可以參考下2017-05-05

