摘要:理解執(zhí)行上下文和執(zhí)行堆棧對于理解的其它概念如提升,范圍和閉包至關重要。正確地理解執(zhí)行上下文和執(zhí)行堆棧將幫助你更好地使用開發(fā)應用。引擎執(zhí)行位于執(zhí)行堆棧頂部的方法。當調用時,為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文,并且把它推入到當前執(zhí)行堆棧。
原文
如果你是或者你想要成為一名js開發(fā)者,那么你必須了解js程序內部的運作。理解執(zhí)行上下文和執(zhí)行堆棧對于理解js的其它概念(如提升,范圍和閉包)至關重要。
正確地理解執(zhí)行上下文和執(zhí)行堆棧將幫助你更好地使用js開發(fā)應用。
廢話少說,讓我們開始吧:)
簡單來說,執(zhí)行上下文是預估和執(zhí)行當前環(huán)境下js代碼的抽象概念。每當在js中運行代碼時,它都在執(zhí)行上下文中運行。
(譯者:emmm,就是執(zhí)行上下文包含了追蹤當前正在執(zhí)行的代碼的全部狀態(tài)。)
執(zhí)行上下文的類型在js中有三種執(zhí)行類型
全局執(zhí)行上下文——這是默認或者說基礎執(zhí)行上下文。函數(shù)外的代碼就處于全局執(zhí)行上下文中。它做了兩件事:它創(chuàng)建了window對象(在瀏覽器環(huán)境下),也就是全局對象。并把this指向全局對象。在程序里面只能有一個全局上下文。
函數(shù)執(zhí)行上下文——每次函數(shù)被調用,都會為這個函數(shù)創(chuàng)建一個新的上下文。每個函數(shù)都有自己的上下文,但是只有被調用的時候才會被創(chuàng)建??梢杂泻芏鄠€函數(shù)執(zhí)行上下文。每當創(chuàng)建一個新的函數(shù)執(zhí)行上下文,js引擎都會按照定義好的順序執(zhí)行一系列的步驟,我將會在下文中討論。
Eval 函數(shù)的執(zhí)行上下文——eval函數(shù)執(zhí)行的時候也會為它里面的代碼創(chuàng)建上下文,但是這個方法用的少,在本文略過。
執(zhí)行堆棧執(zhí)行堆棧在其它語言中被稱為“調用棧”,是一種先進后出的一種數(shù)據(jù)結構,在代碼執(zhí)行期間被用于存儲所有的執(zhí)行上下文。
當js引擎開始解析js代碼時,會先創(chuàng)建全局執(zhí)行上下文并且放在當前執(zhí)行堆棧中。每當引擎遇到函數(shù)調用的代碼時,都會創(chuàng)建該函數(shù)的上下文并推入當前執(zhí)行堆棧中。
引擎執(zhí)行位于執(zhí)行堆棧頂部的方法。當方法執(zhí)行完畢,執(zhí)行堆棧pop掉最頂部的上下文,接著引擎繼續(xù)執(zhí)行堆棧頂部的方法。
用代碼示范一下:
let a = "Hello World!"; function first() { console.log("Inside first function"); second(); console.log("Again inside first function"); } function second() { console.log("Inside second function"); } first(); console.log("Inside Global Execution Context");
當上述代碼在瀏覽器內加載,js引擎就會創(chuàng)建一個全局執(zhí)行上下文并且把它推入當前執(zhí)行堆棧中。當調用first()時,js為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文,并且把它推入到當前執(zhí)行堆棧。
當second()方法被first()調用,js引擎為該方法創(chuàng)建一個新的執(zhí)行上下文并把它推入當前執(zhí)行堆棧。當second()執(zhí)行完畢,這個方法的上下文就被執(zhí)行堆棧推出,并且執(zhí)行下一個執(zhí)行上下文,也就是first()。
當first()執(zhí)行完畢,重復以上步驟。一旦執(zhí)行了所有代碼,JavaScript引擎就會從當前堆棧中刪除全局執(zhí)行上下文。
執(zhí)行上下文如何創(chuàng)建?直到現(xiàn)在,我們已經(jīng)知道js引擎如何管理執(zhí)行上下文的了,現(xiàn)在讓我們了解下執(zhí)行上下文如何被js創(chuàng)建的。
創(chuàng)建執(zhí)行上下文有兩個階段:1)創(chuàng)建階段,2)執(zhí)行階段(譯者:???懵逼臉)。
創(chuàng)建階段在執(zhí)行任何JavaScript代碼之前,執(zhí)行上下文將經(jīng)歷創(chuàng)建階段。在創(chuàng)建階段會發(fā)生三件事:
this綁定
詞法環(huán)境(LexicalEnvironment)創(chuàng)建
變量環(huán)境(VariableEnvironment)創(chuàng)建
(譯者:VariableEnvironment和LexicalEnvironment譯者也是第一次聽到,慚愧,大學沒學過編譯原理,在js中還有個this綁定,似乎是js特有)
因此,執(zhí)行上下文在概念上可以這樣表示,如下:
ExecutionContext = { ThisBinding =this綁定, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
在全局執(zhí)行上下文中,this值指向全局對象(在瀏覽器內是window對象)。
在函數(shù)執(zhí)行上下文中,this的值取決于函數(shù)的調用的時候的情況。如果它由對象引用調用,this值就是該對象,否則this值指向全局或者為undefined(在嚴格模式下)。
例如:
let person = { name: "peter", birthYear: 1994, calcAge: function() { console.log(2018 - this.birthYear); } } person.calcAge(); // "this" refers to "person", because "calcAge" was called with //"person" object reference let calculateAge = person.calcAge; calculateAge(); // "this" refers to the global window object, because no object reference was given詞法環(huán)境
官方es6文檔對詞法環(huán)境有如下解釋:
詞匯環(huán)境是一種規(guī)范類型,用于根據(jù)ECMAScript代碼的詞法嵌套結構定義標識符與特定變量和函數(shù)的關聯(lián)。詞匯環(huán)境由environment record(譯者:實在不知道咋翻)和外部詞匯環(huán)境的可能為null的引用組成。
(譯者:硬翻的,有點怪)
簡而言之,詞匯環(huán)境是一種包含標識符變量映射的結構(此處標識符指的是變量/函數(shù)的名稱,變量是對實際對象【包括函數(shù)類型的對象】或原始值的引用)。
現(xiàn)在,詞法環(huán)境由兩部分組成:
(1)environment record
(2)外部環(huán)境的引用
1、environment record是存放變量和函數(shù)聲明的一個地方
2、對外部環(huán)境的引用意味著它可以訪問其外部詞匯環(huán)境。
有兩種類型的詞法環(huán)境:
一種是全局環(huán)境(在全局執(zhí)行上下文里),它沒有外部環(huán)境的引用。它的外部環(huán)境引用為null。它有全局對象(window對象)和關聯(lián)的方法和屬性(例如數(shù)組方法),以及任何用戶定義的全局變量,并且this的值指向全局對象。
一種是函數(shù)環(huán)境,它存放用戶在函數(shù)里定義的變量。并且外部環(huán)境可以指向全局環(huán)境,或者是外層函數(shù)環(huán)境。
筆記——對于函數(shù)環(huán)境,environment record還包含一個arguments對象,該對象包含索引和傳遞給函數(shù)的參數(shù)之間的映射以及傳遞給函數(shù)的參數(shù)的長度(數(shù)量)。例如,下面函數(shù)的參數(shù)對象如下所示:
function foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2},
environment record也有兩種類型:
聲明性environment record存儲變量,函數(shù)和參數(shù)。 一個函數(shù)環(huán)境包含聲明性environment record。
對象environment record用于定義在全局執(zhí)行上下文中出現(xiàn)的變量和函數(shù)的關聯(lián)。全局環(huán)境包含對象environment record。
抽象地說,詞法環(huán)境在偽代碼中看起來像這樣:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here } outer:變量環(huán)境} } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here } outer: } }
它也是一個詞法環(huán)境,其EnvironmentRecord包含由此執(zhí)行上下文中的VariableStatements創(chuàng)建的綁定。
如上所述,變量環(huán)境也是一個詞匯環(huán)境,因此它具有上面定義的詞法環(huán)境的所有屬性。
在es6,詞法環(huán)境和變量環(huán)境的不同在于前者用于存儲函數(shù)聲明和變量(let和const)綁定,而后者用于存儲變量(var)的綁定。
來看個例子:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
然后執(zhí)行上下文會像這樣:
GlobalExectionContext = { ThisBinding:, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: } } FunctionExectionContext = { ThisBinding: , LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: } }
筆記——函數(shù)multiply調用時才會創(chuàng)建函數(shù)執(zhí)行上下文。
正如你所注意到的一樣,let和const定義的變量沒有綁定任何值,但var定義的變量為undefined
這是因為在創(chuàng)建階段,掃描代碼尋找變量和函數(shù)聲明時,函數(shù)聲明完全存儲在環(huán)境中,但變量最初設置為undefined(var)或保持為為初始化(let、const)。
(譯者:就是var會聲明提升,而let和const不會)
這就是為什么你可以在變量聲明前訪問到var定義的變量,而訪問let和const定義的變量則會拋出引用錯誤。
這就是js的變量提升。
執(zhí)行階段這是整篇文章中最簡單的部分。 在此階段,完成對所有這些變量的分配,最后執(zhí)行代碼。
筆記——在執(zhí)行階段,如果js引擎在源代碼聲明的實際位置找不到let變量的值,那么它將為其分配undefined值。
結論現(xiàn)在,我們已經(jīng)了js的部分執(zhí)行原理,雖然理解了這些概念不一定能讓你成為出色的js開發(fā)者,但是明白了上述的概念能讓你更好理解js的其它概念,例如變量提升、閉包。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/98959.html
摘要:每次調用函數(shù)時,都會創(chuàng)建一個新的執(zhí)行上下文。理解執(zhí)行上下文和堆??梢宰屇私獯a為什么要計算您最初沒有預料到的不同值的原因。 首發(fā):https://www.love85g.com/?p=1723 在這篇文章中,我將深入研究JavaScript最基本的部分之一,即執(zhí)行上下文。在這篇文章的最后,您應該更清楚地了解解釋器要做什么,為什么在聲明一些函數(shù)/變量之前可以使用它們,以及它們的值是如何...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執(zhí)行這段代碼的時候,調用棧會被清空。之后,產(chǎn)生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執(zhí)行這段代碼的時候,調用棧會被清空。之后,產(chǎn)生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:調用棧是一種數(shù)據(jù)結構,它記錄了我們在程序中的位置。當從這個函數(shù)返回的時候,就會將這個函數(shù)從棧頂彈出,這就是調用棧做的事情。而且這不是唯一的問題,一旦你的瀏覽器開始處理調用棧中的眾多任務,它可能會停止響應相當長一段時間。 原文地址: https://blog.sessionstack.com... PS: 好久沒寫東西了,最近一直在準備寫一個自己的博客,最后一些技術方向已經(jīng)敲定了,又可以...
摘要:技術上來說這個機制被稱為動態(tài)分配或代理。定義類一個類是一個正式的抽象集,它規(guī)定了對象的初始狀態(tài)和行為。技術上來說一個類表示構造函數(shù)原型的組合。因此構造函數(shù)創(chuàng)建對象并自動設置新創(chuàng)建實例的原型。第二次調用時,相同的上下文再次被壓入棧并恢復。 原文:JavaScript. The Core: 2nd Edition作者:Dmitry Soshnikov 文章其他語言版本:俄語 這篇文章是 ...
閱讀 2337·2023-04-26 00:28
閱讀 3085·2019-08-30 15:55
閱讀 2755·2019-08-30 12:47
閱讀 1567·2019-08-29 11:04
閱讀 3197·2019-08-28 18:14
閱讀 957·2019-08-28 18:11
閱讀 1683·2019-08-26 18:36
閱讀 3401·2019-08-23 18:21