日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

說(shuō)到JAVAScript的運(yùn)行原理,自然繞不開(kāi)JS引擎,運(yùn)行上下文,單線程,事件循環(huán),事件驅(qū)動(dòng),回調(diào)函數(shù)等概念。本文主要參考文章[1,2]。

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

  • JS Engine(JS引擎)
  • Runtime(運(yùn)行上下文)
  • Call Stack (調(diào)用棧)
  • Event Loop(事件循環(huán))
  • Callback (回調(diào))

1.JS Engine

簡(jiǎn)單來(lái)說(shuō),JS引擎主要是對(duì)JS代碼進(jìn)行詞法、語(yǔ)法等分析,通過(guò)編譯器將代碼編譯成可執(zhí)行的機(jī)器碼讓計(jì)算機(jī)去執(zhí)行。

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

JavaScript 運(yùn)行原理解析

 

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

  • 棧。用來(lái)存儲(chǔ)方法調(diào)用的地方,以及基礎(chǔ)數(shù)據(jù)類型(如var a = 1)也是存儲(chǔ)在棧里面的,會(huì)隨著方法調(diào)用結(jié)束而自動(dòng)銷(xiāo)毀掉(入棧-->方法調(diào)用后-->出棧)。
  • 堆。JS引擎中給對(duì)象分配的內(nèi)存空間是放在堆中的。如var foo = {name: 'foo'} 那么這個(gè)foo所指向的對(duì)象是存儲(chǔ)在堆中的。

此外,JS中存在閉包的概念,對(duì)于基本類型變量如果存在與閉包當(dāng)中,那么也將存儲(chǔ)在堆中。詳細(xì)可見(jiàn)此處1,3

關(guān)于閉包的情況,就涉及到Captured Variables。我們知道Local Variables是最簡(jiǎn)單的情形,是直接存儲(chǔ)在棧中的。而Captured Variables是對(duì)于存在閉包情況和with,try catch情況的變量。

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

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

2.RunTime

JS在瀏覽器中可以調(diào)用瀏覽器提供的API,如window對(duì)象,DOM相關(guān)API等。這些接口并不是由V8引擎提供的,是存在與瀏覽器當(dāng)中的。因此簡(jiǎn)單來(lái)說(shuō),對(duì)于這些相關(guān)的外部接口,可以在運(yùn)行時(shí)供JS調(diào)用,以及JS的事件循環(huán)(Event Loop)和事件隊(duì)列(Callback Queue),把這些稱為RunTime。有些地方也把JS所用到的core lib核心庫(kù)也看作RunTime的一部分。

JavaScript 運(yùn)行原理解析

 

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

3.Call Stack

JS被設(shè)計(jì)為單線程運(yùn)行的,這是因?yàn)镴S主要用來(lái)實(shí)現(xiàn)很多交互相關(guān)的操作,如DOM相關(guān)操作,如果是多線程會(huì)造成復(fù)雜的同步問(wèn)題。因此JS自誕生以來(lái)就是單線程的,而且主線程都是用來(lái)進(jìn)行界面相關(guān)的渲染操作 (為什么說(shuō)是主線程,因?yàn)閔tml5 提供了Web Worker,獨(dú)立的一個(gè)后臺(tái)JS,用來(lái)處理一些耗時(shí)數(shù)據(jù)操作。因?yàn)椴粫?huì)修改相關(guān)DOM及頁(yè)面元素,因此不影響頁(yè)面性能),如果有阻塞產(chǎn)生會(huì)導(dǎo)致瀏覽器卡死。

如果一個(gè)遞歸調(diào)用沒(méi)有終止條件,是一個(gè)死循環(huán)的話,會(huì)導(dǎo)致調(diào)用棧內(nèi)存不夠而溢出,如:

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

例子中foo函數(shù)循環(huán)調(diào)用其本身,且沒(méi)有終止條件,瀏覽器控制臺(tái)輸出調(diào)用棧達(dá)到最大調(diào)用次數(shù)。

JavaScript 運(yùn)行原理解析

 

JS線程如果遇到比較耗時(shí)操作,如讀取文件,AJAX請(qǐng)求操作怎么辦?這里JS用到了Callback回調(diào)函數(shù)來(lái)處理。

對(duì)于Call%20Stack中的每個(gè)方法調(diào)用,都會(huì)形成它自己的一個(gè)執(zhí)行上下文Execution%20Context,關(guān)于執(zhí)行上下文的詳細(xì)闡述請(qǐng)看這篇文章

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

JS通過(guò)回調(diào)的方式,異步處理耗時(shí)的任務(wù)。一個(gè)簡(jiǎn)單的例子:

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

此時(shí)并不會(huì)得到result的值,result是undefined。這是因?yàn)閍jax的調(diào)用是異步的,當(dāng)前線程并不會(huì)等到ajax請(qǐng)求到結(jié)果后才執(zhí)行console.log語(yǔ)句。而是調(diào)用ajax后請(qǐng)求的操作交給回調(diào)函數(shù),自己是立刻返回。正確的寫(xiě)法應(yīng)該是:

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

此時(shí)才能正確輸出請(qǐng)求返回的結(jié)果。

JS引擎其實(shí)并不提供異步的支持,異步支持主要依賴于運(yùn)行環(huán)境(瀏覽器或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如何調(diào)用回調(diào)函數(shù)實(shí)現(xiàn)異步處理。

所以什么是Event%20Loop?

Event%20Loop只做一件事情,負(fù)責(zé)監(jiān)聽(tīng)Call%20Stack和Callback%20Queue。當(dāng)Call%20Stack里面的調(diào)用棧運(yùn)行完變成空了,Event%20Loop就把Callback%20Queue里面的第一條事件(其實(shí)就是回調(diào)函數(shù))放到調(diào)用棧中并執(zhí)行它,后續(xù)不斷循環(huán)執(zhí)行這個(gè)操作。

一個(gè)setTimeout的例子以及對(duì)應(yīng)的Event%20Loop動(dòng)態(tài)圖:

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

 

setTimeout有個(gè)要注意的地方,如上述例子延遲5s執(zhí)行,不是嚴(yán)格意義上的5s,正確來(lái)說(shuō)是至少5s以后會(huì)執(zhí)行。因?yàn)閃eb API會(huì)設(shè)定一個(gè)5s的定時(shí)器,時(shí)間到期后將回調(diào)函數(shù)加到隊(duì)列中,此時(shí)該回調(diào)函數(shù)還不一定會(huì)馬上運(yùn)行,因?yàn)殛?duì)列中可能還有之前加入的其他回調(diào)函數(shù),而且還必須等到Call Stack空了之后才會(huì)從隊(duì)列中取一個(gè)回調(diào)執(zhí)行。

所以常見(jiàn)的setTimeout(callback, 0) 的做法就是為了在常規(guī)的調(diào)用介紹后馬上運(yùn)行回調(diào)函數(shù)。

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

在說(shuō)一個(gè)容易犯錯(cuò)的栗子:

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

上面這個(gè)栗子并不是輸出0,1,2,3,4,第一反應(yīng)覺(jué)得應(yīng)該是這樣。但梳理了JS的時(shí)間循環(huán)后,應(yīng)該很容易明白。

調(diào)用棧先執(zhí)行 for(var i = 0; i < 5; i++) {...}方法,里面的定時(shí)器會(huì)到時(shí)間后會(huì)直接把回調(diào)函數(shù)放到事件隊(duì)列中,等f(wàn)or循環(huán)執(zhí)行完在依次取出放進(jìn)調(diào)用棧。當(dāng)for循環(huán)執(zhí)行完時(shí),i的值已經(jīng)變成5,所以最后輸出全都是5。

關(guān)于定時(shí)器又可以看看這篇有意思的文章

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

總結(jié)

最后總結(jié)一下,JS的運(yùn)行原理主要有以下幾個(gè)方面:

  • JS引擎主要負(fù)責(zé)把JS代碼轉(zhuǎn)為機(jī)器能執(zhí)行的機(jī)器碼,而JS代碼中調(diào)用的一些WEB API則由其運(yùn)行環(huán)境提供,這里指的是瀏覽器。
  • JS是單線程運(yùn)行,每次都從調(diào)用棧出取出代碼進(jìn)行調(diào)用。如果當(dāng)前代碼非常耗時(shí),則會(huì)阻塞當(dāng)前線程導(dǎo)致瀏覽器卡頓。
  • 回調(diào)函數(shù)是通過(guò)加入到事件隊(duì)列中,等待Event Loop拿出并放到調(diào)用棧中進(jìn)行調(diào)用。只有Event Loop監(jiān)聽(tīng)到調(diào)用棧為空時(shí),才會(huì)從事件隊(duì)列中從隊(duì)頭拿出回調(diào)函數(shù)放進(jìn)調(diào)用棧里。

分享到:
標(biāo)簽:JavaScript
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定