異步
先看一下下面對async、defer的介紹
下面的內容是文章《異步vs延遲屬性》里的內容
<script>
我們首先定義什么<script>沒有任何屬性。HTML文件將被解析,直到腳本文件被命中,在這一點解析將停止,并將請求獲取文件(如果它是外部的)。然后在執行解析之前執行該腳本。
<script async>
async 在HTML解析過程中下載文件,并在完成下載后暫停HTML解析器執行。
<script defer>
defer在HTML解析過程中下載文件,只有在解析器完成后才能執行該文件。defer腳本也可以按照它們在文檔中出現的順序執行。
什么時候應該用什么?
通常你想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)
}
}
函數解析后,不會馬上執行
等鼠標點擊有序列表項,
才會執行,
無論是哪一項都會在控制臺打出‘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
函數,
這就是請求里的回調。