亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

說到JAVAScript的運行原理,自然繞不開JS引擎,運行上下文,單線程,事件循環,事件驅動,回調函數等概念。本文主要參考文章[1,2]。

為了更好的理解JavaScript如何工作的,首先要理解以下幾個概念。

  • JS Engine(JS引擎)
  • Runtime(運行上下文)
  • Call Stack (調用棧)
  • Event Loop(事件循環)
  • Callback (回調)

1.JS Engine

簡單來說,JS引擎主要是對JS代碼進行詞法、語法等分析,通過編譯器將代碼編譯成可執行的機器碼讓計算機去執行。

目前最流行的JS引擎非V8莫屬了,Chrome瀏覽器和Node.js采用的引擎就是V8引擎。引擎的結構可以簡單由下圖表示:

JavaScript 運行原理解析

 

就如JVM虛擬機一樣,JS引擎中也有堆(Memory Heap)和棧(Call Stack)的概念。

  • 棧。用來存儲方法調用的地方,以及基礎數據類型(如var a = 1)也是存儲在棧里面的,會隨著方法調用結束而自動銷毀掉(入棧-->方法調用后-->出棧)。
  • 堆。JS引擎中給對象分配的內存空間是放在堆中的。如var foo = {name: 'foo'} 那么這個foo所指向的對象是存儲在堆中的。

此外,JS中存在閉包的概念,對于基本類型變量如果存在與閉包當中,那么也將存儲在堆中。詳細可見此處1,3

關于閉包的情況,就涉及到Captured Variables。我們知道Local Variables是最簡單的情形,是直接存儲在棧中的。而Captured Variables是對于存在閉包情況和with,try catch情況的變量。

function foo () {
 var x; // local variables
 var y; // captured variable, bar中引用了y
 function bar () {
 // bar 中的context會capture變量y
 use(y);
 }
 return bar;
}
復制代碼

如上述情況,變量y存在與bar()的閉包中,因此y是captured variable,是存儲在堆中的。

2.RunTime

JS在瀏覽器中可以調用瀏覽器提供的API,如window對象,DOM相關API等。這些接口并不是由V8引擎提供的,是存在與瀏覽器當中的。因此簡單來說,對于這些相關的外部接口,可以在運行時供JS調用,以及JS的事件循環(Event Loop)和事件隊列(Callback Queue),把這些稱為RunTime。有些地方也把JS所用到的core lib核心庫也看作RunTime的一部分。

JavaScript 運行原理解析

 

同樣,在Node.js中,可以把Node的各種庫提供的API稱為RunTime。所以可以這么理解,Chrome和Node.js都采用相同的V8引擎,但擁有不同的運行環境(RunTime Environments)[4]。

3.Call Stack

JS被設計為單線程運行的,這是因為JS主要用來實現很多交互相關的操作,如DOM相關操作,如果是多線程會造成復雜的同步問題。因此JS自誕生以來就是單線程的,而且主線程都是用來進行界面相關的渲染操作 (為什么說是主線程,因為html5 提供了Web Worker,獨立的一個后臺JS,用來處理一些耗時數據操作。因為不會修改相關DOM及頁面元素,因此不影響頁面性能),如果有阻塞產生會導致瀏覽器卡死。

如果一個遞歸調用沒有終止條件,是一個死循環的話,會導致調用棧內存不夠而溢出,如:

function foo() {
 foo();
}
foo();
復制代碼

例子中foo函數循環調用其本身,且沒有終止條件,瀏覽器控制臺輸出調用棧達到最大調用次數。

JavaScript 運行原理解析

 

JS線程如果遇到比較耗時操作,如讀取文件,AJAX請求操作怎么辦?這里JS用到了Callback回調函數來處理。

對于Call%20Stack中的每個方法調用,都會形成它自己的一個執行上下文Execution%20Context,關于執行上下文的詳細闡述請看這篇文章

4.Event%20Loop%20&%20Callback

JS通過回調的方式,異步處理耗時的任務。一個簡單的例子:

var%20result%20=%20ajax('...');
console.log(result);
復制代碼

此時并不會得到result的值,result是undefined。這是因為ajax的調用是異步的,當前線程并不會等到ajax請求到結果后才執行console.log語句。而是調用ajax后請求的操作交給回調函數,自己是立刻返回。正確的寫法應該是:

ajax('...',%20function(result)%20{
%20console.log(result);
})
復制代碼

此時才能正確輸出請求返回的結果。

JS引擎其實并不提供異步的支持,異步支持主要依賴于運行環境(瀏覽器或Node.js)。

So,%20for%20example,%20when%20your%20JavaScript%20program%20makes%20an%20Ajax%20request%20to%20fetch%20some%20data%20from%20the%20server,%20you%20set%20up%20the%20“response”%20code%20in%20a%20function%20(the%20“callback”),%20and%20the%20JS%20Engine%20tells%20the%20hosting%20environment:%20“Hey,%20I’m%20going%20to%20suspend%20execution%20for%20now,%20but%20whenever%20you%20finish%20with%20that%20network%20request,%20and%20you%20have%20some%20data,%20please%20call%20this%20function%20back.”

The%20browser%20is%20then%20set%20up%20to%20listen%20for%20the%20response%20from%20the%20network,%20and%20when%20it%20has%20something%20to%20return%20to%20you,%20it%20will%20schedule%20the%20callback%20function%20to%20be%20executed%20by%20inserting%20it%20into%20the%20event%20loop.

上面這兩段話摘自于How%20JavaScript%20works,以通俗的方式解釋了JS如何調用回調函數實現異步處理。

所以什么是Event%20Loop?

Event%20Loop只做一件事情,負責監聽Call%20Stack和Callback%20Queue。當Call%20Stack里面的調用棧運行完變成空了,Event%20Loop就把Callback%20Queue里面的第一條事件(其實就是回調函數)放到調用棧中并執行它,后續不斷循環執行這個操作。

一個setTimeout的例子以及對應的Event%20Loop動態圖:

console.log('Hi');
setTimeout(function%20cb1()%20{%20
%20console.log('cb1');
},%205000);
console.log('Bye');
復制代碼

 

setTimeout有個要注意的地方,如上述例子延遲5s執行,不是嚴格意義上的5s,正確來說是至少5s以后會執行。因為Web API會設定一個5s的定時器,時間到期后將回調函數加到隊列中,此時該回調函數還不一定會馬上運行,因為隊列中可能還有之前加入的其他回調函數,而且還必須等到Call Stack空了之后才會從隊列中取一個回調執行。

所以常見的setTimeout(callback, 0) 的做法就是為了在常規的調用介紹后馬上運行回調函數。

console.log('Hi');
setTimeout(function() {
 console.log('callback');
}, 0);
console.log('Bye');
// 輸出
// Hi
// Bye
// callback
復制代碼

在說一個容易犯錯的栗子:

for (var i = 0; i < 5; i++) {
 setTimeout(function() {
 console.log(i);
 }, 1000 * i);
}
	
// 輸出:5 5 5 5 5
復制代碼

上面這個栗子并不是輸出0,1,2,3,4,第一反應覺得應該是這樣。但梳理了JS的時間循環后,應該很容易明白。

調用棧先執行 for(var i = 0; i < 5; i++) {...}方法,里面的定時器會到時間后會直接把回調函數放到事件隊列中,等for循環執行完在依次取出放進調用棧。當for循環執行完時,i的值已經變成5,所以最后輸出全都是5。

關于定時器又可以看看這篇有意思的文章

最后關于Event Loop,可以參考下這個視頻。到目前為止說的event loop是前端瀏覽器中的event loop,關于Nodejs的Event Loop的細節闡述,請看我的另一篇文章Node.js design pattern : Reactor (Event Loop)。兩者的區別對比可查看這篇文章你不知道的Event Loop,對兩種event loop做了相關總結和比較。

總結

最后總結一下,JS的運行原理主要有以下幾個方面:

  • JS引擎主要負責把JS代碼轉為機器能執行的機器碼,而JS代碼中調用的一些WEB API則由其運行環境提供,這里指的是瀏覽器。
  • JS是單線程運行,每次都從調用棧出取出代碼進行調用。如果當前代碼非常耗時,則會阻塞當前線程導致瀏覽器卡頓。
  • 回調函數是通過加入到事件隊列中,等待Event Loop拿出并放到調用棧中進行調用。只有Event Loop監聽到調用棧為空時,才會從事件隊列中從隊頭拿出回調函數放進調用棧里。

分享到:
標簽:JavaScript
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定