js 單線程
??相信大家玩 js 很久了,Js語言的執(zhí)行環(huán)境是 “ 單線程 ”。什么是單 線程,就是當(dāng)一個任務(wù)完成之后才會有下一個任務(wù) 。 舉個栗子,你在奶茶店買奶茶 ,只有一個 奶茶妹妹,客人們都在排隊,問你喝什么,你告訴她,她下好單再叫下一個,以此類推,這就是 單線程的同步。再比如到你了,你沒選好喝什么,她先問下一位喝什么,等你想好了,再問你喝什么,這就是 單線程的異步。多個奶茶妹妹同事問客人 ,就是多線程。
js 為什么是單線程的。
??為什么 JavaScript 不能有多個 線程 呢?這樣能提高效率啊。辦事快啊, 我能撩到好幾個奶茶妹妹,但這個和 js 的作用有關(guān) ,當(dāng)初 js 只是為了服務(wù)于 游覽器 ,用戶交互,所以設(shè)計了單 線程 , 如果多 線程 你想有多么麻煩,你操作了 dom 是修改 ,同時另外的線程執(zhí)行了 刪除 dom 這時候游覽器怎么選擇呢?為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主 線程 控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)。所以我們?nèi)稳?堅持說 js 是單 線程 的。
異步同步
??既然 js 是 單線程 的,那么按照我們的之前的栗子, 你去買奶茶,你沒想好要喝什么,一直占著位置,估計后面的兄弟們對你肯定不爽了,開始罵娘了 ,所以 這時候我們的 奶茶妹 就很機智,然你在后面等等,先問下一個要喝什么。于是,所有任務(wù)可以分成兩種。
同步任務(wù)(synchronous)
在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);
執(zhí)行過程大概是這樣:
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。
(2)一旦"執(zhí)行棧"中的當(dāng)前任務(wù)執(zhí)行完畢,就會執(zhí)行下一個任務(wù),進入執(zhí)行棧,開始執(zhí)行。
(3)主線程不斷重復(fù)上面的(2)
異步任務(wù)(asynchronous)
??不進入主線程、而進入”任務(wù)隊列”(task queue)的任務(wù),只有”任務(wù)隊列”通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進入主線程執(zhí)行。
執(zhí)行過程大概是這樣:
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。
(2)主線程之外,還存在一個"任務(wù)隊列"(task queue)。只要異步任務(wù)有了運行結(jié)果,就在"任務(wù)隊列"之中放置一個事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列",看看里面有哪些事件。開始執(zhí)行。
(4)主線程不斷重復(fù)上面的(3)
瀏覽器不是單線程的
雖然JS運行在瀏覽器中,是單線程的,每個window一個JS線程,但瀏覽器不是單線程的,可能有如下線程:
javascript引擎線程
這個就是我們常說的單線程
界面渲染線程
渲染dom頁面的進程
瀏覽器事件觸發(fā)線程
有可能觸發(fā)多個事件。例如 keyup ,clickup 等。
Http請求線程
你可以發(fā)起多個請求,至于最多能發(fā)送多少請求,我們后面會提到。
如果js是單線程的,那么誰去輪詢大的Event loop事件隊列?答案是瀏覽器會有單獨的線程去處理這個隊列。
js 的 異步編程
??異步編程有這么幾種方式:
ES 6以前:
- 回調(diào)函數(shù)
- 事件監(jiān)聽(事件發(fā)布/訂閱)
ES 6:
- Generator函數(shù)(協(xié)程coroutine)
- Promise對象
ES 7:
- async和await
* 回調(diào)函數(shù)
//一個定時器
function timer(time, callback){
setTimeout(function(){
callback();
}, time);
};
timer(3000, function(){
console.log(123);
})
?? 這個最常見,也是我們經(jīng)常寫的。非常簡單。一個 setTimeout 然后執(zhí)行后面的函數(shù)。至于 setTimeout 的話 ,我們后面再講。
* 事件監(jiān)聽(發(fā)布/訂閱)
??發(fā)布—訂閱模式可以廣泛應(yīng)用于異步編程中,這是一種替代傳遞回調(diào)函數(shù)的方案。比如,我們可以訂閱 ajax 請求的 error 、 succ 等事件。 或者如果想在動畫的每一幀完成之后做一些事情,那我們可以訂閱一個事件,然后在動畫的每一幀完成之后發(fā)布這個事件。在異步編程中使用發(fā)布—訂閱模式,我們就無需過多關(guān)注對象在異步運行期間的內(nèi)部狀態(tài),而只需要訂閱感興趣的事件發(fā)生點。
??這里不詳細說了,因為后面我們會把這種設(shè)計模式單獨拿出來講。這里舉例一個事件監(jiān)聽,其實是一樣的:
element.addEventListener("click",function(){
alert("clicked");
})
* Generator函數(shù)
這個函數(shù)我用的很少,所以我打算后面專門也來研究一下這個東西。
形式上,Generator 函數(shù)是一個普通函數(shù),但是有兩個特征。
function關(guān)鍵字與函數(shù)名之間有一個星號;
函數(shù)體內(nèi)部使用yield語句,定義不同的內(nèi)部狀態(tài)
我們來大概看看這個東西
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對圓括號。不同的是,調(diào)用Generator函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象,我們可以通過調(diào)用 next 方法,使得指針移向下一個狀態(tài)。
* Promise對象
??這個就比較熟了,之前有提到。不太熟悉的童鞋們可以看這篇文章: 你就需要這篇文章,帶你搞懂實戰(zhàn)的 Promise 對象 ; 后面我們也會將他拿出來,看看我們的 JavaScript 原生如何實現(xiàn) promise 對象。
* async和await
??這個也比較熟悉,之前的博客也提到過 ,了解 JavaScript ES7 的 async / await , ; 后面我們也會將他拿出來,看看我們的 JavaScript 原生如何實現(xiàn) async / await 對象。
結(jié)語
??以上就是我們的異步編程方式,我們后面會慢慢將這些東西拿出來講解 :
- setTimeout
- 發(fā)布訂閱
- 原生(es2015)實現(xiàn) promiss
- Generator
- 原生(es2015)實現(xiàn) async / await