深入JavaScript系列(一) 詞法環境

2021-09-11 12:44:39 字數 4353 閱讀 8771

ecmascript規範中對詞法環境的描述如下:詞法環境是用來定義 基於詞法巢狀結構的ecmascript**內的識別符號與變數值和函式值之間的關聯關係 的一種規範型別。乙個詞法環境由環境記錄(environment record)和乙個可能為null的對外部詞法環境的引用(outer)組成。一般來說,詞法環境都與特定的ecmascript**語法結構相關聯,例如函式、**塊、trycatch中的catch從句,並且每次執行這類**時都會建立新的詞法環境。

簡而言之,詞法環境就是相應**塊內識別符號與值的關聯關係的體現。如果之前了解過作用域概念的話,和詞法環境是類似的(es6之後作用域概念變為詞法環境概念)。

詞法環境有兩個組成部分:

環境記錄(environment record):記錄相應**塊的識別符號繫結。

可以理解為相應**塊內的所有變數宣告、函式宣告(**塊若為函式還包括其形參)都儲存於此

對應es6之前的變數物件or活動物件,沒了解過的可忽略

對外部詞法環境的引用(outer):用於形成多個詞法環境在邏輯上的巢狀結構,以實現可以訪問外部詞法環境變數的能力。

詞法環境在邏輯上的巢狀結構對應es6之前的作用域鏈,沒了解過的可忽略

環境記錄有三種型別,分別是宣告式環境記錄(declarative environment record)物件式環境記錄(object environment record)全域性環境記錄(global environment record)

宣告式環境記錄是用來定義那些直接將識別符號與語言值繫結的es語法元素,例如變數,常量,let,class,module,import以及函式宣告等。

宣告式環境記錄有函式環境記錄(function environment record)和模組環境記錄(module environment record)兩種特殊型別。

函式環境記錄用於體現乙個函式的頂級作用域,如果函式不是箭頭函式,還會提供乙個this的繫結。

模組環境記錄用於體現乙個模組的外部作用域(即模組export所在環境),除了正常繫結外,也提供了所有引入的其他模組的繫結(即import的所有模組,這些繫結唯讀),因此我們可以直接訪問引入的模組。

每個物件式環境記錄都與乙個物件相關聯,這個物件叫做物件式環境記錄的binding object。可以理解為物件式環境記錄就是基於這個binding object,以物件屬性的形式進行識別符號繫結,識別符號與binding object的屬性名一一對應。

是物件就可以動態新增或者刪除屬性,所以物件環境記錄不存在不可變繫結。

物件式環境記錄用來定義那些將識別符號與某些物件屬性相繫結的es語法元素,例如with語句、全域性var宣告和函式宣告。

全域性環境記錄邏輯上來說是單個記錄,但是實際上可以看作是對乙個物件式環境記錄元件和乙個宣告式環境記錄元件的封裝。

之前說過每個物件式環境記錄都有乙個binding object,全域性環境記錄的物件式環境記錄binding object就是全域性物件,在瀏覽器內,全域性的thiswindow繫結都指向全域性物件。

全域性環境記錄的物件式環境記錄元件,繫結了所有內建全域性屬性、全域性的函式宣告以及全域性的var宣告。

所以這些繫結我們可以通過window.xxthis.xx獲取到。

全域性**的其他宣告(如let、const、class等)則繫結在宣告式環境記錄元件內,由於宣告式環境記錄元件並不是基於簡單的物件形式來實現繫結,所以這些宣告我們並不能通過全域性物件的屬性來訪問

首先要說明兩點:

全域性環境的外部詞法環境引用為null

乙個詞法環境可以作為多個詞法環境的外部環境。例如全域性宣告了多個函式,則這些函式詞法環境的外部詞法環境引用都指向全域性環境。

外部詞法環境的引用將乙個詞法環境和其外部詞法環境鏈結起來,外部詞法環境又擁有對其自身的外部詞法環境的引用。這樣就形成乙個鏈式結構,這裡我們稱其為環境鏈(即es6之前的作用域鏈),全域性環境是這條鏈的頂端。

環境鏈的存在是為了識別符號的解析,通俗的說就是查詢變數。首先在當前環境查詢變數,找不到就去外部環境找,還找不到就去外部環境的外部環境找,以此類推,直到找到,或者到環境鏈頂端(全域性環境)還未找到則丟擲referenceerror

識別符號解析:在環境鏈中解析變數(繫結)的過程,

我們使用偽**來模擬一下識別符號解析的過程。

resolvebinding(name[, lexicalenvironment])  is not defined`)

}// 首次查詢,將當前詞法環境設定為解析環境

if (typeof lexicalenvironment === 'undefined')

// 檢查環境的環境記錄中是否有此繫結

let i***ist = lexicalenvironment.enviromentrecord.hasbinding(name)

// 如果有則返回繫結值,沒有則去外層環境查詢

if (i***ist) else

}複製**

上面講了那麼多理論知識,現在我們結合**來複習,有以下全域性**:

var x = 10

let y = 20

const z = 30

class

person

{}function

foo()

foo()

複製**

現在我們有了乙個全域性詞法環境和foo函式詞法環境(以下內容均為抽象偽**):

// 全域性詞法環境

globalenvironment = ,

// 物件式環境記錄的,繫結物件為全域性物件,故其中的繫結可以通過訪問全域性物件的屬性來獲得

objectenvironmentrecord:

}}// foo函式詞法環境

foofunctionenviroment =

}複製**

由於全域性環境記錄是宣告式環境記錄和物件式環境記錄的封裝,所以全域性識別符號的解析與其他環境的識別符號解析有所不同,下面介紹全域性識別符號解析的步驟(偽**):

function

getglobalbingin**alue(name)

let objrec = rec.objectrecord

if (objrec.hasbinding(name) === true)

throw

referenceerror(`$ is not defined`)

}複製**

可以看到讀取全域性變數時,先檢索宣告式環境記錄,再檢索物件式環境記錄。這樣就會出現一些有趣的現象:

letconstclass等宣告的變數如果存在同名var變數或同名函式宣告,就會報錯(之後的文章中會具體介紹)。但是如果我們使用letconstclass宣告變數,然後直接通過給全域性物件新增乙個同名屬性,則可以繞過此類報錯。

此時全域性環境記錄的宣告式環境記錄和物件式環境記錄內都有此識別符號的繫結,但是我們訪問時由於先檢索宣告式環境記錄,所以物件式環境記錄內的繫結會被遮蔽,要想訪問只能通過訪問全域性物件屬性的方法訪問。

準備將之前寫的部分深入ecmascript文章重寫,加深自己理解,使內容更有乾貨,目錄結構也更合理。

深入ecmascript系列目錄位址(持續更新中...)

菜鳥一枚,如果有疑問或者發現錯誤,可以在相應的 issues 進行提問或勘誤,與大家共同進步。

JavaScript系列 深入之從原型到原型鏈

本文詳情 建構函式建立物件 先使用建構函式建立乙個物件 function person var person new person person.name mit console.log person.name 在這個例子中,person 就是乙個建構函式,我們使用 new 建立了乙個例項物件 pe...

JavaScript系列 ECMAScript語句

1 if語句 if語句語法 if condition statement1 else statement2 2 迭代語句 迭代語句又叫迴圈語句,宣告一組需要反覆執行的命令,直到滿足了某些條件為止。ecmascript為了這種處理提供了四種迭代語句。2.1 do while語句 do while語句是...

React Hooks 深入系列

本文基於近段時間對 hooks 碎片化的理解作一次簡單梳理,個人部落格。同時歡迎關注基於 hooks 構建的 ui 元件庫 snake design。在 class 已經融入 react 生態的節點下,react 推出的 hooks 具有如下優勢 寫出 useeffect 的所用到的依賴 在以下 d...