js 執(zhí)行上下文和作用域的相關(guān)總結(jié)
前言
如果你是或者你想成為一名合格的前端開(kāi)發(fā)工作者,你必須知道JavaScript代碼在執(zhí)行過(guò)程,知道執(zhí)行上下文、作用域、變量提升等相關(guān)概念,并且熟練應(yīng)用到自己的代碼中。本文參考了你不知道的JavaScript,和JavaScript高級(jí)程序設(shè)計(jì),以及部分博客。
正文
1.JavaScript代碼的執(zhí)行過(guò)程相關(guān)概念
js代碼的執(zhí)行分為編譯器的編譯和js引擎與作用域執(zhí)行兩個(gè)階段,其中編譯器編譯的階段(預(yù)編譯階段)分為分詞/詞法分析、解析/語(yǔ)法分析、代碼生成三個(gè)階段?! ?/p>
(1)在分詞/詞法分析階段,編譯器負(fù)責(zé)將代碼進(jìn)行分割處理,將語(yǔ)句分割成詞法單元流/數(shù)組;
?。?)在解析/詞法分析階段,將上一階段的詞法單元流轉(zhuǎn)換成由元素嵌套組成的符合程序語(yǔ)法結(jié)構(gòu)的抽象語(yǔ)法樹;
(3)在代碼生成階段,將抽象語(yǔ)法樹轉(zhuǎn)換成可執(zhí)行代碼,并交付給js引擎。
js代碼執(zhí)行的三個(gè)重要角色:
?。?)js引擎:負(fù)責(zé)代碼執(zhí)行的整個(gè)過(guò)程
(2)編譯器:負(fù)責(zé)js代碼語(yǔ)法解析和生成可執(zhí)行代碼
?。?)作用域:手機(jī)并維護(hù)所有聲明標(biāo)識(shí)符,根據(jù)特定規(guī)則確定當(dāng)前代碼對(duì)聲明的標(biāo)識(shí)符的訪問(wèn)權(quán)限
2. 執(zhí)行上下文和執(zhí)行棧
每當(dāng)js代碼在運(yùn)行的時(shí)候,它都是在執(zhí)行上下文中運(yùn)行。說(shuō)到執(zhí)行上下文,需要知道什么時(shí)執(zhí)行棧,執(zhí)行棧,就是其他編程語(yǔ)言中的“調(diào)用?!?,是一種擁有LIFO(后進(jìn)先出)數(shù)據(jù)結(jié)構(gòu)的棧,被用來(lái)存儲(chǔ)代碼運(yùn)行時(shí)所創(chuàng)建的執(zhí)行上下文。當(dāng)js引擎第一次遇到要執(zhí)行的代碼的時(shí)候,首先會(huì)創(chuàng)建一個(gè)全局的執(zhí)行上下文并壓入當(dāng)前執(zhí)行棧,每當(dāng)引擎遇到一個(gè)函數(shù)調(diào)用,它會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并壓入棧頂,js引擎執(zhí)行棧頂?shù)暮瘮?shù),當(dāng)該函數(shù)執(zhí)行完畢,執(zhí)行上下文從棧中彈出,控制流程到達(dá)下一個(gè)上下文。對(duì)于每一個(gè)執(zhí)行上下文都含有三個(gè)重要屬性:變量對(duì)象,作用域鏈,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()();
上面兩段代碼都會(huì)輸出 local scope
上面代碼中scope一定是局部變量,查找塊級(jí)作用域即可,不管何時(shí)何地執(zhí)行 f(),這種綁定在執(zhí)行f()時(shí)依然有效。出現(xiàn)了一樣的結(jié)果,但是兩段代碼的執(zhí)行上下文棧的變化不一樣 :
第一段代碼:push(<checkscope1>functionContext)=>push(<f>functionContext)=>pop()=>pop()
第二段代碼:push(<checkscope2>functionContext)=>pop()=>push(<f>functionContext)=>pop()
2.2 、三種執(zhí)行上下文類型
?。?)全局上下文
js引擎開(kāi)始解析js代碼的時(shí)候首先遇到的就是全局代碼,初始化的時(shí)候會(huì)在調(diào)用棧中壓入一個(gè)全局執(zhí)行的上下文,當(dāng)整個(gè)應(yīng)用程序結(jié)束的時(shí)候才會(huì)清空?qǐng)?zhí)行上下文棧,棧的最底部永遠(yuǎn)時(shí)全局執(zhí)行上下文。這是默認(rèn)的或者說(shuō)基礎(chǔ)的全局作用域,任何函數(shù)內(nèi)部的代碼都在全局作用域中,首先創(chuàng)建一個(gè)全局的window對(duì)象,然后設(shè)置this的值等于這個(gè)全局對(duì)象,一個(gè)程序中只有一個(gè)全局執(zhí)行上下文。在頂層js代碼中可以使用this引用全局對(duì)象,因?yàn)槿謱?duì)象時(shí)是域鏈的頭,意味著所有非限定性的變量和函數(shù)都作為該對(duì)象的函數(shù)來(lái)查詢。
總之,全局執(zhí)行上下文只有一個(gè),在客戶端中一般由瀏覽器創(chuàng)建,也就是我們熟知的window對(duì)象,我們能通過(guò)this直接訪問(wèn)到它。
?。?)函數(shù)上下文
每當(dāng)一個(gè)函數(shù)被調(diào)用是,都會(huì)外該函數(shù)創(chuàng)建一個(gè)新的上下文,每個(gè)函數(shù)都擁有自己的上下文,不過(guò)是在函數(shù)調(diào)用的時(shí)候創(chuàng)建的,需要注意的是同一個(gè)函數(shù)被多次調(diào)用,都會(huì)創(chuàng)建一個(gè)新的上下文。
(3)eval和with上下文
執(zhí)行在 eval和with 函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文,但由于 JavaScript 開(kāi)發(fā)者并不經(jīng)常使用 eval,所以在這里我不會(huì)討論它。
2.3 、執(zhí)行上下文創(chuàng)建階段
執(zhí)行上下文創(chuàng)建分為創(chuàng)建階段與執(zhí)行階段兩個(gè)階段
js引擎在執(zhí)行上下文創(chuàng)建階段主要負(fù)責(zé)三件事:確定this==>創(chuàng)建詞法環(huán)境組件==>創(chuàng)建變量環(huán)境組件(目前還不太理解)
(1)確定this,這個(gè)不做詳解
?。?)創(chuàng)建詞法環(huán)境組件
詞法環(huán)境是一種規(guī)范類型,基于 ECMAScript 代碼的詞法嵌套結(jié)構(gòu)來(lái)定義標(biāo)識(shí)符和具體變量和函數(shù)的關(guān)聯(lián)。一個(gè)詞法環(huán)境由環(huán)境記錄器和一個(gè)可能的引用外部詞法環(huán)境的空值組成。其中環(huán)境記錄用于存儲(chǔ)當(dāng)前環(huán)境中的變量和函數(shù)聲明的實(shí)際位置;外部環(huán)境引入記錄很好理解,它用于保存自身環(huán)境可以訪問(wèn)的其它外部環(huán)境,那么說(shuō)到這個(gè),是不是有點(diǎn)作用域鏈的意思?
詞法環(huán)境有兩種類型:
- 全局環(huán)境(在全局執(zhí)行上下文中)是沒(méi)有外部環(huán)境引用的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用是 null。它擁有內(nèi)建的 Object/Array/等、在環(huán)境記錄器內(nèi)的原型函數(shù)(關(guān)聯(lián)全局對(duì)象,比如 window 對(duì)象)還有任何用戶定義的全局變量,并且 this的值指向全局對(duì)象。
- 在函數(shù)環(huán)境中,函數(shù)內(nèi)部用戶定義的變量存儲(chǔ)在環(huán)境記錄器中。并且引用的外部環(huán)境可能是全局環(huán)境,或者任何包含此內(nèi)部函數(shù)的外部函數(shù)。
?。?)創(chuàng)建變量環(huán)境組件
變量環(huán)境可以說(shuō)也是詞法環(huán)境,它具備詞法環(huán)境所有屬性,一樣有環(huán)境記錄與外部環(huán)境引入。在ES6中唯一的區(qū)別在于詞法環(huán)境用于存儲(chǔ)函數(shù)聲明與let const聲明的變量,而變量環(huán)境僅僅存儲(chǔ)var聲明的變量。
3. JavaScript作用域和作用域鏈
3.1、作用域
詞法作用域是在寫代碼或者定義的時(shí)候確定的,而動(dòng)態(tài)作用域是在運(yùn)行時(shí)確定的,(this也是)詞法作用域關(guān)注函數(shù)在何處聲明,而動(dòng)態(tài)作用域關(guān)注函數(shù)從何處調(diào)用,JavaScript采用詞法作用域,其作用域由你在寫代碼是將變量和塊作用域?qū)懺谀睦餂Q定,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變??梢岳斫鉃樽饔糜蚓褪且粋€(gè)獨(dú)立的地盤,讓變量不會(huì)外泄、暴露出去。也就是說(shuō)作用域最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突。
理解作用域之前先來(lái)看一道題
function foo() { console.log(value); } var value = 1; function bar() { var value = 2; console.log(value); foo(); } bar();
上面的代碼會(huì)輸出什么呢,首先在全局上下文中聲明foo()函數(shù)、value變量(其值為undefined)、bar()函數(shù),代碼執(zhí)行階段,bar函數(shù)上下文入棧并執(zhí)行,打印出value為2,然后執(zhí)行foo(),foo()入棧,打印value時(shí)找不到該變量,js引擎會(huì)查找上層作用域,即全局作用域,于是打印出1。后面函數(shù)執(zhí)行完畢上下文出棧。再來(lái)看下面這個(gè)函數(shù),作用域是分層的,內(nèi)層作用域可以訪問(wèn)外層作用域的變量,反之則不行。
ES6以來(lái),js中的作用域分為全局作用域,函數(shù)作用域,塊級(jí)作用域和欺騙作用域。
3.1.1、全局作用域
在代碼中任何地方都能訪問(wèn)到的對(duì)象擁有全局作用域,最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域,所有末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域。
3.1.2、函數(shù)作用域
函數(shù)作用域的含義是指,屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用(事實(shí)上在嵌套的作用域中也可以使用);
這個(gè)原則是指在軟件設(shè)計(jì)中,應(yīng)該最小限度地暴露必 要內(nèi)容,而將其他內(nèi)容都“隱藏”起來(lá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)的塊中,事實(shí)上它將其重新綁定到了循環(huán)的每一個(gè)迭代中,確保使用上一個(gè)循環(huán)迭代結(jié)束時(shí)的值重新進(jìn)行賦值;
(4)const同樣可以用來(lái)創(chuàng)建塊作用域變量,但其值是固定的 (常量)。創(chuàng)建對(duì)象時(shí)值可以被改變。
3.1.4、欺騙詞法作用域的方法,eval()和with()
eval()參數(shù)為一個(gè)字符串,并把里面的內(nèi)容當(dāng)作書寫在該位置的代碼一樣處理(非嚴(yán)格模式);
with()當(dāng)需要重復(fù)引用一個(gè)對(duì)象的多個(gè)屬性時(shí),可以不需要重復(fù)引用對(duì)象本身。
3.2、作用域鏈
作用域鏈本質(zhì)上就是根據(jù)名稱查找變量(標(biāo)識(shí)符名稱)的一套規(guī)則。規(guī)則非常簡(jiǎn)單,在自己的變量對(duì)象里找不到變量,就上父級(jí)的變量對(duì)象查找,當(dāng)?shù)诌_(dá)最外層的全局上下文中,無(wú)論找到還是沒(méi)找到,查找過(guò)程都會(huì)停止。查找會(huì)在找到第一個(gè)匹配的變量時(shí)停止,被稱為遮蔽效應(yīng)
作用域鏈的用途是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)
作用域鏈:當(dāng)函數(shù)定義時(shí),系統(tǒng)生成([scope])屬性,該屬性保存該函數(shù)的作用域鏈,該作用域鏈的第0位存儲(chǔ)當(dāng)前環(huán)境下的全局執(zhí)行期上下文GO,GO里存儲(chǔ)全局下的所有對(duì)象,其中包含函數(shù)和全局變量,當(dāng)函數(shù)執(zhí)行的前一刻,預(yù)編譯的時(shí)候,作用域鏈的頂端(第0位)存儲(chǔ)函數(shù)生成的執(zhí)行上下文AO,同時(shí)第一位存儲(chǔ)GO
查找變量是到函數(shù)存儲(chǔ)的作用域鏈中從頂端開(kāi)始依次向下查找(函數(shù)內(nèi)部作用域在最頂端,證明了函數(shù)可以訪問(wèn)外部的變量,而外部無(wú)法訪問(wèn)函數(shù)內(nèi)部的變量)
4.執(zhí)行上下文和作用域的區(qū)別
每個(gè)函數(shù)調(diào)用都有與之相關(guān)的作用域和上下文。從根本上說(shuō),范圍是基于函數(shù)(function-based)而上下文是基于對(duì)象(object-based)。換句話說(shuō), 作用域是和每次函數(shù)調(diào)用時(shí)變量的訪問(wèn)有關(guān),并且每次調(diào)用都是獨(dú)立的。上下文總是關(guān)鍵字 this 的值,是調(diào)用當(dāng)前可執(zhí)行代碼的對(duì)象的引用。作用域是函數(shù)定義的時(shí)候就確定好的了,函數(shù)當(dāng)中的變量是和函數(shù)所處的作用域有關(guān),函數(shù)運(yùn)行的作用域也是與該函數(shù)定義時(shí)的作用域有關(guān)。而上下文,主要是關(guān)鍵字this的值,這個(gè)是由函數(shù)運(yùn)行時(shí)決定的,簡(jiǎn)單來(lái)說(shuō)就是誰(shuí)調(diào)用此函數(shù),this就指向誰(shuí)。
5.最后
以上就是js 執(zhí)行上下文和作用域的相關(guān)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于js 執(zhí)行上下文和作用域的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)滑動(dòng)門效果的方法詳解
這篇文章主要介紹了JS實(shí)現(xiàn)滑動(dòng)門效果的方法,結(jié)合實(shí)例形式分析了滑動(dòng)門效果的實(shí)現(xiàn)原理、步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-12-12用客戶端js實(shí)現(xiàn)帶省略號(hào)的分頁(yè)
帶省略號(hào)的分頁(yè)只有在服務(wù)器端才可以實(shí)現(xiàn),下面為大家介紹的是用js實(shí)現(xiàn)的帶省略號(hào)的分頁(yè),感興趣的朋友可以參考下哈,希望對(duì)你寫出好的分頁(yè)有所幫助2013-04-04javascript將浮點(diǎn)數(shù)轉(zhuǎn)換成整數(shù)的三個(gè)方法
將浮點(diǎn)數(shù)轉(zhuǎn)換成整數(shù)方法有很多,本例為大家介紹常用的三個(gè)方法,如果讀者想到其他好用方法,也可以交流一下2014-06-06javascript中的parseInt和parseFloat區(qū)別
這篇文章用簡(jiǎn)單的小例子演示了parseInt和parseFloat區(qū)別,有需要的朋友可以參考一下2013-07-07javascript 自動(dòng)標(biāo)記來(lái)自搜索結(jié)果頁(yè)的關(guān)鍵字
使用javascript自動(dòng)標(biāo)記來(lái)自搜索結(jié)果頁(yè)的關(guān)鍵字的實(shí)現(xiàn)代碼。2010-01-01javascript瀑布流布局實(shí)現(xiàn)方法詳解
這篇文章主要介紹了javascript瀑布流布局實(shí)現(xiàn)方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了JavaScript實(shí)現(xiàn)瀑布流布局的樣式與具體功能代碼,需要的朋友可以參考下2016-02-02深入淺析JavaScript中數(shù)據(jù)共享和數(shù)據(jù)傳遞
這篇文章主要介紹了深入淺析JavaScript中數(shù)據(jù)共享和數(shù)據(jù)傳遞的相關(guān)資料,需要的朋友可以參考下2016-04-04基于JavaScript實(shí)現(xiàn)購(gòu)物網(wǎng)站商品放大鏡效果
大家在日常生活中都有網(wǎng)購(gòu)的經(jīng)驗(yàn),有的網(wǎng)站會(huì)有商品放大鏡功能,效果非常棒,那么基于js代碼是如何實(shí)現(xiàn)的呢?下面小編給大家?guī)?lái)了基于js實(shí)現(xiàn)購(gòu)物網(wǎng)站商品放大鏡效果,非常不錯(cuò),感興趣的朋友參考下吧2016-09-09