欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript執(zhí)行機制詳細介紹

 更新時間:2021年12月03日 10:46:28   作者:南玖  
這篇文章主要介紹了JavaScript執(zhí)行機制,想要搞懂JavaScript執(zhí)行機制,便與進程與線程的概念脫不了干系,下面我們就來看看這JavaScript執(zhí)行機制的具體介紹吧,需要的朋友可以參考一下

前言:

不論是工作還是面試,我們可能都經常會碰到需要知道代碼的執(zhí)行順序的場景,所以打算花點時間徹底搞懂JavaScript的執(zhí)行機制。

想要搞懂JavaScript執(zhí)行機制,你需要清楚下面這些知識: (以瀏覽器環(huán)境為例,與Node環(huán)境不同)

1.進程與線程的概念

  • 瀏覽器原理
  • 事件循環(huán)(Event-Loop),任務隊列(同步任務,異步任務,微任務,宏任務)
  • 進程與線程

我們都知道計算機的核心是CPU,它承擔了所有的計算任務;而操作系統(tǒng)是計算機的管理者,它負責任務的調度、資源的分配和管理,統(tǒng)領整個計算機硬件;應用程序則是具有某種功能的程序,程序是運行于操作系統(tǒng)之上的。

進程:

進程是一個具有獨立功能的程序在一個數據集上的一次動態(tài)執(zhí)行的過程,是操作系統(tǒng)進行資源分配和調度的一個獨立單位,是應用程序運行的載體 進程是能擁有資源和獨立運行的最小單位,也是程序執(zhí)行的最小單位。

進程具有的特征:

  • 動態(tài)性:進程是程序的一次執(zhí)行過程,是臨時的,有生命期的,是動態(tài)產生,動態(tài)消亡的;
  • 并發(fā)性:任何進程都可以同其他進程一起并發(fā)執(zhí)行;
  • 獨立性:進程是系統(tǒng)進行資源分配和調度的一個獨立單位;
  • 結構性:進程由程序、數據和進程控制塊三部分組成。

線程:

線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調度和分派的基本單位。一個進程可以有一個或多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間)。一個標準的線程由線程ID、當前指令指針(PC)、寄存器和堆棧組成。而進程由內存空間(代碼、數據、進程空間、打開的文件)和一個或多個線程組成。

進程與線程的區(qū)別:

  • 線程是程序執(zhí)行的最小單位,而進程是操作系統(tǒng)分配資源的最小單位;
  • 一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執(zhí)行路線;
  • 進程之間相互獨立,但同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)及一些進程級的資源(如打開文件和信號),進程與進程之間互不可見;
  • 調度和切換:線程上下文切換比進程上下文切換要快得多。

JS為什么是單線程?

JavaScript從它誕生之初就是作為瀏覽器的腳本語言,主要用來處理用戶交互以及操作DOM,這就決定了它只能是單線程的,否則會帶來非常復雜的同步問題。

舉個例子: 如果JS是多線程的,其中一個線程要修改一個DOM元素,另外一個線程想要刪除這個DOM元素,這時候瀏覽器就不知道該聽誰的。所以為了避免復雜性,從一誕生,JavaScript就被設計成單線程。

為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質

2.瀏覽器原理

作為前端工程師,瀏覽器想必都不陌生,并且瀏覽器是多進程的。

瀏覽器組成部分:

  • 用戶界面:包括地址欄,前進/后退/刷新/書簽??等按鈕
  • 瀏覽器引擎:在用戶界面和呈現引擎之間傳送指令
  • 渲染引擎:用來繪制請求的內容
  • 網絡:用來完成網絡調用,例如http請求,它具有平臺無關的接口,可以在不同平臺上工作
  • JavaScript解釋器:用來解析執(zhí)行JavaScript代碼
  • 用戶界面后端:用于繪制基本的窗口小部件,比如組合框和窗口,底層使用操作系統(tǒng)的用戶接口
  • 數據存儲:屬于持久層,瀏覽器在硬盤中保存類似cookie的各種數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術

??注意:與大多數瀏覽器不同的是,谷歌(Chrome)瀏覽器的每個標簽頁都分別對應一個呈現引擎實例。每個標簽頁都是一個獨立的進程

瀏覽器包含哪些進程?

瀏覽器進程:

  • 瀏覽器的主進程(負責協(xié)調、主控),該進程只有一個
  • 負責瀏覽器界面顯示,與用戶交互。如前進,后退等
  • 負責各個頁面的管理,創(chuàng)建和銷毀其他進程
  • 將渲染(Renderer)進程得到的內存中的Bitmap(位圖),繪制到用戶界面上
  • 網絡資源的管理,下載等

第三方插件進程:

  • 負責管理第三方插件

GPU進程:

  • 負責3D繪制與硬件加速(最多一個)

渲染進程

  • 負責頁面文檔解析,執(zhí)行與渲染

渲染進程包含哪些線程?

GUI渲染線程:

  • 主要負責解析HTML,CSS,構建DOM樹,布局,繪制等
  • 該線程與JavaScript引擎線程互斥,當執(zhí)行JavaScript引擎線程時,GUI渲染線程會被掛起,當任務隊列空閑時,主線程才會執(zhí)行GUI渲染

JavaScript引擎線程:

  • 主要負責處理JavaScript腳本,執(zhí)行代碼(如V8引擎)
  • 瀏覽器同時只能有一個JS引擎線程在運行JS程序,即JS是單線程的
  • JS引擎線程與GUI渲染線程是互斥的,所以JS引擎會阻塞頁面渲染

定時觸發(fā)器線程:

  • 負責執(zhí)行定時器函數(setTimeout,setInterval
  • 瀏覽器定時計數器并不是由JS引擎計數的(因為JS是單線程的,如果處于阻塞狀態(tài)就會影響計數器的準確性)
  • 通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件觸發(fā)線程的事件隊列中,等待JS引擎空閑后執(zhí)行),這個線程就是定時觸發(fā)器線程,也叫定時器線程
  • W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms

事件觸發(fā)線程:

  • 負責將準備好的事件交給JS引擎線程執(zhí)行
  • 當事件被觸發(fā)時,該線程會把對應的事件添加到待處理隊列的隊尾,等待JS引擎處理

異步請求線程:

  • XMLHttpRequest連接后瀏覽器會開一個線程
  • 檢測請求狀態(tài)變更時,如果有對應的回調函數,異步請求線程就會產生狀態(tài)變更事件,并把對應的回調函數放入隊列中等待JS引擎執(zhí)行

3.同步與異步

由于JavaScript是單線程的,這就決定了它的任務不可能只有同步任務,那些耗時很長的任務如果也按同步任務執(zhí)行的話將會導致頁面阻塞,

所以JavaScript任務一般分為兩類:

同步任務:

同步任務指的是,在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務;

異步任務:

異步任務指的是,不進入主線程、而進入"任務隊列"(Event queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執(zhí)行了,該任務才會進入主線程執(zhí)行。

常見的異步任務: 定時器,ajax,事件綁定,回調函數,promise,async await

  • 同步和異步任務分別進入不同的執(zhí)行"場所",同步的進入主線程,異步的進入Event Table并注冊函數。
  • 當Event Table中指定的事情完成時,會將這個函數移入Event Queue。
  • 主線程內的任務執(zhí)行完畢為空,會去Event Queue讀取對應的函數,進入主線程執(zhí)行。
  • 上述過程會不斷重復,也就是常說的Event Loop(事件循環(huán))。
  • 我們不禁要問了,那怎么知道主線程執(zhí)行棧為空???js引擎存在monitoring process進程,會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數。

宏任務與微任務:

JavaScript除了廣義上的同步任務與異步任務,還有更精細的任務定義:

  • 宏任務(macro-task): 包括全局代碼,setTimeout,setInterval
  • 微任務(micro-task): new Promise().then(回調) process.nextTick()

不同類型的任務會進入到不同的任務隊列:

事件循環(huán)的順序,決定js代碼的執(zhí)行順序。進入整體代碼(宏任務)后,開始第一次循環(huán)。接著執(zhí)行所有的微任務。然后再次從宏任務開始,找到其中一個任務隊列執(zhí)行完畢,再執(zhí)行所有的微任務。

4.執(zhí)行棧與任務隊列

執(zhí)行棧:

JavaScript代碼都是在執(zhí)行上下文中執(zhí)行的,在JavaScript中有三種執(zhí)行上下文:

  • 全局執(zhí)行上下文
  • 函數執(zhí)行上下文,JS函數被調用時都會創(chuàng)建一個函數執(zhí)行上下文
  • eval執(zhí)行上下文,eval函數產生的上下文(用的較少)

通常來說我們的JS代碼都不止一個上下文,那么這些上下文的執(zhí)行順序是怎樣的呢?

我們都知道棧是一種后進先出的數據結構,我們JavaScript中的執(zhí)行棧就是一種這樣的棧結構,當JS引擎執(zhí)行代碼時,會產生一個全局上下文并把它壓入執(zhí)行棧,每當遇到函數調用時,就會產生函數執(zhí)行上下文并壓入執(zhí)行棧。引擎從棧頂開始執(zhí)行函數,執(zhí)行完后會彈出該執(zhí)行上下文。

function add(){
  console.log(1)
  foo()
  console.log(3)
}

function foo(){
  console.log(2)
}
add()

我們來看下上面這段代碼的執(zhí)行棧是怎樣的:

任務隊列:

前面我們說到了JavaScript中所有的任務分為同步任務與異步任務,同步任務,顧名思義就是立即執(zhí)行的任務,它一般是直接進入到主線程中執(zhí)行。而我們的異步任務則是進入任務隊列等待主線程中的任務執(zhí)行完再執(zhí)行。

任務隊列是一個事件的隊列,表示相關的異步任務可以進入執(zhí)行棧了。主線程讀取任務隊列就是讀取里面有哪些事件。

隊列是一種先進先出的數據結構。

上面我們說到異步任務又可以分為宏任務與微任務,所以任務隊列也可以分為宏任務隊列微任務隊列

  • Macrotask Queue:進行比較大型的工作,常見的有setTimeout,setInterval,用戶交互操作,UI渲染等;
  • Microtask Queue:進行較小的工作,常見的有PromiseProcess.nextTick;

5.事件循環(huán)(Event-Loop)

  • 同步任務直接放入到主線程執(zhí)行,異步任務(點擊事件,定時器,ajax等)掛在后臺執(zhí)行,等待I/O事件完成或行為事件被觸發(fā)。
  • 系統(tǒng)后臺執(zhí)行異步任務,如果某個異步任務事件(或者行為事件被觸發(fā)),則將該任務添加到任務隊列,并且每個任務會對應一個回調函數進行處理。
  • 這里異步任務分為宏任務與微任務,宏任務進入到宏任務隊列,微任務進入到微任務隊列。
  • 執(zhí)行任務隊列中的任務具體是在執(zhí)行棧中完成的,當主線程中的任務全部執(zhí)行完畢后,去讀取微任務隊列,如果有微任務就會全部執(zhí)行,然后再去讀取宏任務隊列
  • 上述過程會不斷的重復進行,也就是我們常說的事件循環(huán)(Event-Loop)。

例題驗證:

我們來看道題目進行驗證

(async ()=>{
    console.log(1) 
  
    setTimeout(() => {
    console.log('setTimeout1')
    }, 0);
  
    function foo (){
        return new Promise((res,rej) => {
            console.log(2)
            res(3)
        })
    }
  
    new Promise((resolve,reject)=>{
    console.log(4)
    resolve() 
    console.log(5)
    }).then(()=> {
    console.log('6')
    })
  
    const res = await foo();
    console.log(res);
    console.log('7')
  
    setTimeout(_ => console.log('setTimeout2'))
})()

打印順序是:1,4,5,2,6,3,7,setTimeout1,setTimeout2

分析:

  • 代碼自上而下執(zhí)行,先遇到console.log(1) ,直接打印1,接著遇到定時器屬于宏任務,放入宏任務隊列
  • 再遇到promise,由于new Promise是一個同步任務,所以直接打印4,遇到resolve,也就是后面的then函數,放入微任務隊列,再打印5
  • 然后再執(zhí)行await foo,foo函數里面有個promisenew promise屬于同步任務,所以會直接打印2,await返回的是一個promise的回調,await后面的任務放入微任務隊列
  • 最后遇到一個定時器,放入宏任務隊列
  • 執(zhí)行棧任務執(zhí)行完了,先去微任務隊列獲取微任務執(zhí)行,先執(zhí)行第一個微任務,打印6,再執(zhí)行第二個微任務,打印3,7
  • 微任務執(zhí)行完,再去宏任務隊列獲取宏任務執(zhí)行,打印setTimeout1,setTimeout2

6.定時器

JavaScript的任務隊列中的異步任務還包括定時器事件,即指定某些代碼在多長時間后執(zhí)行。定時器功能主要由setTimeout()和setInterval()兩個函數來完成,他們的內部執(zhí)行機制完全一樣,區(qū)別主要在于setTimeout是一次執(zhí)行的過程,setInterval則是反復執(zhí)行的過程。

setTimeout函數接受兩個參數,第一個是要執(zhí)行的回調函數,第二個是推遲執(zhí)行的時間(ms)

如果我們把推遲時間設為0,是不是就會立即執(zhí)行呢?

setTimeout(()=>{
    console.log(1)
},0)

console.log(2)

但事實并不是這樣的,上面的打印結果是先打印2,再打印1。是不是覺得很蒙?

我們用上面的事件循環(huán)的規(guī)則來理解就很清晰了,全局代碼執(zhí)行,遇到定時器setTimeout,放入宏任務隊列,接著往下執(zhí)行同步代碼,打印2,執(zhí)行棧任務執(zhí)行完再去微任務隊列,沒有微任務再去看宏任務隊列,有一個宏任務,執(zhí)行打印1。

setTimeout(fn,0)的含義是,指定某個任務在主線程最早可得的空閑時間執(zhí)行,也就是說,盡可能早得執(zhí)行。它在"任務隊列"的尾部添加一個事件,因此要等到同步任務和"任務隊列"現有的事件都處理完,才會得到執(zhí)行。

HTML5標準規(guī)定了setTimeout()的第二個參數的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常不會立即執(zhí)行,而是每16毫秒執(zhí)行一次。這時使用requestAnimationFrame()的效果要好于setTimeout()。

需要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執(zhí)行棧)執(zhí)行完,主線程才會去執(zhí)行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以并沒有辦法保證,回調函數一定會在setTimeout()指定的時間執(zhí)行。

到此這篇關于JavaScript執(zhí)行機制詳細介紹的文章就介紹到這了,更多相關JavaScript執(zhí)行機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論