js 執(zhí)行上下文和作用域的相關(guān)總結(jié)
如果你是或者你想成為一名合格的前端開發(fā)工作者,你必須知道JavaScript代碼在執(zhí)行過程,知道執(zhí)行上下文、作用域、變量提升等相關(guān)概念,并且熟練應(yīng)用到自己的代碼中。本文參考了你不知道的JavaScript,和JavaScript高級程序設(shè)計,以及部分博客。
正文 1.JavaScript代碼的執(zhí)行過程相關(guān)概念js代碼的執(zhí)行分為編譯器的編譯和js引擎與作用域執(zhí)行兩個階段,其中編譯器編譯的階段(預(yù)編譯階段)分為分詞/詞法分析、解析/語法分析、代碼生成三個階段。
(1)在分詞/詞法分析階段,編譯器負(fù)責(zé)將代碼進行分割處理,將語句分割成詞法單元流/數(shù)組;
(2)在解析/詞法分析階段,將上一階段的詞法單元流轉(zhuǎn)換成由元素嵌套組成的符合程序語法結(jié)構(gòu)的抽象語法樹;
(3)在代碼生成階段,將抽象語法樹轉(zhuǎn)換成可執(zhí)行代碼,并交付給js引擎。
js代碼執(zhí)行的三個重要角色:
(1)js引擎:負(fù)責(zé)代碼執(zhí)行的整個過程
(2)編譯器:負(fù)責(zé)js代碼語法解析和生成可執(zhí)行代碼
(3)作用域:手機并維護所有聲明標(biāo)識符,根據(jù)特定規(guī)則確定當(dāng)前代碼對聲明的標(biāo)識符的訪問權(quán)限
2. 執(zhí)行上下文和執(zhí)行棧每當(dāng)js代碼在運行的時候,它都是在執(zhí)行上下文中運行。說到執(zhí)行上下文,需要知道什么時執(zhí)行棧,執(zhí)行棧,就是其他編程語言中的“調(diào)用棧”,是一種擁有LIFO(后進先出)數(shù)據(jù)結(jié)構(gòu)的棧,被用來存儲代碼運行時所創(chuàng)建的執(zhí)行上下文。當(dāng)js引擎第一次遇到要執(zhí)行的代碼的時候,首先會創(chuàng)建一個全局的執(zhí)行上下文并壓入當(dāng)前執(zhí)行棧,每當(dāng)引擎遇到一個函數(shù)調(diào)用,它會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文并壓入棧頂,js引擎執(zhí)行棧頂?shù)暮瘮?shù),當(dāng)該函數(shù)執(zhí)行完畢,執(zhí)行上下文從棧中彈出,控制流程到達(dá)下一個上下文。對于每一個執(zhí)行上下文都含有三個重要屬性:變量對象,作用域鏈,this。這些屬性也需要徹底理解。
2.1 、上下文調(diào)用棧var scope1 = 'global scope'; function checkscope1(){ var scope1 = 'local scope'; function f(){ console.log(scope1); } return f(); } checkscope1();
var scope2 = 'global scope'; function checkscope2(){ var scope2 = 'local scope'; function f(){ console.log(scope2); } return f; } checkscope2()();
上面兩段代碼都會輸出 local scope
上面代碼中scope一定是局部變量,查找塊級作用域即可,不管何時何地執(zhí)行 f(),這種綁定在執(zhí)行f()時依然有效。出現(xiàn)了一樣的結(jié)果,但是兩段代碼的執(zhí)行上下文棧的變化不一樣 :
第一段代碼:push(<checkscope1>functionContext)=>push(<f>functionContext)=>pop()=>pop()
第二段代碼:push(<checkscope2>functionContext)=>pop()=>push(<f>functionContext)=>pop()
2.2 、三種執(zhí)行上下文類型(1)全局上下文
js引擎開始解析js代碼的時候首先遇到的就是全局代碼,初始化的時候會在調(diào)用棧中壓入一個全局執(zhí)行的上下文,當(dāng)整個應(yīng)用程序結(jié)束的時候才會清空執(zhí)行上下文棧,棧的最底部永遠(yuǎn)時全局執(zhí)行上下文。這是默認(rèn)的或者說基礎(chǔ)的全局作用域,任何函數(shù)內(nèi)部的代碼都在全局作用域中,首先創(chuàng)建一個全局的window對象,然后設(shè)置this的值等于這個全局對象,一個程序中只有一個全局執(zhí)行上下文。在頂層js代碼中可以使用this引用全局對象,因為全局對象時是域鏈的頭,意味著所有非限定性的變量和函數(shù)都作為該對象的函數(shù)來查詢。
總之,全局執(zhí)行上下文只有一個,在客戶端中一般由瀏覽器創(chuàng)建,也就是我們熟知的window對象,我們能通過this直接訪問到它。
(2)函數(shù)上下文
每當(dāng)一個函數(shù)被調(diào)用是,都會外該函數(shù)創(chuàng)建一個新的上下文,每個函數(shù)都擁有自己的上下文,不過是在函數(shù)調(diào)用的時候創(chuàng)建的,需要注意的是同一個函數(shù)被多次調(diào)用,都會創(chuàng)建一個新的上下文。
(3)eval和with上下文
執(zhí)行在 eval和with 函數(shù)內(nèi)部的代碼也會有它屬于自己的執(zhí)行上下文,但由于 JavaScript 開發(fā)者并不經(jīng)常使用 eval,所以在這里我不會討論它。
2.3 、執(zhí)行上下文創(chuàng)建階段
執(zhí)行上下文創(chuàng)建分為創(chuàng)建階段與執(zhí)行階段兩個階段
js引擎在執(zhí)行上下文創(chuàng)建階段主要負(fù)責(zé)三件事:確定this==>創(chuàng)建詞法環(huán)境組件==>創(chuàng)建變量環(huán)境組件(目前還不太理解)
(1)確定this,這個不做詳解
(2)創(chuàng)建詞法環(huán)境組件
詞法環(huán)境是一種規(guī)范類型,基于 ECMAScript 代碼的詞法嵌套結(jié)構(gòu)來定義標(biāo)識符和具體變量和函數(shù)的關(guān)聯(lián)。一個詞法環(huán)境由環(huán)境記錄器和一個可能的引用外部詞法環(huán)境的空值組成。其中環(huán)境記錄用于存儲當(dāng)前環(huán)境中的變量和函數(shù)聲明的實際位置;外部環(huán)境引入記錄很好理解,它用于保存自身環(huán)境可以訪問的其它外部環(huán)境,那么說到這個,是不是有點作用域鏈的意思?
詞法環(huán)境有兩種類型:
全局環(huán)境(在全局執(zhí)行上下文中)是沒有外部環(huán)境引用的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用是 null。它擁有內(nèi)建的 Object/Array/等、在環(huán)境記錄器內(nèi)的原型函數(shù)(關(guān)聯(lián)全局對象,比如 window 對象)還有任何用戶定義的全局變量,并且 this的值指向全局對象。 在函數(shù)環(huán)境中,函數(shù)內(nèi)部用戶定義的變量存儲在環(huán)境記錄器中。并且引用的外部環(huán)境可能是全局環(huán)境,或者任何包含此內(nèi)部函數(shù)的外部函數(shù)。(3)創(chuàng)建變量環(huán)境組件
變量環(huán)境可以說也是詞法環(huán)境,它具備詞法環(huán)境所有屬性,一樣有環(huán)境記錄與外部環(huán)境引入。在ES6中唯一的區(qū)別在于詞法環(huán)境用于存儲函數(shù)聲明與let const聲明的變量,而變量環(huán)境僅僅存儲var聲明的變量。
3. JavaScript作用域和作用域鏈3.1、作用域詞法作用域是在寫代碼或者定義的時候確定的,而動態(tài)作用域是在運行時確定的,(this也是)詞法作用域關(guān)注函數(shù)在何處聲明,而動態(tài)作用域關(guān)注函數(shù)從何處調(diào)用,JavaScript采用詞法作用域,其作用域由你在寫代碼是將變量和塊作用域?qū)懺谀睦餂Q定,因此當(dāng)詞法分析器處理代碼時會保持作用域不變。可以理解為作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。
理解作用域之前先來看一道題
function foo() { console.log(value); } var value = 1; function bar() { var value = 2; console.log(value); foo(); } bar();
上面的代碼會輸出什么呢,首先在全局上下文中聲明foo()函數(shù)、value變量(其值為undefined)、bar()函數(shù),代碼執(zhí)行階段,bar函數(shù)上下文入棧并執(zhí)行,打印出value為2,然后執(zhí)行foo(),foo()入棧,打印value時找不到該變量,js引擎會查找上層作用域,即全局作用域,于是打印出1。后面函數(shù)執(zhí)行完畢上下文出棧。再來看下面這個函數(shù),作用域是分層的,內(nèi)層作用域可以訪問外層作用域的變量,反之則不行。
ES6以來,js中的作用域分為全局作用域,函數(shù)作用域,塊級作用域和欺騙作用域。
3.1.1、全局作用域
在代碼中任何地方都能訪問到的對象擁有全局作用域,最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域,所有末定義直接賦值的變量自動聲明為擁有全局作用域。
3.1.2、函數(shù)作用域
函數(shù)作用域的含義是指,屬于這個函數(shù)的全部變量都可以在整個函數(shù)的范圍內(nèi)使用及復(fù)用(事實上在嵌套的作用域中也可以使用); 這個原則是指在軟件設(shè)計中,應(yīng)該最小限度地暴露必 要內(nèi)容,而將其他內(nèi)容都“隱藏”起來; 函數(shù)表達(dá)式可以是匿名的, 而函數(shù)聲明則不可以省略函數(shù)名。3.1.3、塊作用域塊作用域,通常指 { .. } 內(nèi)部(1)if 、 try/catch創(chuàng)建塊作用域;(2)let 關(guān)鍵字可以將變量綁定到所在的任意作用域中(通常是 { .. } 內(nèi)部);(3)for 循環(huán)頭部的 let 不僅將 i 綁定到了 for 循環(huán)的塊中,事實上它將其重新綁定到了循環(huán)的每一個迭代中,確保使用上一個循環(huán)迭代結(jié)束時的值重新進行賦值;(4)const同樣可以用來創(chuàng)建塊作用域變量,但其值是固定的 (常量)。創(chuàng)建對象時值可以被改變。3.1.4、欺騙詞法作用域的方法,eval()和with() eval()參數(shù)為一個字符串,并把里面的內(nèi)容當(dāng)作書寫在該位置的代碼一樣處理(非嚴(yán)格模式); with()當(dāng)需要重復(fù)引用一個對象的多個屬性時,可以不需要重復(fù)引用對象本身。
3.2、作用域鏈作用域鏈本質(zhì)上就是根據(jù)名稱查找變量(標(biāo)識符名稱)的一套規(guī)則。規(guī)則非常簡單,在自己的變量對象里找不到變量,就上父級的變量對象查找,當(dāng)?shù)诌_(dá)最外層的全局上下文中,無論找到還是沒找到,查找過程都會停止。查找會在找到第一個匹配的變量時停止,被稱為遮蔽效應(yīng)
作用域鏈的用途是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問 作用域鏈:當(dāng)函數(shù)定義時,系統(tǒng)生成([scope])屬性,該屬性保存該函數(shù)的作用域鏈,該作用域鏈的第0位存儲當(dāng)前環(huán)境下的全局執(zhí)行期上下文GO,GO里存儲全局下的所有對象,其中包含函數(shù)和全局變量,當(dāng)函數(shù)執(zhí)行的前一刻,預(yù)編譯的時候,作用域鏈的頂端(第0位)存儲函數(shù)生成的執(zhí)行上下文AO,同時第一位存儲GO查找變量是到函數(shù)存儲的作用域鏈中從頂端開始依次向下查找(函數(shù)內(nèi)部作用域在最頂端,證明了函數(shù)可以訪問外部的變量,而外部無法訪問函數(shù)內(nèi)部的變量)
4.執(zhí)行上下文和作用域的區(qū)別每個函數(shù)調(diào)用都有與之相關(guān)的作用域和上下文。從根本上說,范圍是基于函數(shù)(function-based)而上下文是基于對象(object-based)。換句話說, 作用域是和每次函數(shù)調(diào)用時變量的訪問有關(guān),并且每次調(diào)用都是獨立的。上下文總是關(guān)鍵字 this 的值,是調(diào)用當(dāng)前可執(zhí)行代碼的對象的引用。作用域是函數(shù)定義的時候就確定好的了,函數(shù)當(dāng)中的變量是和函數(shù)所處的作用域有關(guān),函數(shù)運行的作用域也是與該函數(shù)定義時的作用域有關(guān)。而上下文,主要是關(guān)鍵字this的值,這個是由函數(shù)運行時決定的,簡單來說就是誰調(diào)用此函數(shù),this就指向誰。
5.最后以上就是js 執(zhí)行上下文和作用域的相關(guān)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于js 執(zhí)行上下文和作用域的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. python利用os模塊編寫文件復(fù)制功能——copy()函數(shù)用法2. php測試程序運行速度和頁面執(zhí)行速度的代碼3. php網(wǎng)絡(luò)安全中命令執(zhí)行漏洞的產(chǎn)生及本質(zhì)探究4. 三個不常見的 HTML5 實用新特性簡介5. 無線標(biāo)記語言(WML)基礎(chǔ)之WMLScript 基礎(chǔ)第1/2頁6. ajax請求添加自定義header參數(shù)代碼7. Python使用jupyter notebook查看ipynb文件過程解析8. 解決Python 進程池Pool中一些坑9. 解決python腳本中error: unrecognized arguments: True錯誤10. IntelliJ IDEA創(chuàng)建普通的Java 項目及創(chuàng)建 Java 文件并運行的教程
