Javascript事件循環(huán)機(jī)制

通過學(xué)習(xí)JavaScript赞草,我們都知道它是一門單線程語言讹堤,也就是說,在同一時(shí)刻房资,最多也只有一個(gè)代碼段在執(zhí)行蜕劝,但一次只能執(zhí)行一個(gè)任務(wù),這些任務(wù)形成一個(gè)任務(wù)隊(duì)列排隊(duì)等候執(zhí)行轰异,但前端的某些任務(wù)是非常耗時(shí)的,比如網(wǎng)絡(luò)請(qǐng)求暑始,定時(shí)器和事件監(jiān)聽搭独,如果讓他們和別的任務(wù)一樣,都老老實(shí)實(shí)的排隊(duì)等待執(zhí)行的話廊镜,執(zhí)行效率會(huì)非常的低牙肝,甚至導(dǎo)致頁面的假死。所以嗤朴,瀏覽器為這些耗時(shí)任務(wù)開辟了另外的線程配椭,主要包括http請(qǐng)求線程,瀏覽器定時(shí)觸發(fā)器雹姊,瀏覽器事件觸發(fā)線程股缸,這些任務(wù)是異步的。也就是我們所說的事件循環(huán)機(jī)制吱雏。


事件循環(huán)機(jī)制.png

從上圖我們可以看出敦姻,js主線程它是有一個(gè)執(zhí)行棧的,所有的js代碼都會(huì)在執(zhí)行棧里運(yùn)行歧杏。在執(zhí)行代碼過程中镰惦,如果遇到一些異步代碼(比如setTimeout,ajax,promise.then以及用戶點(diǎn)擊等操作),那么瀏覽器就會(huì)將這些代碼放到一個(gè)幕后線程中去等待,不阻塞主線程的執(zhí)行犬绒,主線程繼續(xù)執(zhí)行棧中剩余的代碼旺入,當(dāng)幕后線程(background thread)里的代碼準(zhǔn)備好了(比如setTimeout時(shí)間到了,ajax請(qǐng)求得到響應(yīng)),該線程就會(huì)將它的回調(diào)函數(shù)放到任務(wù)隊(duì)列中等待執(zhí)行凯力。而當(dāng)主線程執(zhí)行完棧中的所有代碼后茵瘾,它就會(huì)檢查任務(wù)隊(duì)列是否有任務(wù)要執(zhí)行急膀,如果有任務(wù)要執(zhí)行的話,那么就將該任務(wù)放到執(zhí)行棧中執(zhí)行龄捡。如果當(dāng)前任務(wù)隊(duì)列為空的話卓嫂,它就會(huì)一直循環(huán)等待任務(wù)到來。因此聘殖,這叫做事件循環(huán)晨雳。

事件循環(huán)過程:

javascript在執(zhí)行的時(shí)候,按順序從上往下執(zhí)行奸腺,當(dāng)遇到異步代碼的時(shí)候餐禁,瀏覽器會(huì)把這些異步代碼放到其他執(zhí)行模塊中去執(zhí)行,不阻塞主線程的執(zhí)行突照,當(dāng)執(zhí)行完畢后帮非,將回調(diào)函數(shù)放在任務(wù)隊(duì)列中(taskquene)中,當(dāng)主線程執(zhí)行完棧中的所有代碼后讹蘑,然后會(huì)檢查任務(wù)隊(duì)列中是否有任務(wù)要執(zhí)行末盔,如果有,就將該任務(wù)放到 執(zhí)行棧中執(zhí)行座慰,如果為空陨舱,則一直循環(huán)等待任務(wù)的到來。
因?yàn)槿蝿?wù)隊(duì)列有兩種形式:

macrotask 和 microtask 是異步任務(wù)的兩種分類版仔。在掛起任務(wù)時(shí)游盲,JS 引擎會(huì)將所有任務(wù)按照類別分到這兩個(gè)隊(duì)列中,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù)蛮粮,執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行益缎;之后再取 macrotask 任務(wù),周而復(fù)始然想,直至兩個(gè)隊(duì)列的任務(wù)都取完莺奔。

macro-task: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, Promises(這里指瀏覽器實(shí)現(xiàn)的原生 Promise), Object.observe, MutationObserver

1.檢查Macrotask 隊(duì)列是否為空,若不為空,則進(jìn)行下一步又沾,若為空弊仪,則跳到3
2.從Macrotask隊(duì)列中取隊(duì)首(在隊(duì)列時(shí)間最長(zhǎng))的任務(wù)進(jìn)去執(zhí)行棧中執(zhí)行(僅僅一個(gè)),執(zhí)行完后進(jìn)入下一步
3.檢查Microtask隊(duì)列是否為空杖刷,若不為空励饵,則進(jìn)入下一步,否則滑燃,跳到1(開始新的事件循環(huán))
4.從Microtask隊(duì)列中取隊(duì)首(在隊(duì)列時(shí)間最長(zhǎng))的任務(wù)進(jìn)去事件隊(duì)列執(zhí)行,執(zhí)行完后役听,跳到3;
現(xiàn)在我們已經(jīng)知道了什么是事件循環(huán)機(jī)制,接下來講幾個(gè)我們需要注意的點(diǎn)典予。

(1)setTimeout的時(shí)間設(shè)置為0甜滨,會(huì)立即執(zhí)行嗎?

答案很顯然瘤袖,不會(huì)衣摩,無論我們?cè)O(shè)置多少的延遲時(shí)間,setTimeout總是會(huì)在 主線程的任務(wù)執(zhí)行完z之后輸出捂敌。

<script>
            console.log(1);
            setTimeout(function(){console.log(2);}, 0);
            console.log(3);
        </script>

最后的輸出結(jié)果:


事件循環(huán)機(jī)制setTimeout.png

可能有些瀏覽器可能會(huì)有一個(gè)最小延遲時(shí)間艾扮,有的是 15ms,有的是 10ms占婉,所以會(huì)造成一些錯(cuò)覺泡嘴,所以我們?cè)倏匆幌孪旅孢@個(gè)例子:

            <script>
            console.log("script start");
            setTimeout(function () {
            console.log("setTimeout");
            }, 0);
            //具體數(shù)字不定,這取決于你的硬件配置和瀏覽器
            for(var i = 0; i < 9999; i ++){
                console.log(i);
            }
            console.log("script end");
        </script>

這個(gè)數(shù)字我試了下99999999逆济,結(jié)果電腦就......

(2)Ajax請(qǐng)求是否異步

ajax請(qǐng)求內(nèi)容的時(shí)候是異步的酌予,當(dāng)請(qǐng)求完成后,會(huì)觸發(fā)請(qǐng)求完成的事件奖慌,然后把回調(diào)函數(shù)放入callback queue抛虫,等到主線程執(zhí)行該回調(diào)函數(shù)時(shí)還是單線程的。
這里簡(jiǎn)單說一下Ajax異步請(qǐng)求的過程:
首先說明一下Ajax是局部刷新頁面而不是整體加載頁面升薯,能夠減輕服務(wù)器壓力莱褒,提高用戶體驗(yàn)。

如何使用Ajax涎劈?

第一步:創(chuàng)建xmlhttprequest對(duì)象,var xmlhttp =new XMLHttpRequest();XMLHttpRequest對(duì)象用來和服務(wù)器交換數(shù)據(jù)

var xhttp;
if (window.XMLHttpRequest) {
//現(xiàn)代主流瀏覽器
xhttp = new XMLHttpRequest();
} else {
// 針對(duì)瀏覽器阅茶,比如IE5或IE6
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

第二步:使用xmlhttprequest對(duì)象的open()和send()方法發(fā)送資源請(qǐng)求給服務(wù)器蛛枚。

xmlhttp.open(method,url,async) method包括get 和post,url主要是文件或資源的路徑脸哀,async參數(shù)為true(代表異步)或者false(代表同步)

xhttp.send();使用get方法發(fā)送請(qǐng)求到服務(wù)器蹦浦。

xhttp.send(string);使用post方法發(fā)送請(qǐng)求到服務(wù)器。

post 發(fā)送請(qǐng)求什么時(shí)候能夠使用呢撞蜂?

(1)更新一個(gè)文件或者數(shù)據(jù)庫的時(shí)候盲镶。

(2)發(fā)送大量數(shù)據(jù)到服務(wù)器,因?yàn)閜ost請(qǐng)求沒有字符限制蝌诡。

(3)發(fā)送用戶輸入的加密數(shù)據(jù)溉贿。

get例子:

xhttp.open("GET", "ajax_info.txt", true);
xhttp.open("GET", "index.html", true);
xhttp.open("GET", "demo_get.asp?t=" + Math.random(), true);xhttp.send();

post例子

xhttp.open("POST", "demo_post.asp", true);
xhttp.send();

post表單數(shù)據(jù)需要使用xmlhttprequest對(duì)象的setRequestHeader方法增加一個(gè)HTTP頭。

post表單例子

xhttp.open("POST", "ajax_test.aspx", true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("fname=Henry&lname=Ford");
async參數(shù)

(1)async=true 當(dāng)服務(wù)器準(zhǔn)備響應(yīng)時(shí)將執(zhí)行onreadystatechange函數(shù)浦旱。

xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "index.aspx", true);
xhttp.send();

(2)async=false 則將不需要寫onreadystatechange函數(shù)宇色,直接在send后面寫上執(zhí)行代碼。

xhttp.open("GET", "index.aspx", false);
xhttp.send();
document.getElementById("demo").innerHTML = xhttp.responseText;
第三步:使用xmlhttprequest對(duì)象的responseText或responseXML屬性獲得服務(wù)器的響應(yīng)蘸吓。

使用responseText屬性得到服務(wù)器響應(yīng)的字符串?dāng)?shù)據(jù)维哈,使用responseXML屬性得到服務(wù)器響應(yīng)的XML數(shù)據(jù)。

例子如下:

document.getElementById("demo").innerHTML = xhttp.responseText;

服務(wù)器響應(yīng)的XML數(shù)據(jù)需要使用XML對(duì)象進(jìn)行轉(zhuǎn)換邮绿。

例子:

xmlDoc = xhttp.responseXML;
txt = "";
x = xmlDoc.getElementsByTagName("ARTIST");
for (i = 0; i < x.length; i++) {
txt += x[i].childNodes[0].nodeValue + "<br>";
}
document.getElementById("demo").innerHTML = txt;
第四步:onreadystatechange函數(shù)抢蚀,當(dāng)發(fā)送請(qǐng)求到服務(wù)器镀层,我們想要服務(wù)器響應(yīng)執(zhí)行一些功能就需要使用onreadystatechange函數(shù),每次xmlhttprequest對(duì)象的readyState發(fā)生改變都會(huì)觸發(fā)onreadystatechange函數(shù)皿曲。
onreadystatechange屬性存儲(chǔ)一個(gè)當(dāng)readyState發(fā)生改變時(shí)自動(dòng)被調(diào)用的函數(shù)唱逢。
readyState屬性,XMLHttpRequest對(duì)象的狀態(tài)谷饿,改變從0到4惶我,0代表請(qǐng)求未被初始化,1代表服務(wù)器連接成功博投,2請(qǐng)求被服務(wù)器接收绸贡,3處理請(qǐng)求,4請(qǐng)求完成并且響應(yīng)準(zhǔn)備毅哗。
status屬性听怕,200表示成功響應(yīng),404表示頁面不存在虑绵。
ajax第四步圖解.png

在onreadystatechange事件中尿瞭,服務(wù)器響應(yīng)準(zhǔn)備的時(shí)候發(fā)生,當(dāng)readyState==4和status==200的時(shí)候服務(wù)器響應(yīng)準(zhǔn)備翅睛。

例子:

function loadDoc() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "ajax_info.txt", true);
xhttp.send();
} 
//函數(shù)作為參數(shù)調(diào)用
<!DOCTYPE html>
<html>
<body>
<p id="demo">Let AJAX change this text.</p>
<button type="button"
onclick="loadDoc('index.aspx', myFunction)">Change Content
</button>
<script>
function loadDoc(url, cfunc) {
var xhttp;
xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
cfunc(xhttp);
}
};
xhttp.open("GET", url, true);
xhttp.send();
}
function myFunction(xhttp) {
document.getElementById("demo").innerHTML = xhttp.responseText;
}
</script>
</body>
</html>
(3)界面渲染線程是單獨(dú)開辟的線程声搁,是不是DOM一變化,界面就立刻重新渲染捕发?

如果DOM一變化疏旨,界面就立刻重新渲染,效率必然很低扎酷,所以瀏覽器的機(jī)制規(guī)定界面渲染線程和主線程是互斥的檐涝,主線程執(zhí)行任務(wù)時(shí),瀏覽器渲染線程處于掛起狀態(tài)法挨。

好谁榜,再寫一個(gè)例子總結(jié)說明一下事件循環(huán)機(jī)制
test:

console.log(1)
setTimeout(function() {
  //settimeout1
  console.log(2)
}, 0);
const intervalId = setInterval(function() {
  //setinterval1
  console.log(3)
}, 0)
setTimeout(function() {
  //settimeout2
  console.log(10)
  new Promise(function(resolve) {
    //promise1
    console.log(11)
    resolve()
  })
  .then(function() {
    console.log(12)
  })
  .then(function() {
    console.log(13)
    clearInterval(intervalId)
  })
}, 0);

//promise2
Promise.resolve()
  .then(function() {
    console.log(7)
  })
  .then(function() {
    console.log(8)
  })
console.log(9)
運(yùn)行結(jié)果.png

理解:

第一次事件循環(huán):

console.log(1)被執(zhí)行,輸出1
settimeout1執(zhí)行凡纳,加入macrotask隊(duì)列
setinterval1執(zhí)行窃植,加入macrotask隊(duì)列
settimeout2執(zhí)行,加入macrotask隊(duì)列
promise2執(zhí)行惫企,它的兩個(gè)then函數(shù)加入microtask隊(duì)列
console.log(9)執(zhí)行撕瞧,輸出9

關(guān)于Promise陵叽,Promise的回調(diào)函數(shù)不是傳入的,而是使用then來調(diào)用的丛版。因此巩掺,Promise中定義的函數(shù)應(yīng)該是馬上執(zhí)行的,then才是其回調(diào)函數(shù)页畦,放入microtask中胖替。

根據(jù)事件循環(huán)的定義,接下來會(huì)執(zhí)行新增的microtask任務(wù)豫缨,按照進(jìn)入隊(duì)列的順序独令,執(zhí)行console.log(7)和console.log(8),輸出7和8;

當(dāng)然這里肯定有一個(gè)疑問好芭,為什么不執(zhí)行macrotask隊(duì)列中的任務(wù)燃箭?這是因?yàn)橐婚_始js主線程中跑的任務(wù)就是macrotask任務(wù),而根據(jù)事件循環(huán)的流程舍败,一次事件循環(huán)只會(huì)執(zhí)行一個(gè)macrotask任務(wù)招狸,因此,執(zhí)行完主線程的代碼后邻薯,它就去從microtask隊(duì)列里取隊(duì)首任務(wù)來執(zhí)行裙戏。

microtask隊(duì)列為空,回到第一步厕诡,進(jìn)入下一個(gè)事件循環(huán)累榜,此時(shí)macrotask隊(duì)列為: settimeout1,setinterval1,settimeout2

第二次事件循環(huán):

從macrotask隊(duì)列里取位于隊(duì)首的任務(wù)(settimeout1)并執(zhí)行,輸出2
microtask隊(duì)列為空灵嫌,回到第一步壹罚,進(jìn)入下一個(gè)事件循環(huán),此時(shí)macrotask隊(duì)列為: setinterval1,settimeout2

第三次事件循環(huán):

從macrotask隊(duì)列里取位于隊(duì)首的任務(wù)(setinterval1)并執(zhí)行寿羞,輸出3,然后又將新生成的setinterval1加入macrotask隊(duì)列
microtask隊(duì)列為空渔嚷,回到第一步,進(jìn)入下一個(gè)事件循環(huán)稠曼,此時(shí)macrotask隊(duì)列為: settimeout2,setinterval1

第四次事件循環(huán):

從macrotask隊(duì)列里取位于隊(duì)首的任務(wù)(settimeout2)并執(zhí)行,輸出10,并且執(zhí)行new Promise內(nèi)的函數(shù)(new Promise內(nèi)的函數(shù)是同步操作客年,并不是異步操作),輸出11霞幅,并且將它的兩個(gè)then函數(shù)加入microtask隊(duì)列
從microtask隊(duì)列中,取隊(duì)首的任務(wù)執(zhí)行量瓜,直到為空為止司恳。因此,兩個(gè)新增的microtask任務(wù)按順序執(zhí)行绍傲,輸出12和13扔傅,并且將setinterval1清空
此時(shí)耍共,microtask隊(duì)列和macrotask隊(duì)列都為空,瀏覽器會(huì)一直檢查隊(duì)列是否為空猎塞,等待新的任務(wù)加入隊(duì)列试读。
當(dāng)然,

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荠耽,一起剝皮案震驚了整個(gè)濱河市钩骇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铝量,老刑警劉巖倘屹,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異慢叨,居然都是意外死亡纽匙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門拍谐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烛缔,“玉大人,你說我怎么就攤上這事赠尾×λ耄” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵气嫁,是天一觀的道長(zhǎng)当窗。 經(jīng)常有香客問我,道長(zhǎng)寸宵,這世上最難降的妖魔是什么崖面? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮梯影,結(jié)果婚禮上巫员,老公的妹妹穿的比我還像新娘。我一直安慰自己甲棍,他們只是感情好简识,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著感猛,像睡著了一般七扰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陪白,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天颈走,我揣著相機(jī)與錄音,去河邊找鬼咱士。 笑死立由,一個(gè)胖子當(dāng)著我的面吹牛轧钓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锐膜,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼毕箍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了枣耀?” 一聲冷哼從身側(cè)響起霉晕,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捞奕,沒想到半個(gè)月后牺堰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颅围,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年伟葫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片院促。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筏养,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出常拓,到底是詐尸還是另有隱情渐溶,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布弄抬,位于F島的核電站茎辐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掂恕。R本人自食惡果不足惜拖陆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懊亡。 院中可真熱鬧依啰,春花似錦、人聲如沸店枣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸯两。三九已至坏瞄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甩卓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工蕉斜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逾柿,地道東北人缀棍。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像机错,于是被迫代替她去往敵國(guó)和親爬范。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容