傻傻分不清的javascript運行機制

學習到javascript的運行機制時,有幾個概念經常出現在各種文章中且容易混淆。Execution Context(執行環境或執行上下文),Context Stack (執行棧),Variable Object(VO: 變量對象),Active Object(AO: 活動對象),LexicalEnvironment(詞法環境),VariableEnvironment(變量環境)等,特別是 VO,AO以及LexicalEnvironment,VariableEnvironment的區別很多文章都沒有涉及到。因此我查看了一些國內外的文章,結合自身理解寫下了下面的筆記。雖然因為自身不足導致理解上的偏差,但是依然相信讀完下文會對理解javascript的一些概念如變量提升,作用域和閉包有很大的幫助。

一, 執行環境和執行棧

了解javascript的運行機制,首先必須掌握兩個基本的概念。Execution Context(執行環境或執行上下文)和Context Stack (執行棧)

1. 何為執行環境(執行上下文)(Execution Context)

我們知道javascript是單線程語言,也就是同一時間只能執行一個任務。當javascript解釋器初始化代碼后,默認會進入全局的執行環境,之后每調用一個函數,javascript解釋器會創建一個新的執行環境。

    var a = 1;                       // 1.初始化默認進入全局執行環境function b() {                   // 3.進入b 的執行環境function c() {               // 5. 進入c的執行環境···}c()                          // 4.在b的執行環境里調用c, 創建c的執行環境}b()                              // 2. 調用b 創建 b 的執行環境
復制代碼

執行環境的分類:

  • 全局執行環境:簡單的理解,一個程序只有一個全局對象即window對象,全局對象所處的執行環境就是全局執行環境。
  • 函數執行環境:函數調用過程會創建函數的執行環境,因此每個程序可以有無數個函數執行環境。
  • Eval執行環境:eval代碼特定的環境。

2. 如何單線程運行(Context Stack)

從一個簡單的例子開始講起

function foo(i) {if (i < 0) return;console.log('begin:' + i);foo(i - 1);console.log('end:' + i);
}
foo(2);
復制代碼

如何存儲代碼運行時的執行環境(全局執行環境,函數執行環境)呢,答案是執行棧。而棧遵循的是先進后出的原理,javascript初始化完代碼后,首先會創建全局執行環境并推入當前的執行棧,當調用一個函數時,javascript引擎會創建新的執行環境并推到當前執行棧的頂端,在新的執行環境中,如果繼續發生一個新函數調用時,則繼續創建新的執行環境并推到當前執行棧的頂端,直到再無新函數調用。最上方的函數執行完成后,它的執行環境便從當前棧中彈出,并將控制權移交到當前執行棧的下一個執行環境,直到全局執行環境。當程序或瀏覽器關閉時,全局環境也將退出并銷毀。

因此輸出的結果為:

begin:2
begin:1
begin:0
end:0
end:1
end:2
復制代碼

3. 如何創建執行環境

我們現在知道每次調用函數時,javascript 引擎都會創建一個新的執行環境,而如何創建這一系列的執行環境呢,答案是執行器會分為兩個階段來完成, 分別是創建階段和激活(執行)階段。而即使步驟相同但是由于規范的不同,每個階段執行的過程有很大的不同。

3.1 ES3 規范

創建階段:

  • 1.創建作用域鏈。
  • 2.創建變量對象VO(包括參數,函數,變量)。
  • 3.確定this的值。

激活/執行階段:

  • 完成變量分配,執行代碼。
3.2 ES5 規范

創建階段:

  • 1.確定 this 的值。
  • 2.創建詞法環境(LexicalEnvironment)。
  • 3.創建變量環境(VariableEnvironment)。

激活/執行階段:

  • 完成變量分配,執行代碼。

我們從規范上可以知道,ES3和ES5在執行環境的創建階段存在差異,當然他們都會在這個階段確定this 的值 (關于this 的指向問題我們以后會在專門的文章中分析各種this 的指向問題,這里便不做深究)。我們將圍繞這兩個規范不同點展開。盡管ES3的一些規范已經被拋棄,但是掌握ES3 創建執行環境的過程依然有助于我們理解javascript深層次的概念。

二, Variable Object(VO: 變量對象),Active Object(AO: 活動對象)

2.1 基本概念

VO 和 AO 是ES3規范中的概念,我們知道在創建過程的第二個階段會創建變量對象,也就是VO,它是用來存放執行環境中可被訪問但是不能被 delete 的函數標識符,形參,變量聲明等,這個對象在js環境下是不可訪問的。而AO 和VO之間區別就是AO 是一個激活的VO,僅此而已。

  • 變量對象(Variable) object)是說JS的執行上下文中都有個對象用來存放執行上下文中可被訪問但是不能被delete的函數標示符、形參、變量聲明等。它們會被掛在這個對象上,對象的屬性對應它們的名字對象屬性的值對應它們的值但這個對象是規范上或者說是引擎實現上的不可在JS環境中訪問到活動對象

  • 激活對象(Activation object)有了變量對象存每個上下文中的東西,但是它什么時候能被訪問到呢?就是每進入一個執行上下文時,這個執行上下文兒中的變量對象就被激活,也就是該上下文中的函數標示符、形參、變量聲明等就可以被訪問到了

2.2 執行細節

如何創建VO對象可以大致分為四步

  • 1.創建arguments對象
  • 2.掃描上下文的函數聲明(而非函數表達式),為發現的每一個函數,在變量對象上創建一個屬性——確切的說是函數的名字——其有一個指向函數在內存中的引用。如果函數的名字已經存在,引用指針將被重寫。
  • 3.掃描上下文的變量聲明,為發現的每個變量聲明,在變量對象上創建一個屬性——就是變量的名字,并且將變量的值初始化為undefined。如果變量的名字已經在變量對象里存在,將不會進行任何操作并繼續掃描。

注意: 整個過程可以大概描述成: 函數的形參=>函數聲明=>變量聲明, 其中在創建函數聲明時,如果名字存在,則會被重寫,在創建變量時,如果變量名存在,則忽略不會進行任何操作。

一個簡單的例子

function foo(i) {var a = 'hello';var b = function privateB() {};function c() {}
}foo(22);
復制代碼

執行的偽代碼

// 創建階段
fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: undefined,b: undefined},this: { ... }
}
// 激活階段
fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: 'hello',b: pointer to function privateB()},this: { ... }
}
復制代碼

三, LexicalEnvironment(詞法環境),VariableEnvironment(變量環境)

3.1 基本概念

詞法環境和變量環境是ES5以后提到的概念,官方對詞法環境的解釋如下。

詞法環境是一種規范類型,基于 ECMAScript 代碼的詞法嵌套結構來定義標識符與特定變量和函數的關聯關系。詞法環境由環境記錄(environment record)和可能為空引用(null)的外部詞法環境組成。

簡單的理解,詞法環境是一個包含標識符變量映射的結構。(這里的標識符表示變量/函數的名稱,變量是對實際對象【包括函數類型對象】或原始值的引用)。

ES3的VO,AO為什么可以被拋棄?個人認為有兩個原因,第一個是在創建過程中所執行的創建作用域鏈和創建變量對象(VO)都可以在創建詞法環境的過程中完成。第二個是針對es6中存儲函數聲明和變量(let 和 const)以及存儲變量(var)的綁定,可以通過兩個不同的過程(詞法環境,變量環境)區分開來。

3.2 詞法環境(lexicalEnvironment)

詞法環境由兩個部分組成

  • 環境記錄(enviroment record),存儲變量和函數聲明
  • 對外部環境的引用(outer),可以通過它訪問外部詞法環境

對外部環境的引用關系到作用域鏈,之后再分析,我們先來看看環境記錄的分類。

環境記錄分兩部分

  • 聲明性環境記錄(declarative environment records): 存儲變量、函數和參數, 但是主要用于函數 、catch詞法環境。 注意:函數環境下會存儲arguments的值。而詳細的過程可以參考VO 的執行細節,基本大同小異
  • 對象環境記錄(object environment records), 主要用于with 和全局的詞法環境

偽代碼如下

// 全局環境
GlobalExectionContext = {  
// 詞法環境LexicalEnvironment: {  EnvironmentRecord: {  ···}outer: <null>  }  
}
// 函數環境
FunctionExectionContext = {  
// 詞法環境LexicalEnvironment: {  EnvironmentRecord: {  // 包含argument}outer: <Global or outer function environment reference>  }  
}
復制代碼

3.3 變量環境(objectEnvironment)

變量環境也是個詞法環境,主要的區別在于lexicalEnviroment用于存儲函數聲明和變量( let 和 const )綁定,而ObjectEnviroment僅用于存儲變量( var )綁定。

3.4 偽代碼展示

ES5規范下的整個創建過程可以參考下方的偽代碼

let a = 20;  
const b = 30;  
var c;function d(e, f) {  var g = 20;  return e * f * g;  
}c = d(20, 30);
復制代碼
// 全局環境
GlobalExectionContext = {this: <Global Object>,// 詞法環境LexicalEnvironment: {  EnvironmentRecord: {  Type: "Object",  // 環境記錄分類: 對象環境記錄a: < uninitialized >,  // 未初始化b: < uninitialized >,  d: < func >  }  outer: <null>  },VariableEnvironment: {  EnvironmentRecord: {  Type: "Object",  // 環境記錄分類: 對象環境記錄c: undefined,  // undefined}  outer: <null>  }  
}
// 函數環境
FunctionExectionContext = {  this: <Global Object>,LexicalEnvironment: {  EnvironmentRecord: {  Type: "Declarative",  // 環境記錄分類: 聲明環境記錄Arguments: {0: 20, 1: 30, length: 2},  // 函數環境下,環境記錄比全局環境下的環境記錄多了argument對象},  outer: <GlobalLexicalEnvironment>  },VariableEnvironment: {  EnvironmentRecord: {  Type: "Declarative",  // 環境記錄分類: 聲明環境記錄g: undefined  },  outer: <GlobalLexicalEnvironment>  }  
}
復制代碼

四,作用域鏈

前面講創建過程中,我們留下了一個伏筆,ES3規范中有創建作用域鏈的過程,而ES5中在創建詞法環境或變量環境的過程中,也有生成外部環境的引用的過程。那這個過程有什么作用呢。我們通過一個簡單的例子來說明。

function one() {var a = 1;two();function two() {var b = 2;three();function three() {var c = 3;alert(a + b + c); // 6}}}one();
復制代碼

當執行到three 的執行環境時,此時 a和b 都不在c 的變量內,因此作用域鏈則起到了引用外部執行環境變量的作用。ES3中創建的作用域鏈如圖:

當解釋器執行alert(a + b + c),他首先會找自身執行環境下是否有a這個變量的存在,如果不存在,則通過查看作用域鏈,判斷a是否在上一個執行環境內部。它檢查是否a存在于內部,若找不到,則沿著作用域鏈往上一個執行環境找,直到找到,或者到頂級的全局作用域。同理ES6規范中也可以這樣分析。

因此這會引入一個javascript一個重要的概念,閉包。從上面對執行環境的解釋我們可以這樣理解,閉包就是內部環境通過作用域鏈訪問到上層環境的變量。因此也存在無法進行變量回收的問題,只要函數的作用域鏈在,變量的值便因為閉包無法被回收。

注意: 此作用域鏈和原型鏈的作用域鏈不是同一個概念。

五, 小結

通過對javascript運行機制的介紹,對一些javasript高級概念有了更深的認識,特別是對一些云里霧里的概念區別有了更深刻的認識。不同規范下,不同概念的解釋更有利于深挖javascript底層的執行思想。我相信這是理解javascipt語言最重要的一步。

參考資料:

  • stackoverflow.com/questions/2…
  • davidshariff.com/blog/what-i…
  • davidshariff.com/blog/javasc…
  • github.com/yued-fe/y-t…
  • segmentfault.com/a/119000000…

本文為博主原創文章,轉載請注明出處 juejin.im/post/5c2052…

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537275.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537275.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537275.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

浙江省數字化改革回顧(2022年5月)

事業的偉大在于目標的壯麗&#xff0c;也在于過程的壯麗&#xff1b;改革的成果在于享有的豐富&#xff0c;也在于經歷的豐富。2021年2月18日&#xff0c;春節假期后首個工作日&#xff0c;浙江省委召開全省數字化改革大會&#xff0c;在全國率先開啟數字化改革探索實踐。此后&…

python 某個數是不是在某個范圍內_教寫一個簡單的python小程序(04)

點擊藍字關注我們 會酸的柚子Python愛好者搞機少年七夕結束了~酸柚也是被強塞了滿嘴的狗糧在這樣充滿戀愛腐朽氣息的一天酸柚也是馬不停蹄的在趕稿子兄弟們&#xff0c;給我頂起來呀~我們來看看今日的題目可能很多小伙伴對完全平方數這個概念有點生疏了完全平方數數學上&#x…

Python:模塊module

python中一個模塊就是一個擴展名為.py的文件&#xff0c;也可能是預編譯的.pyc文件。 引入模塊用&#xff1a;import 模塊名 使用引用模塊中定義的標識符&#xff08;函數、變量、類&#xff09;用&#xff1a;模塊名.標識符名 引入模塊中的標識符用&#xff1a;from 模塊名 im…

浙江公布2022年數字化改革“最系列“成果 評選出最佳應用104項

10月29日&#xff0c;省委改革辦&#xff08;省數改辦&#xff09;公布了2022年數字化改革“最系列”成果。該評選由省委改革辦&#xff08;省數改辦&#xff09;會同省委政研室、省人大常委會法工委、省市場監管局和省大數據局共同開展&#xff0c;評選了最佳應用104項、最強大…

dot net core 使用 IPC 進程通信

原文:dot net core 使用 IPC 進程通信版權聲明&#xff1a;博客已遷移到 http://lindexi.gitee.io 歡迎訪問。如果當前博客圖片看不到&#xff0c;請到 http://lindexi.gitee.io 訪問博客。本文地址 https://blog.csdn.net/lindexi_gd/article/details/79946496 dot net core 使…

python可變類型和不可變深淺拷貝類型_python3筆記十四:python可變與不可變數據類型+深淺拷貝...

一&#xff1a;學習內容python3中六種數據類型python賦值python淺拷貝python深拷貝二&#xff1a;python3六種數據類型1.六種數據類型Number(數字)string(字符串)List(列表)Tuple(元祖)Set(集合)Dictionary(字典)2.六種數據類型分類不可變數據(3個)&#xff1a;Number、String、…

Android手機用wifi連接adb調試的方法

https://www.jianshu.com/p/dc6898380e38 0x0 前言 Android開發肯定要連接pc的adb進行調試&#xff0c;傳統的方法是用usb與pc進行連接&#xff0c;操作簡單即插即用&#xff0c;缺點是pc上必須得有對應手機的usb驅動程序&#xff0c;對于谷歌親兒子系列和三星摩托等外國品牌而…

控制臺應用程序換換為窗體應用_Epic為開發者設計了一套iPhone使用的運動捕捉應用程序...

玩懂手機網7月13日資訊&#xff0c;我們都知道對于游戲或者是動漫開發者來說&#xff0c;運動捕捉設備是一套非常昂貴的設備&#xff0c;需要非常專業的獨立開發人員&#xff0c;大量的時間才能完成&#xff0c;最近Epic為開發者設計了一套iPhone使用的運動捕捉應用程序。這套i…

螞蟻金服億級并發下的移動端到端網絡接入架構解析

為了與金融從業者、科技從業者共同探討金融 業務的深層次問題&#xff0c;螞蟻金服聯手 TGO 鯤鵬會上海分會&#xff0c;在 12 月 8 日舉辦了「走進螞蟻金服&#xff1a;雙十一背后的螞蟻金服技術支持」活動。螞蟻金服高級技術專家賈島為大家分享了《億級并發下的螞蟻移動端到…

python3.12答案_編程常見問題

通常&#xff0c;不要使用 from modulename import * 。這樣做會使導入器的命名空間變得混亂&#xff0c;并且使得連接器更難以檢測未定義的名稱。在文件的頂部導入模塊。這樣做可以清楚地了解代碼所需的其他模塊&#xff0c;并避免了模塊名稱是否在范圍內的問題。每行導入一個…

如何根據視頻的寬屏與豎屏來排序?

原理 寬屏與豎屏是根據 幀高度 與 幀寬度 來區分的 幀高度就是圖片高度&#xff08;縱向的像素尺寸&#xff09;&#xff0c;幀寬度就是圖片寬度&#xff08;橫向的像素尺寸&#xff09;&#xff0c;分辨率就是&#xff08;高度x寬度&#xff09;。 windows11的文件排序&…

HashiCorp Vault 1.0開源自動解封特性,新增Batch令牌

HashiCorp發布了其秘密管理工具Vault 的1.0版本&#xff0c;并開源了在發生故障或重啟后繼續使用Vault服務器所需的“自動解封&#xff08;auto-unseal&#xff09;”特性。這個版本提供了一種可以用于臨時工作負載的新令牌batch。另一個新特性是&#xff0c;Kubernetes auth現…

sap模塊介紹_小邁說|SAP究竟有多少模塊?

SAP究竟有哪些模塊繼上一期小邁說SAP&#xff01;SPA&#xff1f;的區別&#xff0c;相信大部分讀者明白了我們與水浴按摩行業的分別&#xff0c;可是僅僅區分名字還不夠&#xff0c;SAP還有眾多的模塊&#xff0c;這些又該怎么去了解呢&#xff1f;這就輪到肩負愛與責任的小邁…

360 再次開源管理平臺 Wayne:基于企業級 Kubernetes 集群

2019獨角獸企業重金招聘Python工程師標準>>> 奇虎 360 宣布正式開源 Wayne &#xff0c;這是一個由 360 搜索云平臺團隊開發的通用的、基于 Web 的 Kubernetes 多集群一站式可視化管理平臺。內置了豐富多樣的功能&#xff0c;滿足企業的通用需求&#xff0c;同時插件…

python setup.py install 出錯_python setup.py install 失敗

python setup&period;py install 報錯ImportError&colon; No module named setuptools學習光榮之路python課程時,使用python setup.py install安裝其他模塊時,第一次安裝某模塊成功了.安裝另一模塊卻報錯ImportError: No module named s ...對于python setup&perio…

Node.js 根本沒有這樣搞性能優化的?

1、使用最新版本的 Node.js 僅僅是簡單的升級 Node.js 版本就可以輕松地獲得性能提升&#xff0c;因為幾乎任何新版本的 Node.js 都會比老版本性能更好&#xff0c;為什么&#xff1f; Node.js 每個版本的性能提升主要來自于兩個方面&#xff1a; V8 的版本更新&#xff1b;Nod…

可交付成果、核實的可交付成果、驗收的可交付成果?

①可交付成果。指的是在某一過程、階段或項目完成時&#xff0c;產出的任何獨特并可核實的產品、成果或服務。可交付成果可能是有形的&#xff0c;也可能是無形的。【研發完成】 ②核實的可交付成果。是指已經完成&#xff0c;并經過“控制質量”過程檢查為正確的可交付成果。…

安裝oracle到create inventory時卡住了怎么辦_win10系統安裝教程(官方工具)

Hi&#xff0c;大家好。對于小白用戶&#xff0c;裝系統是比較頭疼的事&#xff0c;所以今天寫一個簡單易懂的裝系統教程。使用微軟官方提供的工具制作U盤啟動盤&#xff0c;操作簡單&#xff0c;系統純凈&#xff0c;強烈建議小白用戶使用。缺點是該工具功能單一&#xff0c;并…

Microsoft Project 排計劃的步驟

Microsoft Project 排計劃的步驟&#xff1a; 第一步&#xff1a;設置項目信息&#xff0c;開始日期&#xff0c;選擇日歷&#xff1b; 第二步&#xff1a;編制WBS 第三步&#xff1a;設置前置任務 第四步&#xff1a;設置WBS每個工期 第五步&#xff1a;設置資源名稱&#xff…