成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

由 ECMA 規(guī)范解讀 Javascript 可執(zhí)行上下文概念

daryl / 2387人閱讀

摘要:不包括作為其嵌套函數(shù)的被解析的源代碼。作用域鏈當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。棧結(jié)構(gòu)最頂層的執(zhí)行環(huán)境稱為當(dāng)前運行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。無限制函數(shù)上下文?;蛘邟伋霎惓M顺鲆粋€執(zhí)行環(huán)境。

前言

其實規(guī)范這東西不是給人看的,它更多的是給語言實現(xiàn)者提供參考。但是當(dāng)碰到問題找不到答案時,規(guī)范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發(fā)和思考,如果只讀一章 Javascript 規(guī)范,大神們覺得非第10章莫屬。

我們來試試看,這次選用的是 ECMA2.2的 5.1 版,整個規(guī)范才200頁, 而第10章共10頁,可以感受到 Javascript 的精簡,目前的版本加了太多 ES6 的東西,讓人望而生畏。

資料地址:http://www.ecma-international...

任務(wù)

閱讀 ECMA262 5.1 第10章 Executable Code and Execution Contexts (可執(zhí)行代碼與執(zhí)行上下文)
你能針對這章內(nèi)容提出問題嗎? 即知道答案找出問題。
你能使用圖來更形象地表達文章內(nèi)容嗎?

開始我們的探險之旅

原汁原味 ECMAScript 5.1 英文版
平易近人 ECMAScript 5.1 中文版

可執(zhí)行代碼類型

v8JavaScript 引擎都是按照 ecma-262 的規(guī)范來實現(xiàn)的,JavaScript 引擎在解釋 JavaScript 代碼時,將可執(zhí)行代碼分為了三種。分別是:

全局代碼

代碼加載時首先進入的環(huán)境。
例如加載外部的 JavaScript 文件或者本地  標簽內(nèi)的代碼。
但不包括任何 function 體內(nèi)的代碼。

函數(shù)代碼

是指作為 function 被解析的源代碼。
不包括作為其嵌套函數(shù)的 function 被解析的源代碼。
因為 JavaScript 函數(shù)中還可以嵌套函數(shù),因此這也是三種可執(zhí)行代碼中最復(fù)雜的一種。

eval代碼

指的是傳遞給 eval 內(nèi)置函數(shù)的代碼。

注:不了解 eval(string) 的小伙伴,請參考 eval() - JavaScript | MDN

當(dāng) JavaScript 引擎開始執(zhí)行(進入)一段可執(zhí)行代碼之后,會生成一個執(zhí)行環(huán)境(Execution Context),或執(zhí)行上下文。引擎用執(zhí)行環(huán)境來維護執(zhí)行當(dāng)前代碼所需要的變量聲明、this指向等。

詞法環(huán)境 (Lexical Environments)

詞法環(huán)境 是執(zhí)行環(huán)境的三個組成的狀態(tài)之一。

官方解釋:詞法環(huán)境是用來定義特定變量和函數(shù)標識符的。一個詞法環(huán)境由一個環(huán)境記錄項和可能為空的外部詞法環(huán)境引用構(gòu)成。

通常詞法環(huán)境會與 ECMAScript 代碼諸如 函數(shù)聲明(FunctionDeclaration)、WithStatement 或者 TryStatementCatch 塊這樣的特定句法結(jié)構(gòu)相聯(lián)系,且類似代碼每次執(zhí)行都會有一個新的詞法環(huán)境被創(chuàng)建出來。

外部詞法環(huán)境引用 用于表示詞法環(huán)境的邏輯嵌套關(guān)系模型。(內(nèi)部)詞法環(huán)境的外部引用是邏輯上包含內(nèi)部詞法環(huán)境的詞法環(huán)境。外部詞法環(huán)境自然也可能有多個內(nèi)部詞法環(huán)境。

例如,如果一個 FunctionDeclaration 包含兩個嵌套的 FunctionDeclaration,那么每個內(nèi)嵌函數(shù)的詞法環(huán)境都是外部函數(shù)本次執(zhí)行所產(chǎn)生的詞法環(huán)境。

環(huán)境記錄項 又可以分為兩種聲明式環(huán)境記錄項對象式環(huán)境記錄項。

聲明式環(huán)境記錄項 用于標識標識符和函數(shù)聲明變量聲明、catch 語句等語法元素的綁定。對象式環(huán)境記錄項 主要用于定義那些將標識符與具體對象的屬性綁定的語法元素。

咬文嚼字,不好理解?

通俗點講: 詞法環(huán)境就是 JavaScript 引擎在執(zhí)行代碼過程中用來標識函數(shù)聲明、變量聲明這一類的。我們每次聲明一個函數(shù),或者使用 withcatch語句的時候,就會有新的詞法環(huán)境被創(chuàng)建出來。全局詞法環(huán)境的外部詞法環(huán)境就是空的,因為他已經(jīng)是最外層的詞法環(huán)境了。

我們用個例子來說明詞法環(huán)境:

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);

詞法環(huán)境的運算

給出一個標識符字符串,首先在當(dāng)前的詞法環(huán)境內(nèi)尋找,如果存在,返回引用的標識符字符串,如果不存在,再在當(dāng)前詞法環(huán)境的外部詞法環(huán)境尋找。

咦,怎么感覺和作用域鏈的概念很相似?他們有什么關(guān)系嗎?

執(zhí)行環(huán)境 當(dāng)執(zhí)行流進入一個函數(shù)時,函數(shù)的環(huán)境會被推入一個環(huán)境棧中。當(dāng)函數(shù)執(zhí)行之后,環(huán)境棧將其彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。

作用域鏈 當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。其用途是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。一個包含環(huán)境的變量對象到另一個包含環(huán)境的變量對象,最后到全局執(zhí)行環(huán)境的變量對象。

函數(shù)只要被創(chuàng)建,就會有自己的“地盤”,有自己的作用域。但是只有函數(shù)被執(zhí)行的時候,才會有自己的執(zhí)行環(huán)境。函數(shù)執(zhí)行完畢的時候,執(zhí)行環(huán)境就會退出。而且一個作用域下可能存在多個執(zhí)行環(huán)境,比如閉包。

小總結(jié)

1、詞法環(huán)境分為了兩部分:環(huán)境記錄項和外部詞法環(huán)境。
2、環(huán)境記錄項根據(jù)綁定的 ECMA 腳本元素的不同也分為了兩部分。
3、函數(shù)聲明或者使用 withcatch語句時,就會有新的詞法環(huán)境被創(chuàng)建出來。

執(zhí)行環(huán)境(Execution Contexts)

如果我們的 JavaScript 程序有各種函數(shù),函數(shù)之間還有嵌套的情況,那 JavaScript 引擎怎么解釋各種聲明和執(zhí)行上下文哪?

當(dāng)控制器轉(zhuǎn)入 ECMA 腳本的可執(zhí)行代碼時,上文已經(jīng)說了有三種可執(zhí)行代碼,不管進入哪一種控制器都會進入一個執(zhí)行環(huán)境。多個執(zhí)行環(huán)境在邏輯上形成一個棧結(jié)構(gòu)。棧結(jié)構(gòu)最頂層的執(zhí)行環(huán)境稱為當(dāng)前運行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。

用一張圖解釋

因為 JS 引擎被實現(xiàn)為單線程,也就是同一時間只能發(fā)生一件事情,其他的行為就會依次排隊。

你可以有任意多個函數(shù)執(zhí)行環(huán)境,每次調(diào)用函數(shù)創(chuàng)建一個新的執(zhí)行環(huán)境,會創(chuàng)建一個私有作用域,函數(shù)內(nèi)部聲明的任何變量都不能在當(dāng)前函數(shù)作用域外部直接訪問。函數(shù)能訪問當(dāng)前執(zhí)行環(huán)境外面的變量聲明,但在外部執(zhí)行環(huán)境不能訪問內(nèi)部的變量/函數(shù)聲明。

小總結(jié)

關(guān)于執(zhí)行棧(調(diào)用棧)

單線程。
同步執(zhí)行。
一個全局上下文。
無限制函數(shù)上下文。
每次函數(shù)被調(diào)用創(chuàng)建新的執(zhí)行上下文,包括調(diào)用自己。
return 或者拋出異常退出一個執(zhí)行環(huán)境。

我們用一個具體的函數(shù)理解:

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

代碼的執(zhí)行流程進入內(nèi)部函數(shù),創(chuàng)建一個新的執(zhí)行上下文并把它壓入執(zhí)行棧的頂部。瀏覽器總會執(zhí)行位于棧頂?shù)膱?zhí)行上下文,一旦當(dāng)前上下文函數(shù)執(zhí)行結(jié)束,它將被從棧頂彈出,并將上下文控制權(quán)交給當(dāng)前的棧。 這樣,堆棧中的上下文就會被依次執(zhí)行并且彈出堆棧,直到回到全局的上下文。

執(zhí)行環(huán)境包含所有用于追蹤與其相關(guān)的代碼的執(zhí)行進度的狀態(tài)。精確地說,每個執(zhí)行環(huán)境包含如下表列出的組件。

執(zhí)行環(huán)境的三個狀態(tài)

組件 作用目的
詞法環(huán)境 指定一個詞法環(huán)境對象,用于解析該執(zhí)行環(huán)境內(nèi)的代碼創(chuàng)建的標識符引用。
變量環(huán)境 指定一個詞法環(huán)境對象,其環(huán)境數(shù)據(jù)用于保存由該執(zhí)行環(huán)境內(nèi)的代碼通過 變量表達式 和 函數(shù)表達式 創(chuàng)建的綁定。
this 綁定 指定該執(zhí)行環(huán)境內(nèi)的 ECMA 腳本代碼中 this 關(guān)鍵字所關(guān)聯(lián)的值。

當(dāng)創(chuàng)建一個執(zhí)行環(huán)境時,其詞法環(huán)境組件和變量環(huán)境組件最初是同一個值。在該執(zhí)行環(huán)境相關(guān)聯(lián)的代碼的執(zhí)行過程中,變量環(huán)境組件永遠不變,而詞法環(huán)境組件有可能改變。變量環(huán)境的不變和詞法環(huán)境的可能改變都是指引用的改變。

建立執(zhí)行環(huán)境

解釋執(zhí)行全局代碼或使用 eval 函數(shù)輸入的代碼會創(chuàng)建并進入一個新的執(zhí)行環(huán)境。每次調(diào)用 ECMA 腳本代碼定義的函數(shù)也會建立并進入一個新的執(zhí)行環(huán)境,即便函數(shù)是自身遞歸調(diào)用的。

每一次 return 都會退出一個執(zhí)行環(huán)境。拋出異常也可退出一個或多個執(zhí)行環(huán)境。

當(dāng)控制流進入一個執(zhí)行環(huán)境時,會設(shè)置該執(zhí)行環(huán)境的 this 綁定組件,定義變量環(huán)境和初始詞法環(huán)境,并執(zhí)行聲明式綁定初始化過程。以上這些步驟的嚴格執(zhí)行方式由進入的代碼的類型決定。

進入全局代碼

執(zhí)行以下步驟:

1、將變量環(huán)境設(shè)置為 全局環(huán)境 。 
2、將詞法環(huán)境設(shè)置為 全局環(huán)境 。
3、將 this 綁定設(shè)置為 全局對象 。
4、使用全局代碼執(zhí)行聲明式綁定初始化化步驟。
進入函數(shù)代碼

當(dāng)控制流根據(jù)一個函數(shù)對象 F、調(diào)用者提供的 thisArg 以及調(diào)用者提供的 argumentList,進入函數(shù)代碼的執(zhí)行環(huán)境時,執(zhí)行以下步驟

如果函數(shù)代碼是嚴格模式下的代碼,設(shè) this 綁定 為 thisArg。
否則如果 thisArg 是 null 或 undefined,則設(shè) this 綁定 為全局對象。
否則如果 Type(thisArg) 的結(jié)果不為 Object,則設(shè) this 綁定 為 ToObject(thisArg)。
否則設(shè) this 綁定 為 thisArg。
以 F 的 [[Scope]] 內(nèi)部屬性為參數(shù)調(diào)用 NewDeclarativeEnvironment(新建聲明式詞法環(huán)境),并令 localEnv 為調(diào)用的結(jié)果。
設(shè) 詞法環(huán)境組件 為 localEnv。
設(shè) 變量環(huán)境組件 為 localEnv。
令 code 為 F 的 [[Code]] 內(nèi)部屬性的值。
使用函數(shù)代碼 code 和 argumentList 執(zhí)行聲明式綁定初始化化步驟。

我們用偽代碼表示一下:

if(是 嚴格模式) {
    this = thisArg
} else if(thisArg === null || thisArg === undefined) {
    this = window
} else if(typeof thisArg != "object") {
    this = Object(thisArg)
} else {
    this = thisArg
}

哎,這里的 thisArg 指的是什么?上文說了 thisArg 來自于函數(shù)的調(diào)用者。

這里代表函數(shù)的 apply,callbind 等設(shè)置 this 綁定的參數(shù):

通過 call 或者 apply 調(diào)用函數(shù)時,thisArg 的值比較明顯,為傳入的第一個參數(shù)。

Function.prototype.apply (thisArg, argArray)

Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )

當(dāng)然 thisArg 還有其他的可能,具體的可以參考這篇文章 Javascript this 解析

對上面的偽代碼做一下解釋:

嚴格模式: 也就是說,在嚴格模式下,this 只能為 thisArg,而當(dāng) thisArgundefined 時,this 就是 undefined ,而不是 window。

非嚴格模式: 如果 thisArgnull (如 fun.call(null)) 或 undefined (直接調(diào)用函數(shù)),則 this 為全局對象,瀏覽器里就是 window

否則,如果 傳入了 thisArg, 但不是個對象,則把它轉(zhuǎn)為對象,并賦給 this,比如,當(dāng) fun.call("hhh") 時,打印 fun 內(nèi)的 this

String {0: "h", 1: "h", 2: "h", length: 3, [[PrimitiveValue]]: "hhh"}

否則 ,也就是僅剩的一種情況,顯式的傳入了一個對象作為 thisArg 參數(shù)的情況下,設(shè) this 綁定為 thisArg。

聲明式綁定初始化

每個執(zhí)行環(huán)境都有一個關(guān)聯(lián)的 變量環(huán)境。當(dāng)在一個執(zhí)行環(huán)境下評估一段 ECMA 腳本時,變量和函數(shù)定義會以綁定的形式添加到這個 變量環(huán)境 的環(huán)境記錄中。對于函數(shù)代碼,參數(shù)也同樣會以綁定的形式添加到這個 變量環(huán)境 的環(huán)境記錄中。

總結(jié)

ECMAScript 代碼的執(zhí)行由運行環(huán)境來完成。不同的運行環(huán)境可能采取不同的執(zhí)行方式,但基本的流程是相同的。如瀏覽器在解析 HTML 頁面中遇到

閱讀需要支付1元查看
<