最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript執(zhí)行機制、作用域及作用域鏈詳解

 更新時間:2026年03月03日 10:52:30   作者:亦妤  
JavaScript中,作用域鏈(Scope Chain)是實現(xiàn)變量和函數(shù)訪問的核心機制,它決定了代碼中變量和函數(shù)的可訪問范圍,這篇文章主要介紹了JavaScript執(zhí)行機制、作用域及作用域鏈的相關(guān)資料,需要的朋友可以參考下

要了解js的執(zhí)行機制,那么首先需要明白執(zhí)行上下文、執(zhí)行棧以及作用域和作用域鏈的概念。

執(zhí)行上下文

執(zhí)行上下文(Execution Context),縮寫為EC。js代碼在執(zhí)行之前需要做一些準(zhǔn)備,類比我們在上課之前需要在教室中準(zhǔn)備好粉筆,黑板課桌等等。js代碼在執(zhí)行之前也需要做一些準(zhǔn)備,給代碼的執(zhí)行創(chuàng)建一個環(huán)境 —— 執(zhí)行上下文。

執(zhí)行上下文的類型

  • 全局執(zhí)行上下文:js代碼在在執(zhí)行前首先會創(chuàng)建一個全局執(zhí)行上下文,并且一個程序只有一個。
  • 函數(shù)執(zhí)行上下文:當(dāng)一個函數(shù)被調(diào)用時,就會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文,函數(shù)執(zhí)行上下文可以有多個。
  • eval函數(shù)執(zhí)行上下文

現(xiàn)在了解了執(zhí)行上下文,但是js代碼在執(zhí)行過程中怎么去找到不同的執(zhí)行上下文呢?那么就需要一個存放這些不同執(zhí)行上下文的地方 —— 執(zhí)行棧。執(zhí)行棧,顧名思義是一個棧的數(shù)據(jù)結(jié)構(gòu),有先入后出的特點。執(zhí)行棧棧頂?shù)膱?zhí)行上下文就是當(dāng)前的執(zhí)行上下文

現(xiàn)在兩個重要的概念都了解之后,我們學(xué)習(xí)一下執(zhí)行上下文到底是什么,它運作的過程是什么樣的?

執(zhí)行上下文的結(jié)構(gòu)

可以看到執(zhí)行上下文中主要包含四個部分:

  1. 變量環(huán)境用來存儲所有的var聲明的變量以及函數(shù)聲明;
  2. 詞法環(huán)境用來存儲let/const/class聲明的變量和全局對象;
  3. this綁定對于全局執(zhí)行上下文,如果是瀏覽器綁定的是window,如果是node.js指向的就是global,函數(shù)執(zhí)行上下文中的this指向取決于函數(shù)的調(diào)用方法;
  4. 外部引用其實就是作用域鏈,全局執(zhí)行上下文引用為null,函數(shù)執(zhí)行上下文指向函數(shù)定義時的詞法環(huán)境。

了解了執(zhí)行上下文的結(jié)構(gòu),我們來用一個小的代碼片段說明一下執(zhí)行上下文的創(chuàng)建和執(zhí)行。

創(chuàng)建和執(zhí)行步驟

  1. 創(chuàng)建全局執(zhí)行上下文,并加入執(zhí)行棧頂
  2. 分析:
    • 找到所有的非函數(shù)中的var聲明,在變量環(huán)境中創(chuàng)建綁定
    • 找到所有的頂級函數(shù)聲明,在變量環(huán)境中創(chuàng)建綁定
    • 找到頂級let、const、class聲明,在詞法環(huán)境中創(chuàng)建綁定
    • 塊級作用域中的變量聲明(let/const)創(chuàng)建新的詞法環(huán)境放入其中,函數(shù)聲明特殊,類似于let
  3. 變量名重復(fù)處理:let const class聲明的變量名不能重復(fù),他們與var function的名字也不能重復(fù);若varfunction 名字重復(fù),function聲明的函數(shù)名優(yōu)先
  4. 創(chuàng)建綁定:
    • 變量環(huán)境綁定:var初始化為undefined,函數(shù)初始化為函數(shù)對象,并且會把函數(shù)定義時的詞法環(huán)境保存到函數(shù)對象中。
    • 詞法環(huán)境綁定:let/const/class 創(chuàng)建但未初始化 —— 暫時性死區(qū)
  5. 執(zhí)行語句
var a = 10
function foo(){
  console.log(a)
  let a
}
foo()

以上面的代碼塊為例

  • 創(chuàng)建階段,創(chuàng)建全局的執(zhí)行上下文,在變量環(huán)境中存放a變量,并初始化為undefined,存放函數(shù)名為foo,初始化為函數(shù)對象,并且保存函數(shù)定義時的詞法環(huán)境(全局執(zhí)行上下文的詞法環(huán)境)。
  • 執(zhí)行階段,變量a賦值為10,foo函數(shù)被調(diào)用,會創(chuàng)建一個foo函數(shù)的執(zhí)行上下文壓入棧頂,這個執(zhí)行上下文的詞法環(huán)境的outer會指向foo函數(shù)初始化時保存在體內(nèi)的那個詞法環(huán)境,也就是指向函數(shù)定義時的執(zhí)行上下文的詞法環(huán)境(全局執(zhí)行上下文的詞法環(huán)境)。
    • 函數(shù)foo中又繼續(xù)上述的創(chuàng)建步驟,在詞法環(huán)境中存放變量a,未初始化。
    • 繼續(xù)執(zhí)行,console.log(a),當(dāng)前的執(zhí)行上下文就是foo函數(shù)的執(zhí)行上下文,所以會在foo函數(shù)的執(zhí)行上下文中去查找變量a,找到為未初始化的狀態(tài),所以最終會報錯。

到這里就說完了這個簡單代碼塊的執(zhí)行上下文的創(chuàng)建和執(zhí)行,那么會不會有一個疑問,我在上面的描述中著重強調(diào)函數(shù)執(zhí)行上下文的詞法環(huán)境的一個指向,但是似乎也沒有起到什么作用。

那么如果把函數(shù)foo中的let聲明去掉我們就可以看出這個指向的用處了!如果去掉的話,函數(shù)執(zhí)行上下文中就找不到這個變量a,那么就會沿著outer指向找到父級的執(zhí)行上下文查看其中是否有變量a,最后會輸出10。

這也就引出了作用域鏈

作用域

那么在介紹作用域鏈之前,我們先了解什么是作用域

作用域是解析(查找)變量名的一個集合,規(guī)定了變量和函數(shù)的可訪問范圍,也就是它定義了在哪里可以訪問什么變量。作用域就類比規(guī)則,當(dāng)前執(zhí)行上下文的詞法環(huán)境就類比實現(xiàn)規(guī)則的數(shù)據(jù)結(jié)構(gòu)。

作用域的類型

  • 全局作用域:在代碼任何地方都可訪問的作用域,對應(yīng)全局執(zhí)行上下文
  • 函數(shù)作用域:函數(shù)內(nèi)部創(chuàng)建的作用域,只在函數(shù)內(nèi)部可訪問。
  • 塊級作用域:ES6 引入 letconst后新增的作用域,由 {}代碼塊創(chuàng)建。

我們再舉一個例子來說明

function foo(){
    console.log(a)
}

funtion bar(){
    var a = 3
    foo()
}
var a = 2
bar()

以上的代碼最終會輸出2。

  • 創(chuàng)建階段,將函數(shù)foo、函數(shù)bar以及變量a存放在全局執(zhí)行上下文的變量環(huán)境中,并且初始化。
  • 執(zhí)行階段,首先給全局執(zhí)行上下文中的a賦值2,遇到bar(),創(chuàng)建一個新的bar函數(shù)執(zhí)行上下文,將該執(zhí)行上下文的outer指向全局執(zhí)行上下文的詞法環(huán)境,然后運行函數(shù)內(nèi)部的代碼。
    • 創(chuàng)建階段,函數(shù)內(nèi)部的變量a存放在bar函數(shù)執(zhí)行上下文的詞法環(huán)境中。
    • 執(zhí)行階段,變量a賦值為3,調(diào)用foo函數(shù)。這時候會繼續(xù)創(chuàng)建一個foo函數(shù)的執(zhí)行上下文,并且它的outer指向也是全局執(zhí)行上下文的詞法環(huán)境,然后foo函數(shù)中就一句代碼,輸出a,就會先去當(dāng)前執(zhí)行上下文中查找有沒有變量a,發(fā)現(xiàn)沒有;那么!就會沿著outer指向繼續(xù)往父級查找到全局執(zhí)行上下文,發(fā)現(xiàn)有變量a,值為2。

這就是作用域鏈!經(jīng)過以上的分析,我們也發(fā)現(xiàn)函數(shù)的作用域是函數(shù)定義時的作用域決定的,和函數(shù)調(diào)用時的作用域沒有關(guān)系,否則輸出就應(yīng)該是3。

現(xiàn)在對于函數(shù)作用域有了一定的了解后,我們繼續(xù)看塊級作用域。

遇到塊級作用域,也有以下幾個步驟:

  1. 創(chuàng)建新的記錄環(huán)境(詞法環(huán)境),連接在原來記錄之前
  2. 分析:
    • 所有的頂級函數(shù)聲明
    • let/const聲明
  3. 名字重復(fù)處理
  4. 創(chuàng)建綁定:
    • 登記function,初始化為函數(shù)對象
    • 登記let const,未初始化
  5. 執(zhí)行語句

細心的同學(xué)肯定已經(jīng)發(fā)現(xiàn),遇到塊級作用域{},我們的處理方式其實之前類似,但是不會創(chuàng)建新的執(zhí)行上下文了,而是改成了創(chuàng)建新的一個記錄指向原來的記錄。

用以下代碼塊來說明:

let inIf = 'out of statement'

if(true){
    let inIf = 'in of statement'
    console.log(inIf)
}

console.log(inIf)

最后會先輸出in of statement,再輸出 out of statement

如上圖,執(zhí)行完塊級作用域后,這個記錄就會銷毀,然后把原先的記錄重新接回。

那么我們再看下面這個例子,體會閉包的作用

var liList = []

for(var i = 0; i < 5; i++){
    liList[i] = function(){
        console.log(i)
     }
}

liList[0]()
liList[1]()
liList[2]()
liList[3]()
liList[4]()

以上代碼我們執(zhí)行會發(fā)現(xiàn),五個函數(shù)調(diào)用后輸出的結(jié)果都是5。這是因為var沒有塊級作用域,會直接把變量i存放在全局執(zhí)行上下文中,后面塊級作用域中定義的所有函數(shù)的environment屬性都指向全局執(zhí)行上下文的詞法環(huán)境,所以循環(huán)結(jié)束 i為5,每個函數(shù)調(diào)用創(chuàng)建的新的函數(shù)執(zhí)行上下文中的outer都指向全局執(zhí)行上下文,也會去全局中找i輸出都為5。

將上述代碼改為:

var liList = []

for(let i = 0; i < 5; i++){
    liList[i] = function(){
        console.log(i)
     }
}

liList[0]()
liList[1]()
liList[2]()
liList[3]()
liList[4]()

就可以利用閉包,讓調(diào)用輸出的i不再是共享的值。因為let聲明創(chuàng)建了塊級作用域,每次循環(huán)都會創(chuàng)建一個新的詞法環(huán)境存儲變量i,定義的五個函數(shù)的enviroment屬性也會指向不同的塊級作用域。最后調(diào)用沿著作用域鏈找到的也是不同的i值。

閉包

這個例子清晰地展示了閉包的核心機制:函數(shù)能夠記住并訪問其定義時所處的詞法作用域,即使該函數(shù)在其詞法作用域之外被調(diào)用。在上面的例子中,每個函數(shù)都通過閉包"記住"了定義時所在的塊級作用域(及其中的 i值)。

閉包的主要用途

  • 數(shù)據(jù)封裝與私有變量
  • 函數(shù)工廠與柯里化
  • 事件處理與異步編程

閉包的注意事項

  • 內(nèi)存泄漏風(fēng)險
  • 性能考慮

總結(jié) 

到此這篇關(guān)于JavaScript執(zhí)行機制、作用域及作用域鏈的文章就介紹到這了,更多相關(guān)JS執(zhí)行機制、作用域及作用域鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

博客| 房产| 安岳县| 平湖市| 烟台市| 赤峰市| 类乌齐县| 齐河县| 麻江县| 滦平县| 娱乐| 三江| 东安县| 舒兰市| 灯塔市| 安平县| 中卫市| 建水县| 阿图什市| 闸北区| 临潭县| 长治市| 梁河县| 育儿| 莱州市| 宝坻区| 内乡县| 满城县| 玉溪市| 西和县| 东阿县| 高唐县| 鲁山县| 巴彦淖尔市| 新竹县| 昌邑市| 林周县| 兖州市| 元朗区| 昆明市| 郴州市|