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

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

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

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

執(zhí)行上下文

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

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

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

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

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

執(zhí)行上下文的結構

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

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

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

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

  1. 創(chuàng)建全局執(zhí)行上下文,并加入執(zhí)行棧頂
  2. 分析:
    • 找到所有的非函數中的var聲明,在變量環(huán)境中創(chuàng)建綁定
    • 找到所有的頂級函數聲明,在變量環(huán)境中創(chuàng)建綁定
    • 找到頂級let、const、class聲明,在詞法環(huán)境中創(chuàng)建綁定
    • 塊級作用域中的變量聲明(let/const)創(chuàng)建新的詞法環(huán)境放入其中,函數聲明特殊,類似于let
  3. 變量名重復處理:let const class聲明的變量名不能重復,他們與var function的名字也不能重復;若varfunction 名字重復,function聲明的函數名優(yōu)先
  4. 創(chuàng)建綁定:
    • 變量環(huán)境綁定:var初始化為undefined,函數初始化為函數對象,并且會把函數定義時的詞法環(huán)境保存到函數對象中。
    • 詞法環(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,存放函數名為foo,初始化為函數對象,并且保存函數定義時的詞法環(huán)境(全局執(zhí)行上下文的詞法環(huán)境)。
  • 執(zhí)行階段,變量a賦值為10,foo函數被調用,會創(chuàng)建一個foo函數的執(zhí)行上下文壓入棧頂,這個執(zhí)行上下文的詞法環(huán)境的outer會指向foo函數初始化時保存在體內的那個詞法環(huán)境,也就是指向函數定義時的執(zhí)行上下文的詞法環(huán)境(全局執(zhí)行上下文的詞法環(huán)境)。
    • 函數foo中又繼續(xù)上述的創(chuàng)建步驟,在詞法環(huán)境中存放變量a,未初始化。
    • 繼續(xù)執(zhí)行,console.log(a),當前的執(zhí)行上下文就是foo函數的執(zhí)行上下文,所以會在foo函數的執(zhí)行上下文中去查找變量a,找到為未初始化的狀態(tài),所以最終會報錯。

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

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

這也就引出了作用域鏈

作用域

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

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

作用域的類型

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

我們再舉一個例子來說明

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

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

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

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

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

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

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

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

細心的同學肯定已經發(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),五個函數調用后輸出的結果都是5。這是因為var沒有塊級作用域,會直接把變量i存放在全局執(zhí)行上下文中,后面塊級作用域中定義的所有函數的environment屬性都指向全局執(zhí)行上下文的詞法環(huán)境,所以循環(huán)結束 i為5,每個函數調用創(chuàng)建的新的函數執(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]()

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

閉包

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

閉包的主要用途

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

閉包的注意事項

  • 內存泄漏風險
  • 性能考慮

總結 

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

相關文章

最新評論

金堂县| 上蔡县| 四子王旗| 宜良县| 兰坪| 定州市| 塔城市| 临澧县| 浦城县| 临泉县| 赤水市| 西丰县| 宜良县| 天柱县| 大方县| 无极县| 武宣县| 探索| 枝江市| 嘉义县| 长治市| 临泉县| 古浪县| 永川市| 营山县| 神木县| 皋兰县| 南平市| 阳信县| 凌源市| 华宁县| 阿巴嘎旗| 太保市| 奉化市| 共和县| 万山特区| 旬邑县| 余江县| 兴城市| 桐梓县| 青龙|