單線程JavaScript


什么是線程

由于JavaScript是單線程語言,因此,在一個進(jìn)程上,只能運(yùn)行一個線程,而不能多個線程同時運(yùn)行。也就是說JavaScript不允許多個線程共享內(nèi)存空間。因此,如果有多個線程想同時運(yùn)行,則需采取排隊(duì)的方式,即只有當(dāng)前一個線程執(zhí)行完畢,后一個線程才開始執(zhí)行。

Heap 、Stack、 Queue

堆:對象被分配在一個堆中,用以表示一個內(nèi)存中未被組織的區(qū)域。我們知道,在函數(shù)被調(diào)用之前,JavaScript引擎會對函數(shù)進(jìn)行編譯(詞法分析、語法分析、代碼生成)的工作。當(dāng)完成編譯時會將函數(shù)(這里不限于函數(shù),JavaScript所有皆對象,除了undefined,null)放入堆中,分配內(nèi)存空間,等待執(zhí)行或者調(diào)用。
棧: 當(dāng)函數(shù)調(diào)用時,會形成一個"執(zhí)行棧"。我們看一個簡單的例子。

function bar(b){
  return b*2;
}
function foo(a){
  return bar(a * 3);
}
console.log(foo(1));  //6

當(dāng)JavaScript引擎在編譯階段,會將foo、bar置于堆中,分配內(nèi)存空間。當(dāng)調(diào)用foo()時,引擎創(chuàng)建了一個執(zhí)行棧,包含了foo函數(shù)的參數(shù)和局部變量。當(dāng)在foo的詞法作用域中調(diào)用bar時,會將bar函數(shù)推入執(zhí)行棧,并置于foo函數(shù)之上,同時包含bar函數(shù)的參數(shù)和局部變量。當(dāng)bar返回時(此例中bar函數(shù)調(diào)用并返回結(jié)果是瞬間完成的),bar函數(shù)出棧。當(dāng)foo函數(shù)返回結(jié)果時,整個執(zhí)行棧就空了。此時,如果任務(wù)隊(duì)列中存在異步任務(wù),則主線程會讀取任務(wù)隊(duì)列中的任務(wù)。待會介紹任務(wù)隊(duì)列。

任務(wù)隊(duì)列

單線程就意味著,所有任務(wù)(線程)需要排隊(duì),前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。如果前一個任務(wù)耗時很長,后一個任務(wù)不得不一直等待。

因此,所有任務(wù)可以分為兩種,一種是同步任務(wù),一種是異步任務(wù)。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,后一個任務(wù)才會執(zhí)行;異步任務(wù)指的是不進(jìn)入主線程、而進(jìn)入任務(wù)隊(duì)列的任務(wù),只有當(dāng)主線程上的所有同步任務(wù)執(zhí)行完畢之后,主線程才會讀取任務(wù)隊(duì)列,開始執(zhí)行異步任務(wù)。

任務(wù)隊(duì)列是一個事件的隊(duì)列(也可以理解成消息的隊(duì)列),IO設(shè)備完成一項(xiàng)任務(wù),就在"任務(wù)隊(duì)列"中添加一個事件,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了。主線程讀取"任務(wù)隊(duì)列",就是讀取里面有哪些事件。

“任務(wù)隊(duì)列”中的事件,除了IO設(shè)備(ajax獲取服務(wù)器數(shù)據(jù))的事件以外,還包括一些用戶產(chǎn)生的事件(mousehover、click、scroll、keyup等)和定時器等。只要在事件中指定了回調(diào)函數(shù),這些事件發(fā)生時就會進(jìn)入“任務(wù)隊(duì)列”,等待主線程讀取。而主線程讀取任務(wù)隊(duì)列中的異步任務(wù),主要就是讀取回調(diào)函數(shù)。

當(dāng)主線程的所有同步任務(wù)執(zhí)行(排隊(duì)執(zhí)行)完畢之后,就會讀取任務(wù)隊(duì)列中的異步任務(wù),將異步任務(wù)推入執(zhí)行棧中執(zhí)行。任務(wù)隊(duì)列是一個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),即排在前面的事件,優(yōu)先被主線程讀取。如果存在定時器,時間越短的越先進(jìn)入執(zhí)行棧。

因此,可以做一個簡單總結(jié):

  • 當(dāng)主線程開始執(zhí)行同步任務(wù)時,會創(chuàng)建一個"執(zhí)行棧",每一個同步任務(wù)排隊(duì)執(zhí)行,只有前一個任務(wù)執(zhí)行完畢,才會執(zhí)行下一個任務(wù)。同時,執(zhí)行棧與汗水的調(diào)用位置有關(guān)。
  • 當(dāng)主線程上的所有同步任務(wù)執(zhí)行完畢之后,主線程會讀取任務(wù)隊(duì)列上的異步任務(wù),并將異步任務(wù)推入執(zhí)行棧中開始執(zhí)行。
  • 主線程不斷重復(fù)以上2個步驟。

setTimeout

明白了主線程執(zhí)行相關(guān)任務(wù)的思路后,來看看定時器。上面介紹到,定時器是屬于任務(wù)隊(duì)列中的異步任務(wù)。因此會等待“執(zhí)行?!鄙系乃型饺蝿?wù)執(zhí)行完畢之后,主線程計(jì)算定時器的執(zhí)行時間,再將事件推入“執(zhí)行?!薄?匆粋€簡單的例子。

function foo() {
    setTimeout(function() {
        console.log(1);
    }, 0)
    console.log(2);
}
function bar() {
    setTimeout(function() {
        console.log(3);
    }, 0);
    console.log(4);
}
foo();
bar();

這段函數(shù)的輸出結(jié)果為2, 4, 1, 3。做一個簡單的分析。

foo、bar函數(shù)的內(nèi)部有相同的結(jié)構(gòu),都有一個定時器和console.log()函數(shù)。當(dāng)foo、bar函數(shù)調(diào)用時,會形成一個“執(zhí)行?!?,主線程會先執(zhí)行“執(zhí)行?!敝械耐饺蝿?wù),即console.log(2), console.log(4),而兩個定時器會被推入任務(wù)隊(duì)列中,等待執(zhí)行。當(dāng)主線程上的同步任務(wù)執(zhí)行完畢之后,結(jié)束定時器的等待,將任務(wù)隊(duì)列中的兩個異步任務(wù)推入“執(zhí)行棧”中執(zhí)行,因此輸出的順序?yàn)?, 4, 1, 3。

定時器的第一個參數(shù)是一個函數(shù),第二個參數(shù)是推遲執(zhí)行的毫秒數(shù)。從函數(shù)的定義上看,如果將時間設(shè)定為0,此時應(yīng)該是立即執(zhí)行定時器才對,為什么輸出順序會不同呢?

需要注意的是,setTimeout()只是將回調(diào)函數(shù)插入到“任務(wù)隊(duì)列”中,因此必須等到主線程上的同步任務(wù)全部執(zhí)行完畢,主線程才會執(zhí)行任務(wù)隊(duì)列中的異步任務(wù),并且,setTimeout會等到同步任務(wù)執(zhí)行完畢之后,再等到任務(wù)隊(duì)列中的異步任務(wù)執(zhí)行完畢之后才開始執(zhí)行。setTimeout的第二個參數(shù)只能確保任務(wù)在指定的時間之后執(zhí)行,而不能保證一定就在該時間之后立即執(zhí)行,是否能夠立即執(zhí)行,取決于“執(zhí)行?!敝械娜蝿?wù)數(shù)量。

function foo() {
    setTimeout(function() {
        console.log(1);
    }, 2000)
    console.log(2);
}

function bar() {
    setTimeout(function() {
        console.log(3);
    }, 1000);
    console.log(4);
}

function baz() {
    setTimeout(function() {
        console.log(5);
    }, 0)
    console.log(6);
}
foo();
bar();
baz();
//結(jié)果: 2, 4, 6, 5, 3, 1;

主線程上的同步任務(wù)按照執(zhí)行棧排隊(duì)執(zhí)行,任務(wù)隊(duì)列上的定時器按照時間長短排隊(duì)執(zhí)行。時間越短,越早進(jìn)入“執(zhí)行?!?,越早被主線程執(zhí)行。也就是說,先進(jìn)入任務(wù)隊(duì)列的任務(wù)先執(zhí)行。

如果換一種函數(shù)的調(diào)用位置
baz();
foo();
bar();
//此時的結(jié)果: 6, 2, 4, 5, 3, 1

從上面的兩種運(yùn)行結(jié)果可以看出,

同步任務(wù)取決于函數(shù)的調(diào)用位置,不同的調(diào)用位置,進(jìn)入執(zhí)行棧的位置就不同,主線程執(zhí)行的順序就不同

異步任務(wù)的執(zhí)行與函數(shù)的調(diào)用位置無關(guān),只取決于執(zhí)行棧的任務(wù)數(shù)量,當(dāng)同步任務(wù)執(zhí)行完畢之后,才會開始執(zhí)行異步任務(wù),并且遵循先進(jìn)入任務(wù)隊(duì)列的事件先執(zhí)行的原則。
參考文獻(xiàn):http://www.cnblogs.com/Uncle-Keith/p/6436047.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,917評論 1 17
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,926評論 0 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • 蓬萊仙山長島行, 旖旎飄渺攝人心。最是黃渤交接處,欲化大鵟嘯天清。 三十號晚七點(diǎn)在牡丹園上的大巴車,沒有避開如浪的...
  • 文/小馬乖乖乖 01 和他在一起已經(jīng)六年了。 我們相識于網(wǎng)絡(luò),約見在嚴(yán)肅而又莊重的人民大禮堂前的一顆大樹下。 他,...
    小馬乖乖乖閱讀 9,165評論 6 15

友情鏈接更多精彩內(nèi)容