新聞中心
作用域和作用域鏈在Javascript和很多其它的編程語(yǔ)言中都是一種基礎(chǔ)概念。但很多Javascript開(kāi)發(fā)者并不真正理解它們,但這些概念對(duì)掌握J(rèn)avascript至關(guān)重要。
公司專(zhuān)注于為企業(yè)提供成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開(kāi)發(fā)、購(gòu)物商城網(wǎng)站建設(shè),成都微信小程序,軟件按需開(kāi)發(fā)等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。憑借多年豐富的經(jīng)驗(yàn),我們會(huì)仔細(xì)了解各客戶的需求而做出多方面的分析、設(shè)計(jì)、整合,為客戶設(shè)計(jì)出具風(fēng)格及創(chuàng)意性的商業(yè)解決方案,創(chuàng)新互聯(lián)更提供一系列網(wǎng)站制作和網(wǎng)站推廣的服務(wù)。
正確的去理解這個(gè)概念有利于你去寫(xiě)更好,更高效和更簡(jiǎn)潔的代碼,讓你成為一個(gè)更優(yōu)秀的Javascript開(kāi)發(fā)者。
因此,在本文中,我將會(huì)向大家解釋清楚什么是作用域和作用域鏈,以及Javascript引擎在內(nèi)部是如何通過(guò)它們操作和查找變量的。
事不宜遲,正文開(kāi)始 :)
什么是作用域
Javascript中的作用域說(shuō)的是變量的可訪問(wèn)性和可見(jiàn)性。也就是說(shuō)整個(gè)程序中哪些部分可以訪問(wèn)這個(gè)變量,或者說(shuō)這個(gè)變量都在哪些地方可見(jiàn)。
為什么作用域很重要
作用域最為重要的一點(diǎn)是安全。變量只能在特定的區(qū)域內(nèi)才能被訪問(wèn),有了作用域我們就可以避免在程序其它位置意外對(duì)某個(gè)變量做出修改。
作用域也會(huì)減輕命名的壓力。我們可以在不同的作用域下面定義相同的變量名。
作用域的類(lèi)型
Javascript中有三種作用域:
- 全局作用域;
- 函數(shù)作用域;
- 塊級(jí)作用域;
1. 全局作用域
任何不在函數(shù)中或是大括號(hào)中聲明的變量,都是在全局作用域下,全局作用域下聲明的變量可以在程序的任意位置訪問(wèn)。例如:
// 全局變量 var greeting = 'Hello World!'; function greet() { console.log(greeting); } // 打印 'Hello World!' greet();
2. 函數(shù)作用域
函數(shù)作用域也叫局部作用域,如果一個(gè)變量是在函數(shù)內(nèi)部聲明的它就在一個(gè)函數(shù)作用域下面。這些變量只能在函數(shù)內(nèi)部訪問(wèn),不能在函數(shù)以外去訪問(wèn)。例如:
function greet() { var greeting = 'Hello World!'; console.log(greeting); } // 打印 'Hello World!' greet(); // 報(bào)錯(cuò): Uncaught ReferenceError: greeting is not defined console.log(greeting);
3. 塊級(jí)作用域
ES6引入了let和const關(guān)鍵字,和var關(guān)鍵字不同,在大括號(hào)中使用let和const聲明的變量存在于塊級(jí)作用域中。在大括號(hào)之外不能訪問(wèn)這些變量??蠢樱?/p>
{ // 塊級(jí)作用域中的變量 let greeting = 'Hello World!'; var lang = 'English'; console.log(greeting); // Prints 'Hello World!' } // 變量 'English' console.log(lang); // 報(bào)錯(cuò):Uncaught ReferenceError: greeting is not defined console.log(greeting);
上面代碼中可以看出,在大括號(hào)內(nèi)使用var聲明的變量lang是可以在大括號(hào)之外訪問(wèn)的。使用var聲明的變量不存在塊級(jí)作用域中。
作用域嵌套
像Javascript中函數(shù)可以在一個(gè)函數(shù)內(nèi)部聲明另一個(gè)函數(shù)一樣,作用域也可以嵌套在另一個(gè)作用域中。請(qǐng)看例子:
var name = 'Peter'; function greet() { var greeting = 'Hello'; { let lang = 'English'; console.log(`${lang}: ${greeting} ${name}`); } } greet();
這里我們有三層作用域嵌套,首先第一層是一個(gè)塊級(jí)作用域(let聲明的),被嵌套在一個(gè)函數(shù)作用域(greet函數(shù))中,最外層作用域是全局作用域。
詞法作用域
詞法作用域(也叫靜態(tài)作用域)從字面意義上看是說(shuō)作用域在詞法化階段(通常是編譯階段)確定而非執(zhí)行階段確定的??蠢樱?/p>
let number = 42; function printNumber() { console.log(number); } function log() { let number = 54; printNumber(); } // Prints 42 log();
上面代碼可以看出無(wú)論printNumber()在哪里調(diào)用console.log(number)都會(huì)打印42。動(dòng)態(tài)作用域不同,console.log(number)這行代碼打印什么取決于函數(shù)printNumber()在哪里調(diào)用。
如果是動(dòng)態(tài)作用域,上面console.log(number)這行代碼就會(huì)打印54。
使用詞法作用域,我們可以僅僅看源代碼就可以確定一個(gè)變量的作用范圍,但如果是動(dòng)態(tài)作用域,代碼執(zhí)行之前我們沒(méi)法確定變量的作用范圍。
像C,C++,Java,Javascript等大多數(shù)編程語(yǔ)言都支持靜態(tài)作用域。Perl 既支持動(dòng)態(tài)作用域也支持靜態(tài)作用域。
作用域鏈
當(dāng)在Javascript中使用一個(gè)變量的時(shí)候,首先Javascript引擎會(huì)嘗試在當(dāng)前作用域下去尋找該變量,如果沒(méi)找到,再到它的上層作用域?qū)ふ?,以此?lèi)推直到找到該變量或是已經(jīng)到了全局作用域。
如果在全局作用域里仍然找不到該變量,它就會(huì)在全局范圍內(nèi)隱式聲明該變量(非嚴(yán)格模式下)或是直接報(bào)錯(cuò)。
例如:
let foo = 'foo'; function bar() { let baz = 'baz'; // 打印 'baz' console.log(baz); // 打印 'foo' console.log(foo); number = 42; console.log(number); // 打印 42 } bar();
當(dāng)函數(shù)bar()被調(diào)用,Javascript引擎首先在當(dāng)前作用域下尋找變量baz,然后尋找foo變量但發(fā)現(xiàn)在當(dāng)前作用域下找不到,然后繼續(xù)在外部作用域?qū)ふ艺业搅怂?這里是在全局作用域找到的)。
然后將42賦值給變量number。Javascript引擎會(huì)在當(dāng)前作用域以及外部作用域下一步步尋找number變量(沒(méi)找到)。
如果是在非嚴(yán)格模式下,引擎會(huì)創(chuàng)建一個(gè)number的全局變量并把42賦值給它。但如果是嚴(yán)格模式下就會(huì)報(bào)錯(cuò)了。
結(jié)論:當(dāng)使用一個(gè)變量的時(shí)候,Javascript引擎會(huì)循著作用域鏈一層一層往上找該變量,直到找到該變量為止。
作用域和作用域鏈?zhǔn)侨绾喂ぷ鞯?br />
以上內(nèi)容已經(jīng)講解了作用域,作用域的類(lèi)型,現(xiàn)在讓我們看下Javascript引擎是如何確定變量的作用域鏈和如何去查找變量的。
要想理解Javascript是如何進(jìn)行變量查找的,必須要了解Javascript中詞法環(huán)境這個(gè)概念(請(qǐng)參考:理解Javascript中的執(zhí)行上下文和執(zhí)行棧)。
什么是詞法環(huán)境
所謂詞法環(huán)境就是一種標(biāo)識(shí)符—變量映射的結(jié)構(gòu)(這里的標(biāo)識(shí)符指的是變量/函數(shù)的名字,變量是對(duì)實(shí)際對(duì)象[包含函數(shù)和數(shù)組類(lèi)型的對(duì)象]或基礎(chǔ)數(shù)據(jù)類(lèi)型的引用)。
簡(jiǎn)單地說(shuō),詞法環(huán)境是Javascript引擎用來(lái)存儲(chǔ)變量和對(duì)象引用的地方。
注意:不要混淆了詞法環(huán)境和詞法作用域,詞法作用域是在代碼編譯階段確定的作用域(譯者注:一個(gè)抽象的概念),而詞法環(huán)境是Javascript引擎用來(lái)存儲(chǔ)變量和對(duì)象引用的地方(譯者注:一個(gè)具象的概念)。
一個(gè)詞法環(huán)境就像下面這樣:
lexicalEnvironment = { a: 25, obj:}
只有當(dāng)該作用域的代碼被執(zhí)行的時(shí)候,引擎才會(huì)為那個(gè)作用域創(chuàng)建一個(gè)新的詞法環(huán)境。詞法環(huán)境還會(huì)記錄所引用的外部詞法環(huán)境(即外部作用域)。例:
lexicalEnvironment = { a: 25, obj:outer: }
Javascript引擎是如何進(jìn)行變量查找的
現(xiàn)在我們已經(jīng)知道了作用域,作用域鏈和詞法環(huán)境的概念,現(xiàn)在讓我們看下Javascript引擎是如何利用詞法環(huán)境來(lái)確定作用域和作用域鏈的。
結(jié)合例子我們來(lái)理解上面的這些概念:
let greeting = 'Hello'; function greet() { let name = 'Peter'; console.log(`${greeting} ${name}`); // Hello Peter } greet(); { let greeting = 'Hello World!' console.log(greeting); // Hello World! }
上述代碼加載后,首先會(huì)創(chuàng)建一個(gè)全局詞法環(huán)境,其中包含在全局范圍內(nèi)聲明的變量和函數(shù)。像下面這樣:
globalLexicalEnvironment = { greeting: 'Hello' greet:outer: }
這里的outer字段(也就是外部詞法環(huán)境)被設(shè)置為了null,是因?yàn)槿衷~法環(huán)境已經(jīng)是最頂層的詞法環(huán)境了。
然后,我們調(diào)用了greet()函數(shù),然后一個(gè)新的詞法環(huán)境會(huì)被被創(chuàng)建:
functionLexicalEnvironment = { name: 'Peter' outer:}
這里的outer字段被設(shè)置為了globalLexicalEnvironment,是因?yàn)樗耐獠孔饔糜蚓褪侨肿饔糜颉?/p>
然后,執(zhí)行console.log(`${greeting} ${name}`)這行代碼,Javascript引擎首先在當(dāng)前函數(shù)的詞法環(huán)境中尋找變量greeting和name,但只找到了name,沒(méi)找到greeting。然后繼續(xù)在上層的詞法環(huán)境中找greeting(這里是全局作詞法環(huán)境)。最后在全局詞法環(huán)境中找到了greeting。
緊接著執(zhí)行那段在大括號(hào)里的代碼,為這個(gè)塊級(jí)創(chuàng)建一個(gè)新的詞法環(huán)境。如下:
blockLexicalEnvironment = { greeting: 'Hello World', outer:}
然后執(zhí)行console.log(greeting)這行代碼,首先在本層詞法環(huán)境中找greeting,OK,找到,結(jié)束。此時(shí)就不會(huì)再去外部作用域(這里是全局作用域)尋找該變量了。
注意:只有l(wèi)et和const聲明變量才會(huì)創(chuàng)建一個(gè)新的詞法環(huán)境存儲(chǔ),使用var聲明的變量會(huì)被存儲(chǔ)在當(dāng)前塊(大括號(hào))所在的詞法環(huán)境中(全局詞法環(huán)境或是函數(shù)詞法環(huán)境中)。
結(jié)論:當(dāng)一個(gè)變量被使用時(shí),Javascript引擎會(huì)首先在當(dāng)前的詞法環(huán)境中進(jìn)行尋找,如果找不到就找上層詞法環(huán)境中尋找,直到找到為止。
結(jié)論
作用域就是一個(gè)變量可訪問(wèn)和可見(jiàn)的區(qū)域,和函數(shù)一樣,Javascript的作用域也可以嵌套,Javascript引擎會(huì)層層遍歷作用域來(lái)尋找用到的變量。
Javascript使用詞法作用域,這意味著變量的作用在編譯階段就會(huì)被確定。
Javascript引擎在程序執(zhí)行期間使用詞法環(huán)境來(lái)存儲(chǔ)變量和函數(shù)。
作用域和作用域鏈?zhǔn)荍avascript中的基礎(chǔ)概念,理解作用域和作用域鏈能讓你成為一個(gè)更優(yōu)秀的Javascript開(kāi)發(fā)者。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
當(dāng)前題目:Javascript作用域和作用域鏈原理解析
瀏覽路徑:http://fisionsoft.com.cn/article/jshghp.html