摘要:不包括作為其嵌套函數的被解析的源代碼。作用域鏈當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。棧結構最頂層的執行環境稱為當前運行的執行環境,最底層是全局執行環境。無限制函數上下文。或者拋出異常退出一個執行環境。
前言
其實規范這東西不是給人看的,它更多的是給語言實現者提供參考。但是當碰到問題找不到答案時,規范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發和思考,如果只讀一章 Javascript 規范,大神們覺得非第10章莫屬。
我們來試試看,這次選用的是 ECMA2.2的 5.1 版,整個規范才200頁, 而第10章共10頁,可以感受到 Javascript 的精簡,目前的版本加了太多 ES6 的東西,讓人望而生畏。
資料地址:http://www.ecma-international...
任務閱讀 ECMA262 5.1 第10章 Executable Code and Execution Contexts (可執行代碼與執行上下文)
你能針對這章內容提出問題嗎? 即知道答案找出問題。
你能使用圖來更形象地表達文章內容嗎?
原汁原味 ECMAScript 5.1 英文版
平易近人 ECMAScript 5.1 中文版
像 v8 等 JavaScript 引擎都是按照 ecma-262 的規范來實現的,JavaScript 引擎在解釋 JavaScript 代碼時,將可執行代碼分為了三種。分別是:
全局代碼
代碼加載時首先進入的環境。 例如加載外部的 JavaScript 文件或者本地 標簽內的代碼。 但不包括任何 function 體內的代碼。
函數代碼
是指作為 function 被解析的源代碼。 不包括作為其嵌套函數的 function 被解析的源代碼。 因為 JavaScript 函數中還可以嵌套函數,因此這也是三種可執行代碼中最復雜的一種。
eval代碼
指的是傳遞給 eval 內置函數的代碼。
注:不了解 eval(string) 的小伙伴,請參考 eval() - JavaScript | MDN
詞法環境 (Lexical Environments)當 JavaScript 引擎開始執行(進入)一段可執行代碼之后,會生成一個執行環境(Execution Context),或執行上下文。引擎用執行環境來維護執行當前代碼所需要的變量聲明、this指向等。
詞法環境 是執行環境的三個組成的狀態之一。
官方解釋:詞法環境是用來定義特定變量和函數標識符的。一個詞法環境由一個環境記錄項和可能為空的外部詞法環境引用構成。
通常詞法環境會與 ECMAScript 代碼諸如 函數聲明(FunctionDeclaration)、WithStatement 或者 TryStatement 的 Catch 塊這樣的特定句法結構相聯系,且類似代碼每次執行都會有一個新的詞法環境被創建出來。
外部詞法環境引用 用于表示詞法環境的邏輯嵌套關系模型。(內部)詞法環境的外部引用是邏輯上包含內部詞法環境的詞法環境。外部詞法環境自然也可能有多個內部詞法環境。
例如,如果一個 FunctionDeclaration 包含兩個嵌套的 FunctionDeclaration,那么每個內嵌函數的詞法環境都是外部函數本次執行所產生的詞法環境。
環境記錄項 又可以分為兩種聲明式環境記錄項和對象式環境記錄項。
聲明式環境記錄項 用于標識標識符和函數聲明、變量聲明、catch 語句等語法元素的綁定。對象式環境記錄項 主要用于定義那些將標識符與具體對象的屬性綁定的語法元素。
咬文嚼字,不好理解?
通俗點講: 詞法環境就是 JavaScript 引擎在執行代碼過程中用來標識函數聲明、變量聲明這一類的。我們每次聲明一個函數,或者使用 with 、catch語句的時候,就會有新的詞法環境被創建出來。全局詞法環境的外部詞法環境就是空的,因為他已經是最外層的詞法環境了。
我們用個例子來說明詞法環境:
var x = 10; function foo(y){ var z = 30; function bar(q){ return x+y+z+q; } return bar; } var bar = foo(20); bar(40);詞法環境的運算
給出一個標識符字符串,首先在當前的詞法環境內尋找,如果存在,返回引用的標識符字符串,如果不存在,再在當前詞法環境的外部詞法環境尋找。
咦,怎么感覺和作用域鏈的概念很相似?他們有什么關系嗎?
執行環境 當執行流進入一個函數時,函數的環境會被推入一個環境棧中。當函數執行之后,環境棧將其彈出,把控制權返回給之前的執行環境。
作用域鏈 當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。其用途是保證對執行環境有權訪問的所有變量和函數的有序訪問。一個包含環境的變量對象到另一個包含環境的變量對象,最后到全局執行環境的變量對象。
函數只要被創建,就會有自己的“地盤”,有自己的作用域。但是只有函數被執行的時候,才會有自己的執行環境。函數執行完畢的時候,執行環境就會退出。而且一個作用域下可能存在多個執行環境,比如閉包。
小總結1、詞法環境分為了兩部分:環境記錄項和外部詞法環境。
2、環境記錄項根據綁定的 ECMA 腳本元素的不同也分為了兩部分。
3、函數聲明或者使用 with 、catch語句時,就會有新的詞法環境被創建出來。
如果我們的 JavaScript 程序有各種函數,函數之間還有嵌套的情況,那 JavaScript 引擎怎么解釋各種聲明和執行上下文哪?
當控制器轉入 ECMA 腳本的可執行代碼時,上文已經說了有三種可執行代碼,不管進入哪一種控制器都會進入一個執行環境。多個執行環境在邏輯上形成一個棧結構。棧結構最頂層的執行環境稱為當前運行的執行環境,最底層是全局執行環境。
用一張圖解釋
因為 JS 引擎被實現為單線程,也就是同一時間只能發生一件事情,其他的行為就會依次排隊。
你可以有任意多個函數執行環境,每次調用函數創建一個新的執行環境,會創建一個私有作用域,函數內部聲明的任何變量都不能在當前函數作用域外部直接訪問。函數能訪問當前執行環境外面的變量聲明,但在外部執行環境不能訪問內部的變量/函數聲明。
小總結關于執行棧(調用棧)
單線程。 同步執行。 一個全局上下文。 無限制函數上下文。 每次函數被調用創建新的執行上下文,包括調用自己。 return 或者拋出異常退出一個執行環境。
我們用一個具體的函數理解:
function foo(i) { if (i < 0) return; console.log("begin:" + i); foo(i - 1); console.log("end:" + i); } foo(2); // 輸出: // begin:2 // begin:1 // begin:0 // end:0 // end:1 // end:2
代碼的執行流程進入內部函數,創建一個新的執行上下文并把它壓入執行棧的頂部。瀏覽器總會執行位于棧頂的執行上下文,一旦當前上下文函數執行結束,它將被從棧頂彈出,并將上下文控制權交給當前的棧。 這樣,堆棧中的上下文就會被依次執行并且彈出堆棧,直到回到全局的上下文。
執行環境包含所有用于追蹤與其相關的代碼的執行進度的狀態。精確地說,每個執行環境包含如下表列出的組件。
執行環境的三個狀態
組件 | 作用目的 |
---|---|
詞法環境 | 指定一個詞法環境對象,用于解析該執行環境內的代碼創建的標識符引用。 |
變量環境 | 指定一個詞法環境對象,其環境數據用于保存由該執行環境內的代碼通過 變量表達式 和 函數表達式 創建的綁定。 |
this 綁定 | 指定該執行環境內的 ECMA 腳本代碼中 this 關鍵字所關聯的值。 |
當創建一個執行環境時,其詞法環境組件和變量環境組件最初是同一個值。在該執行環境相關聯的代碼的執行過程中,變量環境組件永遠不變,而詞法環境組件有可能改變。變量環境的不變和詞法環境的可能改變都是指引用的改變。
建立執行環境進入全局代碼解釋執行全局代碼或使用 eval 函數輸入的代碼會創建并進入一個新的執行環境。每次調用 ECMA 腳本代碼定義的函數也會建立并進入一個新的執行環境,即便函數是自身遞歸調用的。
每一次 return 都會退出一個執行環境。拋出異常也可退出一個或多個執行環境。
當控制流進入一個執行環境時,會設置該執行環境的 this 綁定組件,定義變量環境和初始詞法環境,并執行聲明式綁定初始化過程。以上這些步驟的嚴格執行方式由進入的代碼的類型決定。
執行以下步驟:
1、將變量環境設置為 全局環境 。 2、將詞法環境設置為 全局環境 。 3、將 this 綁定設置為 全局對象 。 4、使用全局代碼執行聲明式綁定初始化化步驟。進入函數代碼
當控制流根據一個函數對象 F、調用者提供的 thisArg 以及調用者提供的 argumentList,進入函數代碼的執行環境時,執行以下步驟
如果函數代碼是嚴格模式下的代碼,設 this 綁定 為 thisArg。 否則如果 thisArg 是 null 或 undefined,則設 this 綁定 為全局對象。 否則如果 Type(thisArg) 的結果不為 Object,則設 this 綁定 為 ToObject(thisArg)。 否則設 this 綁定 為 thisArg。 以 F 的 [[Scope]] 內部屬性為參數調用 NewDeclarativeEnvironment(新建聲明式詞法環境),并令 localEnv 為調用的結果。 設 詞法環境組件 為 localEnv。 設 變量環境組件 為 localEnv。 令 code 為 F 的 [[Code]] 內部屬性的值。 使用函數代碼 code 和 argumentList 執行聲明式綁定初始化化步驟。
我們用偽代碼表示一下:
if(是 嚴格模式) { this = thisArg } else if(thisArg === null || thisArg === undefined) { this = window } else if(typeof thisArg != "object") { this = Object(thisArg) } else { this = thisArg }
哎,這里的 thisArg 指的是什么?上文說了 thisArg 來自于函數的調用者。
這里代表函數的 apply,call,bind 等設置 this 綁定的參數:
通過 call 或者 apply 調用函數時,thisArg 的值比較明顯,為傳入的第一個參數。
Function.prototype.apply (thisArg, argArray) Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )
當然 thisArg 還有其他的可能,具體的可以參考這篇文章 Javascript this 解析
對上面的偽代碼做一下解釋:
嚴格模式: 也就是說,在嚴格模式下,this 只能為 thisArg,而當 thisArg 為 undefined 時,this 就是 undefined ,而不是 window。
非嚴格模式: 如果 thisArg 是 null (如 fun.call(null)) 或 undefined (直接調用函數),則 this 為全局對象,瀏覽器里就是 window。
否則,如果 傳入了 thisArg, 但不是個對象,則把它轉為對象,并賦給 this,比如,當 fun.call("hhh") 時,打印 fun 內的 this 為
String {0: "h", 1: "h", 2: "h", length: 3, [[PrimitiveValue]]: "hhh"}
否則 ,也就是僅剩的一種情況,顯式的傳入了一個對象作為 thisArg 參數的情況下,設 this 綁定為 thisArg。
聲明式綁定初始化每個執行環境都有一個關聯的 變量環境。當在一個執行環境下評估一段 ECMA 腳本時,變量和函數定義會以綁定的形式添加到這個 變量環境 的環境記錄中。對于函數代碼,參數也同樣會以綁定的形式添加到這個 變量環境 的環境記錄中。
總結ECMAScript 代碼的執行由運行環境來完成。不同的運行環境可能采取不同的執行方式,但基本的流程是相同的。如瀏覽器在解析 HTML 頁面中遇到 元素時,會下載對應的代碼來運行,或直接執行內嵌的代碼。代碼的基本執行方式是從上到下,順序執行。在調用函數之后,代碼的執行會進入一個執行上下文之中。由于在一個函數的執行過程中會調用其他的函數,執行過程中的活動執行上下文會形成一個堆棧結構。在棧頂的是當前正在執行的代碼。當函數返回時,會退出當前的執行上下文,而回到之前的執行上下文中。如果代碼執行中出現異常,則可能從多個執行上下文中退出。
在代碼執行過程中很重要的一步是標識符的解析。比如當執行過程中遇到語句 alert(val) 時,首先要做的是解析標識符 val 的值。ECMAScript 不同于 Java 和 C/C++ 等語言,在進行標識符解析時需要利用詞法環境并與函數調用方式相關。具體來說,標識符解析由當前代碼所對應的執行上下文來完成。為了描述標識符的解析過程,ECMAScript 規范中使用了詞法環境的概念來進行描述。一個詞法環境描述了標識符與變量或函數之間的對應關系。一個詞法環境由兩個部分組成:一部分是記錄標識符與變量之間的綁定關系的環境記錄,另一部分是包圍當前詞法環境的外部詞法環境。環境記錄可以看成是一個標識符與變量或函數之間的映射表。不同詞法環境之間可以互相嵌套,而內部詞法環境會持有一個包圍它的外部詞法環境的引用。在進行標識符解析時,如果當前詞法環境中找不到標識符所對應的變量或函數,則使用外部詞法環境來嘗試解析。遞歸查找下去,直到解析成功或外部詞法環境為 null。
具體來說,根據標識符關聯方式的不同,環境記錄可以進一步分成兩類。兩種類型分別對應不同的 ECMAScript 中不同的語法結構。當使用這些語法結構時,會對環境記錄中的內容產生影響,進而影響標識符的解析過程。第一類環境記錄是聲明式環境記錄。顧名思義,聲明式環境記錄用來綁定 ECMAScript 代碼中的變量聲明。當使用 var 聲明變量或使用類似 function func(){} 的形式聲明函數時,對應的變量或函數會被綁定到相應的環境記錄中。另一類環境記錄是對象環境記錄。對象環境記錄并不綁定具體的變量或函數,而是綁定另外一個對象中的屬性。對象環境變量主要用來描述 ECMAScript 中 with 操作符的行為。
每個執行上下文會對應兩個不同的詞法環境。一個是用來進行標識符解析的詞法環境,可能隨著代碼的執行而發生變化;另外一個是包含執行上下文對應的作用域中的變量或函數聲明的詞法環境。
提問讀完這篇文章,問問自己,能夠回答下面的問題嗎?
1、ECMAScript 中可執行代碼有幾種?
2、什么情況下會創建一個執行環境?
3、什么情況下會退出一個執行環境?
4、作用域鏈和執行環境的關系?
5、執行環境的存在是為了解決什么?
6、詞法環境和變量環境的異同?
7、this 的綁定的幾種情況?
ES5/可執行代碼與執行環境
深入理解JavaScript系列(11):執行上下文(Execution Contexts)
了解JavaScript的執行上下文
深入理解JavaScript執行上下文、函數堆棧、提升的概念
關于js作用域那些事
從 ECMAScript 規范來看 JS 的 this 綁定規則
深入探討 ECMAScript 規范
JavaScript欲速則不達—通過解析過程了解JavaScript
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88516.html
摘要:標準對象,語義由本規范定義的對象。三個冒號作為分隔符分割數字字符串文法的產生式。所有因為帶來的問題,基本上是占著茅坑不拉屎的行為導致。以數組測試操作為例,標準中的描述如下相對于來說,規范中增加了對的處理。 前言 本文是對《ECMAScript 2018 Language Specification》的解讀。本文是對標準的概述性導讀,不是對 ES2018特性的詳細描述,也不會針對某個技術...
摘要:不同執行上下文中的變量對象對于所有的執行上下文類型,變量對象的一些操作比如變量實例化和行為是共通的。從這點看,將變量對象看作是抽象的基礎的東西很便利。全局上下文的變量對象現在,應該先給出全局對象的定義。 原文地址 作者的話 有很多文章已經對ECMAScript的核心概念做了詳盡解讀。本系列文章翻譯自Dmitry Soshnikov的個人網站,相信不少人已經看過原文或者譯文。原文簡潔易懂...
摘要:邏輯上講,活躍的執行上下文集合組成了一個棧。棧底是全局上下文,棧頂是當前活躍執行上下文。舉個例子,我們將執行上下文定義成一個數組。一個拋出但是沒有捕獲的異常可能導致退出一個或多個執行上下文代碼代碼更加復雜。 原文地址 作者的話 有很多文章已經對ECMAScript的核心概念做了詳盡解讀。本系列文章翻譯自Dmitry Soshnikov的個人網站,相信不少人已經看過原文或者譯文。原文簡潔...
摘要:本文總結了的各種情況,并從規范的角度探討了的具體實現,希望對大家理解有所幫助。規范規范里面詳細介紹了的實現細節,通過閱讀規范,我們可以更準確的理解上述四種情況到底是怎么回事。由于本人能力有限,如有理解錯誤的地方還望指出。 this是面向對象編程中的一個概念,它一般指向當前方法調用所在的對象,這一點在java、c++這類比較嚴格的面向對象編程語言里是非常明確的。但是在javascript...
摘要:另一方面,我不建議初次接觸的開發人員閱讀規范。在維護語言的最新規范。在這一點上,我想指出的是,絕對沒有人從上到下閱讀規范。拓展閱讀由于的定義,中的細節如冒泡錯誤,直到塊在規范中不存在。換句話說,會轉發中拋出的錯誤,并終止其余的步驟。 翻譯自:How to Read the ECMAScript Specification Ecmascript 語言規范 The ECMAScr...
閱讀 1670·2021-10-29 13:11
閱讀 831·2021-09-22 10:02
閱讀 1690·2021-08-20 09:35
閱讀 1553·2019-08-30 15:54
閱讀 2462·2019-08-30 15:44
閱讀 1385·2019-08-29 16:52
閱讀 1101·2019-08-23 12:56
閱讀 759·2019-08-22 15:16