成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Javascript執(zhí)行機(jī)制--單線程,同異步任務(wù),事件循環(huán)

gaomysion / 3119人閱讀

摘要:如果過(guò)程中遇到引擎執(zhí)行會(huì)被掛起線程,更新保存在一個(gè)隊(duì)列中等待引擎空閑才執(zhí)行引擎線程負(fù)責(zé)解析運(yùn)行執(zhí)行時(shí)間過(guò)程會(huì)導(dǎo)致頁(yè)面渲染加載阻塞事件觸發(fā)線程,瀏覽器用以控制事件循環(huán)。

總所周知,javascript是一門(mén)依賴宿主環(huán)境的單線程的弱腳本語(yǔ)言,這意味著什么?

javascript的運(yùn)行環(huán)境一般都由宿主環(huán)境(如瀏覽器、Node、Ringo等)和執(zhí)行環(huán)境(Javascript引擎V8,JavaScript Core等)共同構(gòu)成;

弱類型定義語(yǔ)言:數(shù)據(jù)類型可以被忽略的語(yǔ)言。例如計(jì)算時(shí)會(huì)在不同類型之間進(jìn)行隱式轉(zhuǎn)換;

在某一時(shí)刻內(nèi)只能執(zhí)行特定的一個(gè)任務(wù),并且會(huì)阻塞其它任務(wù)執(zhí)行;

本文主要講的就是第三點(diǎn),從中引出下一個(gè)問(wèn)題

單線程的設(shè)計(jì)原因?

Javascript當(dāng)初誕生的目的其實(shí)就是因?yàn)楫?dāng)年網(wǎng)絡(luò)技術(shù)十分低效,如表單驗(yàn)證等個(gè)幾十秒才能得到反饋的用戶體驗(yàn)十分糟糕,為了給瀏覽器做些簡(jiǎn)單處理以前由服務(wù)器端負(fù)責(zé)的一些表單驗(yàn)證。被Netscape公司指派花了十天就負(fù)責(zé)設(shè)計(jì)出一門(mén)新語(yǔ)言的Javascript之父就是Brendan Eich。盡管他并不喜歡自己設(shè)計(jì)的這作品,就有了大家都聽(tīng)過(guò)的一句話:

"與其說(shuō)我愛(ài)Javascript,不如說(shuō)我恨它。它是C語(yǔ)言和Self語(yǔ)言一夜情的產(chǎn)物。十八世紀(jì)英國(guó)文學(xué)家約翰遜博士說(shuō)得好:"它的優(yōu)秀之處并非原創(chuàng),它的原創(chuàng)之處并不優(yōu)秀。"(the part that is good is not original, and the part that is original is not good.)"

作為瀏覽器腳本語(yǔ)言而誕生的JavaScript的主要用途是與用戶互動(dòng),以及操作DOM。這決定了它只需要是單線程就足以解決目的,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。但是沒(méi)想到的是之后的網(wǎng)絡(luò)越發(fā)的發(fā)達(dá),這些年來(lái)的瀏覽器大戰(zhàn)為了爭(zhēng)奪地盤(pán),反而讓Javascript被賦予了更多的職責(zé)跟可能性,今時(shí)今日的Javascript必須想方設(shè)法把自身的潛力激發(fā)出來(lái),而單線程的弱點(diǎn)就被無(wú)限放大了,因?yàn)樵谧枞蝿?wù)的過(guò)程中不一定是因?yàn)镃PU被占用了,而可能是因?yàn)镮/O太慢(如AJAX請(qǐng)求,定時(shí)器任務(wù),Dom事件交互等并不消耗CPU的等待造成資源時(shí)間浪費(fèi))。

瀏覽器中Javascript執(zhí)行線程

我們一直都在說(shuō)Javascript是單線程,但瀏覽器是多線程的,在內(nèi)核控制下互相配合以保持同步,主要的常駐線程有:

GUI渲染線程:負(fù)責(zé)渲染界面,解析HTML,CSS,構(gòu)建DOM和Render樹(shù)布局繪制等。如果過(guò)程中遇到JS引擎執(zhí)行會(huì)被掛起線程,GUI更新保存在一個(gè)隊(duì)列中等待JS引擎空閑才執(zhí)行;

JS引擎線程:負(fù)責(zé)解析運(yùn)行Javascript;執(zhí)行時(shí)間過(guò)程會(huì)導(dǎo)致頁(yè)面渲染加載阻塞;

事件觸發(fā)線程,瀏覽器用以控制事件循環(huán)。當(dāng)JS引擎執(zhí)行過(guò)程中觸發(fā)的事件(如點(diǎn)擊,請(qǐng)求等)會(huì)將對(duì)應(yīng)任務(wù)添加到事件線程中,而當(dāng)對(duì)應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí)會(huì)把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列的尾部等到JS引擎空閑時(shí)處理;

定時(shí)器觸發(fā)線程:因?yàn)镴S引擎是單線程容易阻塞,所以需要有多帶帶線程為setTimeout和setInterval計(jì)時(shí)并觸發(fā),同樣是符合觸發(fā)條件(記時(shí)完畢)被觸發(fā)時(shí)會(huì)把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列的尾部等到JS引擎空閑時(shí)處理;W3C標(biāo)準(zhǔn)規(guī)定時(shí)間間隔低于4ms被算為4ms。

異步http請(qǐng)求線程:XMLHttpRequest在連接后瀏覽器新開(kāi)線程去請(qǐng)求,檢測(cè)到狀態(tài)變化如果有設(shè)置回調(diào)函數(shù)會(huì)產(chǎn)生狀態(tài)變更事件,然后把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列的尾部等到JS引擎空閑時(shí)處理;

好像鋪墊的有點(diǎn)多,往外偏了,接下來(lái)往回拉一點(diǎn)談?wù)勥@些怎么運(yùn)行的。

什么是堆(heap)和棧(stack)?

自己畫(huà)了一個(gè)丑丑的圖,大家將就看著吧。

function addOne(n) {
  var x = n + 1;
  return addTwo(x);
}

function addTwo(n) {
  return n + 2;
}

console.log(addOne(1)) //4;

以這個(gè)例子做說(shuō)明。
當(dāng)調(diào)用addOne時(shí)創(chuàng)建一個(gè)包含addOne入?yún)⒑途植孔兞康膸⑻砑舆M(jìn)去stack,當(dāng)調(diào)用到addTwo時(shí)也同樣創(chuàng)建一個(gè)包含addTwo入?yún)⒑途植孔兞康膸⑻砑舆M(jìn)去在首部,執(zhí)行完addTwo函數(shù)并返回時(shí)addTwo幀被移出stack,addOne執(zhí)行完后addOne幀也被移除。
原理:當(dāng)執(zhí)行方法時(shí)都會(huì)建立自己的內(nèi)存棧,在這個(gè)方法內(nèi)定義的入?yún)⒆兞慷紩?huì)保存在棧內(nèi)存里,執(zhí)行結(jié)束后該方法的內(nèi)存棧也將自然銷毀了。

一般來(lái)說(shuō),程序會(huì)劃分有兩種分配內(nèi)存的空間 -- 堆(heap)棧(stack)。

內(nèi)存空間 分配方式 結(jié)構(gòu) 大小 存取速度 釋放機(jī)制
stack 靜態(tài)分配 隨方法執(zhí)行結(jié)束而銷毀
heap 動(dòng)態(tài)分配 沒(méi)有 系統(tǒng)的垃圾回收機(jī)制銷毀

因?yàn)闂V荒艽娣畔麓_定大小的簡(jiǎn)單數(shù)據(jù),所以像變量(其實(shí)也就是一個(gè)記錄了指向復(fù)雜結(jié)構(gòu)數(shù)據(jù)的地址指向,所以變量也是保存在棧里的)和基本類型Undefined、Null、Boolean、Number 和 String等是按值傳遞的都會(huì)保存在棧里,隨著方法執(zhí)行完畢而被銷毀。
堆負(fù)責(zé)存放復(fù)雜結(jié)構(gòu)的對(duì)象,數(shù)組,函數(shù)等創(chuàng)建成本較高并且可重用數(shù)據(jù),即使方法執(zhí)行完也不會(huì)被銷毀,直到系統(tǒng)的垃圾回收機(jī)制核實(shí)了沒(méi)有任何引用才會(huì)回收。
其實(shí)這只是棧的含義之一,Stack的三種含義

有時(shí)候我們代碼有問(wèn)題導(dǎo)致棧堆溢出原因大概是這種情況:

常見(jiàn)情況 可能情況
棧溢出 無(wú)限遞歸死循環(huán),遞歸越深層分配內(nèi)存越多直至超過(guò)限制
堆溢出 循環(huán)生成復(fù)雜結(jié)構(gòu)數(shù)據(jù)

好了,現(xiàn)在再看回上圖,除了heap和stack之外還有一個(gè)。。。

什么是Queue(任務(wù)隊(duì)列)?

Javascript里分兩種隊(duì)列:

宏任務(wù)隊(duì)列(macro tasks):事件循環(huán)中可以有多個(gè)macro tasks,每次循環(huán)只會(huì)提取一個(gè),包括script(全局任務(wù)), setTimeout, setInterval, setImmediate, I/O, UI rendering等.

微任務(wù)隊(duì)列(micro tasks):事件循環(huán)中只有一個(gè)并且有優(yōu)先級(jí)區(qū)別micro tasks,每次循環(huán)會(huì)提取多次直至隊(duì)列清空,包括process.nextTick, Promise, Object.observer, MutationObserver等.

console.log("log start!");
setTimeout(function () {
  console.log("setTimeout300");
}, 300)

Promise.resolve().then(function () {
  console.log("promise resolve");
}).then(function () {
  console.log("promise resolve then");
})

new Promise(function (resolve, reject) {
  console.log("promise pending");
  resolve();
}).then(function () {
  console.log("promise pending then");
})

setTimeout(function () {
  console.log("setTimeout0");
  Promise.resolve().then(function () {
    console.log("promise3 in setTimeout");
  })
}, 0)
console.log("log end!");

// log start!
// promise pending
// log end!
// promise resolve
// promise pending then
// promise resolve then
// setTimeout0
// promise3 in setTimeout
// setTimeout300

例子過(guò)程,具體分析下面再說(shuō)。
第一次執(zhí)行事件打?。簂og start!, promise pending, log end!, promise resolve,promise pending then,promise resolve then;
第二次執(zhí)行事件打印:setTimeout0,promise3 in setTimeout;
第三次執(zhí)行事件打印:setTimeout300;

下面終于開(kāi)始走到正題了

事件循環(huán)(Event Loop)!

我在上面鋪墊了這么多東西,大家大概都能有個(gè)初步印象,然后所謂的Event Loop就是把這些東西串聯(lián)起來(lái)的一種機(jī)制吧,因?yàn)檫@東西各有理解,比如兩位前端大牛之間就有分歧。
阮一峰:JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
樸靈:樸靈評(píng)注
我看過(guò)他們很多的博客和書(shū)籍,對(duì)我?guī)椭己艽?,我就用自己的看法講講我眼中的Event Loop。

1,所有的任務(wù)都被放主線程上運(yùn)行形成一個(gè)執(zhí)行棧(execution context stack),其中的方法入?yún)⒆兞勘4嬖跅?nèi)存中,復(fù)雜結(jié)構(gòu)對(duì)象被保存在堆內(nèi)存中;
2,同步任務(wù)直接執(zhí)行并阻塞后續(xù)任務(wù)等待結(jié)束,其中遇到一些異步任務(wù)會(huì)新開(kāi)線程去執(zhí)行該任務(wù)(如上面提到的定時(shí)器觸發(fā)線程,異步http請(qǐng)求線程等)然后往下執(zhí)行,異步任務(wù)執(zhí)行完返回結(jié)果之后就把回調(diào)事件加入到任務(wù)隊(duì)列(Queue);
3,當(dāng)執(zhí)行棧(execution context stack)所有任務(wù)執(zhí)行完之后,會(huì)到任務(wù)隊(duì)列(Queue)里提取所有的微任務(wù)隊(duì)列(micro tasks)事件執(zhí)行完;
4,一次循環(huán)結(jié)束,GUI渲染線程接管檢查,重新渲染界面;
5,執(zhí)行棧(execution context stack)宏任務(wù)隊(duì)列(macro tasks)提取一個(gè)事件到執(zhí)行,接著主線程就一直重復(fù)第3步;


大概理解就這樣子,當(dāng)然可能會(huì)有點(diǎn)偏差,歡迎指正!

特殊的定時(shí)器

我在上面線程說(shuō)過(guò)

定時(shí)器觸發(fā)線程:因?yàn)镴S引擎是單線程容易阻塞,所以需要有多帶帶線程為setTimeoutsetInterval計(jì)時(shí)并觸發(fā),同樣是符合觸發(fā)條件(記時(shí)完畢)被觸發(fā)時(shí)會(huì)把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列的尾部等到JS引擎空閑時(shí)處理;W3C標(biāo)準(zhǔn)規(guī)定時(shí)間間隔低于4ms被算為4ms。

里面有一些需要特別注意的地方:
1,計(jì)時(shí)完畢只是把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列,依然要等執(zhí)行??臻e才會(huì)去提取隊(duì)列執(zhí)行,這個(gè)概念很重要,切記!即使設(shè)置0秒也不會(huì)立馬執(zhí)行,因?yàn)閃3C標(biāo)準(zhǔn)規(guī)定時(shí)間間隔低于4ms被算為4ms,具體看瀏覽器,我個(gè)人認(rèn)為不管怎樣始終都會(huì)被放置到處理隊(duì)列等待處理;
2,setTimeout重復(fù)執(zhí)行過(guò)程中每次時(shí)間誤差會(huì)影響后續(xù)執(zhí)行時(shí)間,而setInterval是每次精確時(shí)間執(zhí)行,當(dāng)然這是指他們把對(duì)應(yīng)任務(wù)添加到處理隊(duì)列的精確性;

但是setInterval也有一些問(wèn)題:

累計(jì)效應(yīng),如果執(zhí)行棧阻塞時(shí)間足夠長(zhǎng)以至于隊(duì)列中已經(jīng)存在多個(gè)setInterval的對(duì)應(yīng)任務(wù)的情況,執(zhí)行時(shí)間會(huì)遠(yuǎn)低于開(kāi)發(fā)者期望的結(jié)果;

部分瀏覽器(如Safari等)滾動(dòng)過(guò)程不執(zhí)行JS,容易造成卡頓和未知錯(cuò)誤;

瀏覽器最小化顯示時(shí)setInterval會(huì)繼續(xù)執(zhí)行,但是對(duì)應(yīng)任務(wù)會(huì)等到瀏覽器還原再一瞬間全部執(zhí)行;

結(jié)語(yǔ)

坦白講,我原本時(shí)打算寫(xiě)一篇關(guān)于異步編程的文章,然后在鋪墊前文的路上拉不回來(lái)了就變成了一篇梳理Javascript執(zhí)行機(jī)制了,不過(guò)沒(méi)關(guān)系,理解這些也是很重要的

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106220.html

相關(guān)文章

  • 【轉(zhuǎn)】深入理解JS線程機(jī)制【原文作者:MasterYao】

    摘要:的單線程,與它的用途有關(guān)。只要指定過(guò)回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列,等待主線程讀取。四主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。令人困惑的是,文檔中稱,指定的回調(diào)函數(shù),總是排在前面。 原文:http://www.cnblogs.com/Master... 一、為什么JavaScript是單線程? JavaScript語(yǔ)言的一大特點(diǎn)...

    LittleLiByte 評(píng)論0 收藏0
  • JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用

    摘要:機(jī)制詳解與中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代開(kāi)發(fā)語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。事件循環(huán)機(jī)制詳解與實(shí)踐應(yīng)用是典型的單線程單并發(fā)語(yǔ)言,即表示在同一時(shí)間片內(nèi)其只能執(zhí)行單個(gè)任務(wù)或者部分代碼片。 JavaScript Event Loop 機(jī)制詳解與 Vue.js 中實(shí)踐應(yīng)用歸納于筆者的現(xiàn)代 JavaScript 開(kāi)發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。本文依次介紹了函數(shù)調(diào)用棧、MacroTask 與 Micr...

    livem 評(píng)論0 收藏0
  • Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動(dòng)模型

    摘要:使用了一個(gè)事件驅(qū)動(dòng)非阻塞式的模型,使其輕量又高效。的包管理器,是全球最大的開(kāi)源庫(kù)生態(tài)系統(tǒng)。按照這個(gè)定義,之前所述的阻塞,非阻塞,多路復(fù)用信號(hào)驅(qū)動(dòng)都屬于同步。 系列文章 Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動(dòng)模型Nodejs高性能原理(下) --- 事件循環(huán)詳解 前言 終于開(kāi)始我nodejs的博客生涯了,先從基本的原理講起.以前寫(xiě)過(guò)一篇瀏覽器執(zhí)行機(jī)制的文章,和nodej...

    yy736044583 評(píng)論0 收藏0
  • 淺析JavaScript異步

    摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录](méi)有被觸發(fā)或者條件不滿足。同步方式請(qǐng)求異步同步請(qǐng)求當(dāng)請(qǐng)求開(kāi)始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到 一直以來(lái)都知道JavaScript是一門(mén)單線程語(yǔ)言,在筆試過(guò)程中不斷的遇到一些輸出結(jié)果的問(wèn)題,考量的是對(duì)異步編程掌握情況。一般被問(wèn)到異步的時(shí)候腦子里第一反應(yīng)就是Ajax,setTimse...

    Tangpj 評(píng)論0 收藏0
  • 最后一次搞懂 Event Loop

    摘要:由于是單線程的,這些方法就會(huì)按順序被排列在一個(gè)單獨(dú)的地方,這個(gè)地方就是所謂執(zhí)行棧。事件隊(duì)列每次僅執(zhí)行一個(gè)任務(wù),在該任務(wù)執(zhí)行完畢之后,再執(zhí)行下一個(gè)任務(wù)。 Event Loop 是 JavaScript 異步編程的核心思想,也是前端進(jìn)階必須跨越的一關(guān)。同時(shí),它又是面試的必考點(diǎn),特別是在 Promise 出現(xiàn)之后,各種各樣的面試題層出不窮,花樣百出。這篇文章從現(xiàn)實(shí)生活中的例子入手,讓你徹底理解 E...

    gself 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<