JS的運(yùn)行機(jī)制
HTML頁面中JS的加載原理: 在加載HTML頁面的時候,當(dāng)瀏覽器遇到內(nèi)嵌的JS代碼時會停止處理頁面,先執(zhí)行JS代碼,然后再繼續(xù)解析和渲染頁面。
代碼塊: JS中的代碼塊是指由<script>標(biāo)簽分割的代碼段。JS是按照代碼塊來進(jìn)行編譯和執(zhí)行的,代碼塊間相互獨立(即就算代碼塊1出錯,但不影響代碼塊2的加載和執(zhí)行),但變量和方法共享。
案例:2個代碼塊
- <script type="text/javascript">
- console.log("這是代碼塊一");
- </script>
- <script type="text/javascript">
- console.log ("這是代碼塊二");
- </script>
HTML頁面中JS的加載原理: 在加載HTML頁面的時候,當(dāng)瀏覽器遇到內(nèi)嵌的JS代碼時會停止處理頁面,先執(zhí)行JS代碼,然后再繼續(xù)解析和渲染頁面。同樣的情況也發(fā)生在外鏈的JS文件中,瀏覽器必須先花時間下載外鏈文件中的代碼,然后解析并執(zhí)行它,在這個過程中,頁面的渲染和用戶互交完全被阻塞。由于現(xiàn)代瀏覽器都允許并行下載JS文件,因此<script>標(biāo)簽在下載外部資源時不會阻塞其他的<script>標(biāo)簽。遺憾的是JS下載過程仍然會阻塞其他資源的下載。
JavaScript的單線程:
JS語言的一大特點就是單線程,也就是說,同一個時間只能做一件事情。之所以是單線程,是因為與它的用途有關(guān),作為瀏覽器腳本語言,JS的主要用途是與用戶互動以及操作DOM。這決定了它只能是單線程,否則會帶來復(fù)雜的同步問題。為了利用多核CPU的計算功能,HTML5提出了web worker標(biāo)準(zhǔn),允許JS腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不能操作DOM,所以這個新標(biāo)準(zhǔn)并沒有改變JS單線程的本質(zhì)。
JavaScript的任務(wù)列隊:
JS任務(wù)可以分為兩種:一種是同步任務(wù),另一種是異步任務(wù)。注意,只有主線程空了,才會去讀取"任務(wù)隊列",這就是JS的運(yùn)行機(jī)制,這個過程會不斷重復(fù)。
同步任務(wù):在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢了,才會執(zhí)行后一個任務(wù)。
異步任務(wù):在主線程之外,還存在一個“任務(wù)列隊”,異步任務(wù)就是不進(jìn)入主線程,而是進(jìn)入“任務(wù)列隊”的任務(wù),只有“任務(wù)列隊”通知主線程,某個異步任務(wù)可以執(zhí)行了并且同步任務(wù)執(zhí)行完畢,該任務(wù)才會進(jìn)入主線程執(zhí)行。
Javascript的事件和回調(diào)函數(shù):
"任務(wù)列隊"是一個事件的列隊,IO設(shè)備完成一項任務(wù),就在"任務(wù)隊列"中添加一個事件,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了。主線程讀取"任務(wù)隊列",就是讀取里面有哪些事件。"任務(wù)隊列"中的事件,除了IO設(shè)備的事件以外,還包括一些用戶產(chǎn)生的事件(如鼠標(biāo)點擊、頁面滾動等等)。只要指定過回調(diào)函數(shù),這些事件發(fā)生時就會進(jìn)入"任務(wù)隊列",等待主線程讀取。所謂"回調(diào)函數(shù)",就是那些會被主線程掛起的代碼。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對應(yīng)的回調(diào)函數(shù)。"任務(wù)隊列"是個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過程基本上是自動的,只要執(zhí)行棧一清空,"任務(wù)隊列"上***位的事件就自動進(jìn)入主線程。但是,由于存在后文提到的"定時器"功能,主線程首先檢查一下執(zhí)行時間,某些事件只有到了規(guī)定的時間,才能返回主線程。
定時器:
除了放置異步任務(wù)的事件,"任務(wù)隊列"還可以放置定時事件,即指定某些代碼在多少時間之后執(zhí)行。定時器功能主要由setTimeout()和setInterval()這兩個函數(shù)來完成,它們的內(nèi)部運(yùn)行機(jī)制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行,后者則為反復(fù)執(zhí)行。以下主要討論setTimeou()方法:
setTimeout()接受兩個參數(shù),***個是回調(diào)函數(shù),第二個是推遲執(zhí)行的毫秒數(shù)
- console.log(1)
- setTimeout(function (){
- console.log(2)
- }, 1000);
- console.log(3)
上面代碼的執(zhí)行結(jié)果是1=>3=>2,因為setTimeout()將第二行推遲到1000毫秒之后執(zhí)行
如果將setTimeout()的第二個參數(shù)設(shè)為0,就表示當(dāng)前代碼執(zhí)行完(執(zhí)行棧清空)以后立即執(zhí)行
- setTimeout(function (){
- console.log(2)
- }, 0);
- console.log(3)
上面代碼的執(zhí)行結(jié)果是3=>2,因為只有在執(zhí)行完第二行以后,系統(tǒng)才會去執(zhí)行"任務(wù)隊列"中的回調(diào)函數(shù)??傊?,setTimeout(fn, 0)的含義是,指定某個任務(wù)在主線程最早的空閑時間執(zhí)行,也就是說盡可能早的執(zhí)行。它在"任務(wù)隊列"的尾部添加一個事件,因此要等到同步任務(wù)和"任務(wù)隊列"現(xiàn)有的事情都處理完,才會得到執(zhí)行。
需要注意的是,setTimeout()知識將事件插入了"任務(wù)隊列",必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會去執(zhí)行它指定的回調(diào)函數(shù),要是當(dāng)前代碼耗時很長,有可能要等很久,所以并沒有辦法保證回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行。
責(zé)任編輯:李英杰
來源:
博客園



























