異步、單線程、非阻塞、回調

異步

先看一下下面對async、defer的介紹
下面的內容是文章《異步vs延遲屬性》里的內容

<script>

我們首先定義什么<script>沒有任何屬性。HTML文件將被解析,直到腳本文件被命中,在這一點解析將停止,并將請求獲取文件(如果它是外部的)。然后在執行解析之前執行該腳本。

image.png
<script async>

async 在HTML解析過程中下載文件,并在完成下載后暫停HTML解析器執行。

image.png
<script defer>

defer在HTML解析過程中下載文件,只有在解析器完成后才能執行該文件。defer腳本也可以按照它們在文檔中出現的順序執行。

image.png
什么時候應該用什么?

通常你想async在可能的地方使用,defer然后沒有屬性。以下是一些一般規則:

  • 如果腳本是模塊化的,并且不依賴于任何腳本然后使用async。
  • 如果腳本依賴或被另一個腳本依賴,那么使用defer。
  • 如果腳本很小并被腳本依賴,async那么請使用內置的腳本之上的script任何屬性。
async支持

IE9及以下版本的執行有一些非常糟糕的錯誤,defer以致執行順序不符合要求。如果您需要支持<= IE9,我建議不要使用defer,如果執行順序很重要,請將腳本包含在沒有屬性的位置。

單線程

因為js運行在瀏覽器中,是單線程的,每個window一個JS線程
既然是單線程的,在某個特定的時刻只有特定的代碼能夠被執行。
而瀏覽器是事件驅動的(Event driven),瀏覽器中很多行為是異步(Asynchronized)的,會創建事件并放入執行隊列中。
javascript引擎是單線程處理它的任務隊列,你可以理解成就是普通函數和回調函數構成的隊列。
當異步事件發生時,如鼠標點擊事件發生、定時器觸發事件發生、XMLHttpRequest完成回調觸發等,將他們放入執行隊列,等待當前代碼執行完成。

異步事件驅動

前面已經提到瀏覽器是事件驅動的(Event driven),瀏覽器中很多行為是異步(Asynchronized)的,
例如:鼠標點擊事件、窗口大小拖拉事件、定時器觸發事件XMLHttpRequest完成回調等。
當一個異步事件發生的時候,它就進入事件隊列。瀏覽器有一個內部大消息循環,Event Loop(事件循環),會輪詢大的事件隊列并處理事件。
例如,瀏覽器當前正在忙于處理onclick事件,這時另外一個事件發生了(如:window onSize),這個異步事件就被放入事件隊列等待處理,只有前面的處理完畢了,空閑了才會執行這個事件。
setTimeout也是一樣,當調用的時候,js引擎會啟動定時器timer,大約xxms以后執行xxx,
當定時器時間到,就把該事件放到主事件隊列等待處理(瀏覽器不忙的時候才會真正執行)

異步示例——事件
var items
var i
document.body.innerHTML = `
    <ol>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ol>         
` 
items = document.querySelectorAll('li')
for(i=0;i<items.length;i++){
    items[i].onclick = function(){
        console.log(i)
    }
}
image.png

函數解析后,不會馬上執行
等鼠標點擊有序列表項,
才會執行,
無論是哪一項都會在控制臺打出‘5’。
因為綁定事件的處理函數,
等輸入流給出事件觸發信號,
才會執行函數。
但是,
其它的代碼會繼續解析并執行。
所以,
變量i的值繼續增在,增加到5,
這時候事件還沒觸發,所以函數也不會執行,
等它執行時,
獲取的變量i已經是‘5’了

異步示例——定時器
var a = 1
setTimeout(function(){
    var a = 2
    console.log(a)
},1000)
console.log(a)
1
undefined
//一秒之后才打出‘2’
2

上面,先打出1
說明,全局環境里console.log(a)這一行代碼會先執行,
然后再打出‘2’,
說明,setTimeout()里的console.log(a)
雖然先被瀏覽器解析,
但是要等1秒之后,IO輸入流才給它一個信號說一秒時間到了,
才會執行了

異步示例——請求

data.json

{"name":"llz"}

javascript

var a = 1
var xhr = new XMLHttpRequest()
xhr.open('GET','/data.json')
xhr.onload = function(){
console.log(xhr.responseText)
}
xhr.send()
console.log(1)

console

1
{"name":"llz"}

控制臺,會先打出1,
等請求的數據下載完后,
才會打出請求的數據‘{"name":"llz"}’
說明請求也是異步

非阻塞js的實現(non-blocking javascript)

js在瀏覽器中需要被下載、解釋并執行這三步。在html body標簽中的script都是阻塞的。
也就是說,順序下載、解釋、執行。盡管Chrome可以實現多線程并行下載外部資源,
例如:script file、image、frame等(css比較復雜,在IE中不阻塞下載,但Firefox阻塞下載)。
但是,由于js是單線程的,所以盡管瀏覽器可以并發加快js的下載,但必須依次執行。
所以chrome中image圖片資源是可以并發下載的,但外部js文件并發下載沒有多大意義。

回調

回調 == 異步 + 函數調用

回調示例——定時器
function asyncFn(fn){
    setTimeout(function(){
        fn(Math.random())
    },(2*Math.random()*3)*1000)
}
asyncFn(function(xxx){
    console.log('xxx is ')
    console.log(xxx)
})

等了將近5秒鐘,才打出下面的代碼

xxx is 
0.5311492544878877

我們可以看出,setTimeout造成了異步,
等5秒后,作為函數asyncFn參數的函數才調用

回調示例——請求

案例我放在github上
回調函數實例源碼
案例里的原理是,
在javascript里聲明一個函數callbakFn
然后發出一個請求,將函數名作為參數傳進去xxx.js?callback=callback,
同時請求xxx.js文件,
在服務器里文件server.js將函數名callbakFn替換xxx.js文件里字符串{{callback}}
xxx.js文件,就變成

callbackFn('回調成功')

然后瀏覽器下載xxx.js文件,解析指向上面的代碼,
就會調用callbackFn函數,
這就是請求里的回調。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容