目錄
前言
什么是作用域
作用域類型
全局作用域
局部作用域
塊級作用域
ES6之前
ES6以后
作用域鏈
變量提升
基礎概念
優(yōu)先級問題
閉包
定義
特點
使用場景
封裝私有變量
延長變量周期
模塊化、命名空間
緩存
ES6的作用域
const、let
塊級作用域
變量提升
暫時性死區(qū)
不可重復聲明
箭頭函數(shù)
題外話
動態(tài)作用域與詞法作用域
詞法作用域
動態(tài)作用域
總結
前言
作用域是JavaScript中一個重要的概念,它決定了變量和函數(shù)在代碼中的可訪問性和可見性。了解JavaScript的作用域對于編寫高效、可維護的代碼至關重要。本文將深入介紹JavaScript作用域相關的知識點,其中包括作用域類型,作用域鏈,變量提升以及閉包等。
什么是作用域
在之前的文章中我們對TS中的模塊進行了詳細的分享,TS的在防止命名污染,沖突時采用的是命名空間,當我們看過TS命名空間的編譯產(chǎn)物后我們就會知道,實際上其實際做法是采用JS的作用域實現(xiàn)一個獨立的區(qū)域達到解決命名沖突和變量訪問的可見性作用
JS中的作用域是一個存儲變量、函數(shù)以及對象的位置,每個變量、函數(shù)和對象都被存儲在一個特定的作用域中,它是指變量和函數(shù)在代碼中的可訪問范圍,作用域決定了代碼中哪些部分可以訪問特定的變量和函數(shù),通過作用域,我們可以將變量和函數(shù)封裝在不同的作用域中,使其在合適的范圍內(nèi)可訪問
就像上圖中的示例,F(xiàn)oo1無法獲取Foo2中的var4變量
作用域類型
常見的作用域類型包括全局作用域(Global Scope)、局部作用域(Local Scope)和塊級作用域(Block Scope),JS也不例外
全局作用域
全局作用域是在整個代碼中都可訪問的作用域。在瀏覽器環(huán)境中,全局作用域通常是指window對象;在node環(huán)境下,則是globalThis或global
在全局作用域中聲明的變量或函數(shù)是全局變量或全局函數(shù),在代碼任何地方都可以訪問和使用
比如上面圖示中的Foo4可以訪問到全局作用域的變量及函數(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)部訪問,外部無法直接訪問
就像上述Foo3的效果,可以訪問到全局以及當前所在函數(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();
塊級作用域
塊級作用域是在代碼塊(通常是由大括號{}包裹起來的部分)內(nèi)聲明的作用域。比如if(){...}、for(...){...}、try{...}等
ES6之前
在ES6之前,由于變量都是使用var聲明的,所以沒有塊級作用域此類概念,只有全局作用域和函數(shù)(局部)作用域。那么需要模擬塊級作用域如何怎么操作呢?
答案是使用立即執(zhí)行函數(shù)表達式(IIFE):通過將代碼包裝在匿名函數(shù)中并立即執(zhí)行該函數(shù),可以創(chuàng)建一個獨立的作用域,使得內(nèi)部聲明的變量在函數(shù)外部不可訪問
(function () {
var var5 = "var5";
console.log(var5); // var5
})();
console.log(var5); // var5 is not defined
ES6以后
在es6以后,官方推出了塊級作用域的概念,使用let和const關鍵字聲明的變量具有塊級作用域,它們只能在聲明的代碼塊內(nèi)部訪問。
if (true) {
let var5 = "var5";
console.log(var5); // var5
}
console.log(var5); // var5 is not defined
作用域鏈
作用域鏈(Scope Chain)是JS用于解析標識符(變量和函數(shù))的機制,它是由多個嵌套的作用域組成的,它決定了變量和函數(shù)的查找順序。
上面我們說到局部作用域時談到的Foo3可以訪問全局以及當前函數(shù)作用域中的標識符這個特點就歸功于作用域鏈這個概念。當訪問一個變量時,JS引擎會先從當前作用域開始查找,如果找不到這個名稱的標識符則繼續(xù)向上一級作用域查找,直到找到變量或達到全局作用域為止,如果在全局作用域中仍然找不到,則認為該標識符未定義。
變量提升
變量提升的概念出現(xiàn)在面試題中的頻率十分高,對于開發(fā)者來說也是不可忽略的重要知識點;在之前的面試題文章中的Q6和Q7兩題中就出現(xiàn)了變量提升相關的知識點
基礎概念
變量提升是JS在代碼執(zhí)行前將變量和函數(shù)聲明提升到作用域頂部的行為,它由JavaScript引擎在代碼執(zhí)行前的編譯階段處理。變量提升影響了整個作用域范圍內(nèi)的代碼,它允許我們在聲明之前使用變量,但是需要注意一點:只有變量聲明被提升,賦值不會提升。了解了上述概念后,我們思考下面的代碼:
console.log(var6); // undefined
var var6 = 10;
上面的代碼相當于
var var6;
console.log(var6); // undefined
var6 = 10;
優(yōu)先級問題
當同一個作用域中同時出現(xiàn)同名的函數(shù)和變量時,函數(shù)提升的優(yōu)先級更高,也就是說函數(shù)會在變量之上聲明,參考下面的代碼
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
何以見得?思考以下代碼
function a() {}
var a;
console.log(a); // [Function: a]
當把a的賦值去除時,函數(shù)的賦值順序就可以得到驗證,相當于:
var a; // 函數(shù)a
var a; // 變量a
a = function () {};
console.log(a); // [Function: a]
閉包
定義
在介紹垃圾回收和內(nèi)存變化的文章時,我們提到了堆棧內(nèi)存管理的原理。當函數(shù)開始執(zhí)行時,函數(shù)中的變量以及函數(shù)會壓入棧中,那么此時如果當前的作用域中有另一個函數(shù)正在使用該作用域的變量,該變量占用的內(nèi)存也不會被垃圾回收機制回收,這個現(xiàn)象就是閉包
換句話說,閉包是指函數(shù)能夠"記住"并訪問其創(chuàng)建時的詞法環(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時依然可以訪問num變量
特點
- 即使外部函數(shù)已經(jīng)執(zhí)行完畢,內(nèi)部函數(shù)依然可以訪問外部函數(shù)作用域中的變量(當棧將函數(shù)彈出時,變量依然處于內(nèi)存中)
- 閉包可以持有對外部變量的引用,使得外部變量的值在內(nèi)部函數(shù)中保持活動狀態(tài)(不被垃圾回收機制回收)
- 閉包中的內(nèi)部函數(shù)可以修改并更新外部變量的值
- 閉包的函數(shù)可以獲取到創(chuàng)建時的整個作用域鏈的標識符
- 閉包可能會導致內(nèi)存泄漏,被閉包引用的變量無法被垃圾回收機制處理
使用場景
封裝私有變量
JS中沒有TS的private關鍵詞,無法直接定義私有變量,但是可以通過閉包產(chǎn)生私有環(huán)境作用域(ES2022后引入了#關鍵字,用于定義私有變量,相比于使用閉包,更直觀和方便)
const Animal = (function () {
const name = "dog";
function Animal() {}
Animal.prototype.getName = function () {
return name;
};
return Animal;
})();
console.log(new Animal().getName()); // dog
延長變量周期
延長變量的生命周期也是閉包的特性之一,該效果通過內(nèi)部函數(shù)對外部作用域的可訪問性實現(xiàn)
function delayMessage(msg) {
return function () {
return msg;
};
}
const msg = delayMessage("msg");
console.log(msg()); // msg
模塊化、命名空間
在TypeScript模塊文章中我們使用JS中的立即執(zhí)行函數(shù)實現(xiàn)了命名空間功能,達到模塊化的效果
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ù)與其對應的結果保存在內(nèi)部,避免重復計算
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ù),這三者對作用域分別有什么影響呢?
const、let
塊級作用域
上面說到const、let的出現(xiàn)奠定了JS中的塊級作用域的概念,使if、for等語句中也存在作用域這一功能;然而不僅僅是條件語句、循環(huán)語句,使用{...}定義的范圍都是塊級作用域,這意味著在塊級作用域內(nèi)部聲明的變量只在該作用域內(nèi)有效,并且在作用域外部無法訪問
{
let msg = "hello";
console.log(msg); // hello
}
console.log(msg); // msg is not defined
變量提升
使用let和const聲明的變量不會被提升到作用域的頂部,它們只能在聲明后才能被訪問;這點與var不太一樣
暫時性死區(qū)
暫時性死區(qū)指的是在變量聲明前訪問變量會拋出錯誤。只有在變量聲明語句執(zhí)行完成之后,變量才會進入有效狀態(tài),才能被訪問和使用。這點效果與上面的變量提升效果一樣
不可重復聲明
在同一個作用域中不能被重復聲明,否則會報錯;而使用var定義變量時后聲明的會覆蓋先聲明的
這三個特點可以參考下面的代碼:
console.log(var1); // 在賦值前使用了變量“var1”
const var1 = 11;
let var2 = 22; // 無法重新聲明塊范圍變量“var2”
let var2 = 22;
箭頭函數(shù)
箭頭函數(shù)在JavaScript中仍然與普通函數(shù)一樣有函數(shù)作用域的概念
題外話
動態(tài)作用域與詞法作用域
作用域的種類有兩種:分別是動態(tài)作用域和詞法作用域(靜態(tài)作用域)。上述我們介紹的是詞法作用域,什么是詞法作用域?
詞法作用域
詞法作用域是基于代碼的靜態(tài)結構來確定變量的訪問規(guī)則。也就是說它由變量和函數(shù)在代碼中的聲明位置而不是調用的位置來確定,思考下面的代碼
function foo1() {
var var1 = 10; // foo2可訪問的作用域
// 聲明foo2的作用域
return function foo2() {
console.log(var1);// 10
};
}
var foo2 = foo1();
function foo3() {
var var1 = 20; // foo2不可訪問的作用域
// 執(zhí)行foo2的作用域
foo2();
}
foo3();
在foo1中聲明了函數(shù)foo2,在foo3中執(zhí)行foo2,可以看到,foo2取的var1是聲明foo2的作用域中的變量(10)。這個現(xiàn)象說明JS采用的是詞法作用域。
動態(tài)作用域
那么反之,如果還是上述代碼,foo2取的var1是執(zhí)行foo2的作用域中的變量(20),就說明語言采用的是動態(tài)作用域
總結
JavaScript作用域與變量提升是編寫高質量代碼所必須掌握的重要概念。本文介紹了作用域的定義、作用和目的,以及JavaScript中的不同作用域類型,包括全局作用域、函數(shù)作用域和塊級作用域。我們還討論了變量提升的概念、原理和影響范圍,以及作用域鏈和閉包的關系。此外,還探討了ES6的作用域,并對比了其與早期作用域的區(qū)別。最后針對動態(tài)作用域與詞法作用域作了一個簡單的說明。文章來源:http://www.zghlxwxcb.cn/news/detail-679927.html
好了,以上就是文章的全部內(nèi)容了,謝謝你看到了這里,如果覺得文章不錯的話,還望三連支持一下,感謝支持!文章來源地址http://www.zghlxwxcb.cn/news/detail-679927.html
到了這里,關于JavaScript作用域詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!