首頁  >  文章  >  web前端  >  了解JS中的執行上下文、 執行堆疊、事件循環

了解JS中的執行上下文、 執行堆疊、事件循環

青灯夜游
青灯夜游轉載
2020-07-06 10:39:172005瀏覽

本篇文章帶大家來了解Javascript中的執行上下文和執行堆疊、事件循環。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

了解JS中的執行上下文、 執行堆疊、事件循環

下面的這些概念,無論是執行上下文、 或執行堆疊,它在規範中的概念都很抽象,很多內容的理解實際靠的都是想像力,若有錯誤之處,還請指正。

執行上下文

簡而言之,執行上下文(Execution Context)是正在運行的可執行程式碼所處環境的抽象,用於追蹤一段程式碼中變數的求值。這是我總結過來的概念,可能有些不準確,也可以參考真正的規範定義。

不過總的來說,有三個關鍵點:

  • 只有可執行程式碼才會有執行上下文

  • 執行上下文是有狀態的:運行狀態(Perform)、掛起狀態(Suspend)以及恢復(Resume)。處於Perfrom狀態的執行上下文稱為運行時執行上下文(Running Execution Context)

  • #執行上下文完全不等價於詞法環境,硬說關係,也只是前者引用了後者而已。

  • 執行一個JS腳本時,可以有多個執行上下文存在,但是運行時上下文只有唯一一個(非同步也是如此,至於為什麼提了四個…三大天王有四個不是常識麼…)。

ES規格中規定,可執行程式碼有以下幾個:

  • 全域程式碼

  • #函數程式碼

  • Eval語句

  • #模組程式碼

##換言之,看以下程式碼:

  
  var g=111
  function f(){
      
      console.log(g);
      for(let i =0; i 如果上面的程式碼運行,只會產生兩個執行上下文:<p></p>
    ##全域
  • 函數
  • f

  • 但是如果將標註
(*)

的行註解掉,那麼最終只有一個執行上下文,因為函數f根本就不會執行,自然就不會有對應的執行上下文了。裡面唯一一個迷惑的是,就是for-loop了,但它根本不是可執行程式碼,所以它是函數執行上下文的一部分

執行上下文的重要組成部分

一個執行上下文,可以抽象化為:

ExecutionContext = {
    State: 
    LexEnv = {
        This: ,
        OuterEnv: ,
        DecRec:{
            //... identifiername-variable
        }      
    }
    VaEnv = {
        This: ,
        OuterEnv: ,
        VarRec:{
            //... identifiername-variable
        }      
    }
}

事實上,在一個執行上下文中有兩個相當重要的元件:

LexicalEnvironmentComponent

(詞法環境元件)和VariableEnvironmentComponent(變數環境元件)。詞法環境元件指向目前程式碼中的詞法環境LexEnv),變數環境元件指向目前程式碼變數環境VarEnv)。 關於

執行上下文不得不說的二三事

中,有一個很重要的部分就是作用域鏈,但是在執行上下文中並沒有看到相關內容。不過作用域鏈的確存在,它就在[[Scope]]內部屬性中,透過瀏覽器可以直接看到。 不過也可以這樣理解,當一個執行上下文被創建時,不僅會創建當前詞法環境的

LexEnv

,也會創建LexEnv.OutEnvLexEnv.OutEnv.OutEnv…,直到延伸到全域為止。

執行上下文的建立與銷毀

1、建立一個新執行上下文(ExecutionContext , EC)

2、建立目前詞法環境(LexEnv和VarEnv)

3、將該執行上下文的

LexicalEnvironmentComponent

和 VariableEnvironmentComponent 指向目前環境下的LexEnvVarEnv

#1 #中。

    4、將新執行上下文推入
  • 執行堆疊

    中,並成為運行時執行上下文5、對可執行程式碼區塊內的識別碼進行實例化和初始化:#收集目前詞法環境內所有宣告的識別碼歸入DecRec中,所有var聲明的識別碼歸入

    VarNames
  • 集合中,在此階段會進行
  • 標識符名

    偵測,若與let/const/...宣告的識別碼與VarNames中的識別符重複, 報錯。 DecRec中的識別符進行實例化,並設為

    uninitialized###。 ###VarNames###中的標識符綁定在###ObjRec###中,實例化後直接初始化為###undefined###。 ###
  • 对于function声明的函数,将直接指向函数对象,并也会绑定到ObjRec中,这是浏览器默认行为

6、运行代码。

  • 非var声明的标识符会在声明处进行初始化(默认为undefined)。

  • 完成所有变量的赋值,并可能会一直在变化。

7、运行完毕从 执行栈 中弹出。

备注:

  • 关于This绑定,大部分情况可以用过去的说法解释,然而某些情况下却不尽然。
  • 闭包我会在下一篇介绍。
  • 执行上下文,我个人认为并不如何重要,但是却能在许多情形下起到极为关键的作用,所以还是有必要去深入认识一下。
  • 关于执行上下文和词法环境的关系,最多是前者引用了后者,仅此而已。诚然,有许多情况没必要用执行上下文来说明,但是永远避免不了违和感

执行栈与事件循环

执行栈(Execution Stack)就是由执行上下文构成的堆栈,类似于Call Stack

1、当Javascript引擎遇到一段可执行代码时,新建一个执行上下文。

2、将它推入执行栈中。并设置为运行时执行上下文。

  • 如果存在其他执行上下文。
  • 那么将当前执行上下文挂起
  • 然后再将新执行上下文推入执行栈中。

3、执行上下文运行完毕,弹出销毁恢复并将原执行上下文设为运行时。

总觉得这些没什么好说的,但是水一下吧

执行栈最重要的部分并非是执行栈概念本身,而是与任务队列的关系,它是事件循环的入门关键概念之一

众所周知,Javascript语言是单线程的,此处的执行栈就相当于主线程的调用栈,也是唯一一个调用栈,至于什么是主线程可以查阅相关资料,这里有些超纲了……

那么javascript是如何实现异步的

确切来说,这不是Javascript核心的部分,它是结合浏览器API(如Web Worker, Browser-context了解一下)实现的

事件循环中(事件处理过程),有两个极其重要的概念:

  • 任务序列: Task Quenue
  • 事件: Event

这两个概念,是抽象滴。

在Javascript中,一个任务也可以称之为事件,通常是一个函数回调,由许多任务组成的队列,就是所谓的任务序列了。任务序列有很多分类,例如:作业序列(Job Quenue)、消息序列(Message Quenue),本质没区别。

不必再深入了解,现在需要记住的是:一个任务序列中的任务如果想要被执行,就必须将它取出放入执行栈中。

举一个抽象点的例子:

例如下面的代码:

  
      var temp = 10;
      
      console.log('push task1');
      setTimeout(function task1(){
        temp+=10;
        console.log(temp+'task1 okay! ');
      },1000)
      
      console.log('taskquenue=[task1]; push task2');
      setTimeout(function task2(){
        temp*=10;
        console.log(temp+'task2 okay! ');
      },500) 
     
      console.log('taskquenue=[task1,task2]; push task3');
      setTimeout(function task3(){
        temp*= -0.2;
        console.log(temp+'task3 okay! ');
      },1500)
      console.log('taskquenue=[task1, task2,task3]');

输出如下:

push task1
taskquenue=[task1]; push task2
taskquenue=[task1,task2]; push task3
taskquenue=[task1, task2,task3]
100task2 okay! 
110task1 okay! 
-22task3 okay!

setTimeout是一个定时器,它能够将任务放到任务队列中。如图:

  • 添加作业task1
    了解JS中的執行上下文、 執行堆疊、事件循環
  • 添加作业task2
    了解JS中的執行上下文、 執行堆疊、事件循環
  • 添加作业task3
    了解JS中的執行上下文、 執行堆疊、事件循環

执行到此处, task1task2task3都被放入了任务队列; 然后执行栈全部执行完毕后,开始处理任务队列中的任务。

为什么任务队列中的任务必须在执行栈空时后执行呢?

  • 這裡我也不清楚,這畢竟真正涉及了底層內容;只是有些明白為何這樣,不過擔心誤導他人,就算了。
  • 一般而言, 關於任務序列的相關概念沒有如此簡單,還涉及了很多東西,例如阻塞調度(Schedule )等,這些方面可以參考其他諸如C Java這類多執行緒的語言或是看看作業系統這方面的內容。
  • 如果只是簡單的業務實作,不需要了解這麼多底層的東西。

現在開始處理任務吧:

  • 處理task2
    了解JS中的執行上下文、 執行堆疊、事件循環
  • 處理task1
    了解JS中的執行上下文、 執行堆疊、事件循環
  • 處理task3
    了解JS中的執行上下文、 執行堆疊、事件循環

好了,一個事件循環就這麼結束了。
然後Javascript引擎進入休眠階段Javascript引擎永不結束!),等待有新的任務執行,然後開始下一個事件循環。

備註:
  • 這裡只是一個簡單例子
  • 事件循環可以有多個任務隊列
  • 任務序列共分為兩種: 微任務序列與巨集任務序列
  • 我們的script程式碼,就是巨集任務序列之一。

最後: Javascript引擎

這是我精讀Javascript系列第三篇,猝不及防的就到了事件循環,看起來一下子就深入好多好多… 但我覺得這才是最合理程安排,大多的文檔都把任務序列呼叫棧給分開了,但是在~~_____~~,它們本來應該是一體,不應該以任何方便的理由將它們分開

深入Javascript, 除了看規範,最好也看下JS引擎的實作文件,有些進階內容,不在規範中,而是在這些文件裡(請自己Google查找,百度也能搜尋到)。

如果對Javascript引擎比較有興趣的,可以參考:

  • MDN About_Javascript
  • Chromium V8參考
  • # Github V8

雖然,極不建議新手一下子看V8源代碼,那種頭痛欲裂又渾身顫抖不止的感覺實在是過癮之極啊…

相關教學推薦:JavaScript影片教學

#

以上是了解JS中的執行上下文、 執行堆疊、事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除