1,什么是單線程偷卧,和異步有什么關(guān)系
單線程-只有一個(gè)線程悄晃,只能做一件事
單線程的原因:避免DOM 渲染的沖突
- 瀏覽器需要渲染DOM
- JS 可以修改DOM 結(jié)構(gòu)
- JS 執(zhí)行的時(shí)候靡菇,瀏覽器DOM 渲染會(huì)暫停
- 兩段JS 也不能同時(shí)執(zhí)行(都修改DOM 就沖突了)
- webworker支持多線程便斥,但是不能訪問(wèn)DOM
怎么解決:用異步,提高性能鹏控。但是異步的代碼會(huì)有很多比較多難以理解的問(wèn)題致扯,比如
- 沒(méi)有按照我們代碼的順序執(zhí)行,可讀性差
- callback中不同意模塊化当辐,易出現(xiàn)回調(diào)地獄抖僵。
我們平常的工作中常見(jiàn)的一步的函數(shù)入 setTimeout(fn,time)
,以及常見(jiàn)的網(wǎng)絡(luò)請(qǐng)求缘揪,如 Ajax
等耍群。
js的執(zhí)行機(jī)制就是先跑完同步的代碼,在輪詢(xún)?nèi)プx異步隊(duì)列里面的函數(shù)找筝。(等等會(huì)講到)
如下圖兩個(gè)例子:
下圖中的例子使用 setTimeout
來(lái)寫(xiě)的蹈垢,所以它是一個(gè)異步函數(shù)。
輸出的結(jié)果是:
100
300
400
200
下圖中的例子是 ajax
的例子呻征,也是一個(gè)異步函數(shù)耘婚,js執(zhí)行的時(shí)候,會(huì)把 Ajax
放在異步隊(duì)列中陆赋,等待同步代碼全都執(zhí)行完了再去輪詢(xún)代碼。
接下去我們講講事件輪詢(xún) (event-loop
)
2嚷闭,什么是 event-loop
event-loop
是 js
異步的一種實(shí)現(xiàn)方式攒岛。
他的意思簡(jiǎn)單來(lái)說(shuō)就是:
- 同步代碼,直接執(zhí)行
- 異步函數(shù)先放在異步隊(duì)列中
- 待同步函數(shù)執(zhí)行完畢胞锰,輪詢(xún)執(zhí)行異步隊(duì)列的函數(shù)
我們可以具體看幾個(gè)例子:
例子一
就是一個(gè)setTimeout
函數(shù)灾锯,200s
以后輸出打印100。我們可以看到的是嗅榕,console.log(200)
是同步代碼顺饮,而setTimeout
則是一個(gè)異步函數(shù)吵聪。
setTimeout(function () {
console.log(100)
})
console.log(200)
那么我么就可以得到下圖。
所以輸出結(jié)果為:
200,
100
例子二
這里我們有兩個(gè) setTimeout
兼雄,但是一個(gè)時(shí)間是0ms吟逝,另外一個(gè)是100ms。所以我們能得到圖二赦肋,主進(jìn)程中是 console.log(3)
块攒,而在異步隊(duì)列中的兩個(gè) setTimeout
, 0秒的立刻就被放入我們的進(jìn)程中,還有一個(gè)是隔100ms被放入異步隊(duì)列佃乘。
setTimeout(function () {
console.log(1)
}, 1000)
setTimeout(function () {
console.log(2)
})
console.log(3)
所以上圖的程序的結(jié)果應(yīng)該是:
3,
2,
1
例子三
我們來(lái)看一個(gè)稍微復(fù)雜的函數(shù)囱井,setTimeout
和 ajax
都存在的函數(shù)。這里的結(jié)果我們可以思考一下趣避。
$.ajax({
url: './data.json',
success: function () {
console.log('a')
}
})
setTimeout(function () {
console.log('b')
}, 1000)
setTimeout(function () {
console.log('c')
})
console.log('d')
看下下面的圖庞呕,應(yīng)該可以馬上明白了。
這里應(yīng)該有兩種輸出結(jié)果程帕,當(dāng)我們 ajax
返回的速度快于100ms的時(shí)候住练,那么就會(huì)先輸出a
,如果 ajax
速度很慢的時(shí)候骆捧,慢于100ms澎羞,那么我們就會(huì)先輸出 b
,在輸出 a
敛苇。
3妆绞,是否用過(guò) jquery 的 Deferred
簡(jiǎn)單的來(lái)講,Deferred
就是 promise
的前世枫攀。
在開(kāi)發(fā)中括饶,我們經(jīng)常遇到某些耗時(shí)很長(zhǎng)的javascript操作。其中来涨,既有異步的操作(比如ajax讀取服務(wù)器數(shù)據(jù))图焰,也有同步的操作(比如遍歷一個(gè)大型數(shù)組),它們都不是立即能得到結(jié)果的蹦掐。
通常的做法是技羔,為它們指定回調(diào)函數(shù)(callback)。即事先規(guī)定卧抗,一旦它們運(yùn)行結(jié)束藤滥,應(yīng)該調(diào)用哪些函數(shù)。但是社裆,一旦回調(diào)層級(jí)過(guò)深拙绊,處理和維護(hù)會(huì)變得相當(dāng)困難。即我們常說(shuō)的回調(diào)地獄。
在 JQuery
中,低于 1.5.0 版本标沪,$.ajax()
返回的是XHR
對(duì)象榄攀,所以不能進(jìn)行鏈?zhǔn)讲僮鳎缦拢?/p>
$.ajax({
url: "http://localhost:8888",
success: function(){
console.log("哈哈金句,成功了檩赢!");
},
error: function(){
console.log("出錯(cuò)啦!");
}
});
但是在版本 1.5.0 之后趴梢,$.ajax()
返回的是 deferred
對(duì)象漠畜,可以進(jìn)行鏈?zhǔn)讲僮髁恕H缦拢?/p>
$.ajax("http://localhost:8888")
.done(function(){
console.log("哈哈坞靶,成功了憔狞!");
})
.fail(function(){
console.log("出錯(cuò)啦!");
});
看了這段代碼彰阴,你是不是想起了之前我們使用過(guò)的 promise
瘾敢。
其實(shí)我們無(wú)法改變 JS 異步和單線程的本質(zhì),所以為了解決回調(diào)地獄尿这,我們只能從寫(xiě)法上杜絕callback 這種形式簇抵。deferred
它是一種語(yǔ)法糖形式,但是他是我們的代碼變的更清晰射众。也很好的體現(xiàn)了碟摆,編程中的一個(gè)原則——開(kāi)放封閉原則。
即對(duì)擴(kuò)展開(kāi)放叨橱,對(duì)修改封閉的原則典蜕。我們不需要把所有的代碼都寫(xiě)在success
回調(diào)中了,可以把代碼很好的解耦出來(lái)罗洗,便于更好的維護(hù)和測(cè)試愉舔。
下面我們來(lái)看看一個(gè)對(duì)于 deferred
的簡(jiǎn)單封裝:
// 已經(jīng)封裝好的(A 員工)
function waitHandle() {
// 定義
var dtd = $.Deferred()
var wait = function (dtd) {
var task = function () {
console.log('執(zhí)行完成')
dtd.resolve() // 成功
// dtd.reject() // 失敗
}
setTimeout(task, 1000)
return dtd.promise() // wait 返回
}
return wait(dtd) // 最終返回
}
// 使用(B 員工)
var w = waitHandle() // promise 對(duì)象
$.when(w).then(function () {
console.log('ok 1')
}, function () {
console.log('err 1')
})
上述示例中,結(jié)果會(huì)輸出
執(zhí)行完成
ok1 // 1s后
這里我們講幾個(gè)要點(diǎn)
deferred.resolve()
和deferred.reject()
伙菜。說(shuō)明其作用需要先說(shuō)一下jQuery
規(guī)定deferred
對(duì)象的三種執(zhí)行狀態(tài):未完成轩缤、已完成和已失敗。$.when(deferreds)
方法只能接收defferred對(duì)象作為參數(shù)贩绕,所以我們要給上述函數(shù)返回一個(gè)deferred
對(duì)象-
我們
wait
函數(shù)返回的是dtd.promise()
火的,而不是簡(jiǎn)單的dtd
對(duì)象,是因?yàn)?dtd
的API
可分成兩類(lèi)淑倾,用意不同卫玖。一類(lèi)是判斷是否成功這個(gè)狀態(tài), 另一個(gè)則是判斷成功或者失敗后踊淳,該做什么事情。這兩個(gè)應(yīng)該分開(kāi)。 總結(jié)迂尝,dtd 的 API 可分成兩類(lèi)脱茉,否則后果很?chē)?yán)重!我們可以在wait
函數(shù)返回一個(gè)dfd
垄开。 然后在最末尾加上一句w.reject()
琴许。輸出結(jié)果為:err 1 執(zhí)行完成
這個(gè)是我們需要非常注意的。因?yàn)榉祷?
dtd.promise()
之后溉躲,我們就訪問(wèn)不到reject
與resolve
這兩個(gè)接口了榜田。下面這張的
dfd
的api
。
這張的dfd.promise
的 api
锻梳。
相關(guān)鏈接:jQuery的deferred對(duì)象詳解箭券,面向?qū)ο缶幊痰?大原則,
4疑枯,Promise 的基本使用和原理
4.1 基本語(yǔ)法回顧
先看個(gè)簡(jiǎn)單的例子:
function loadImg(src) {
var promise = new Promise(function (resolve, reject) {
var img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('圖片加載失敗')
}
img.src = src
})
return promise
}
var src = 'http://static.clewm.net/cli/images/cli_logo@2x.png'
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img
}, function () {
console.log('error 1')
}).then(function (img) {
console.log(2, img.height)
})
上述的代碼辩块,輸出的結(jié)果是:
1 300
2 54
代碼很簡(jiǎn)單,就是加載一張圖片荆永,當(dāng)圖片加載成功的時(shí)候顯示圖片的寬度與寬度废亭,我們首先 new
一個(gè) promise
對(duì)象,然后針對(duì)圖片加載成功或者失敗具钥,做一些操作豆村。 都是一些常規(guī)的操作。
4.2 異常捕獲
我們改造一下代碼骂删,loadimg
函數(shù)不變掌动,改變下面調(diào)用的方式:
var src = 'http://static.clewm.net/cli/images/cli_logo@2x.png'
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img
}).then(function (img) {
console.log(2, img.height)
}).catch(function (ex) {
// 統(tǒng)一捕獲異常
console.log(ex)
})
這里我們規(guī)定:then
只接受一個(gè)參數(shù),最后統(tǒng)一用 catch
捕獲異常桃漾。所以我們then只接受一個(gè)成功之后的回調(diào)坏匪。
4.3 多個(gè) promise
對(duì)象、Promise.all
和 Promise.race
這里我們?cè)谠黾右粋€(gè)圖片的請(qǐng)求撬统。
var src1 = 'http://static.clewm.net/cli/images/cli_logo@2x.png'
var result1 = loadImg(src1)
var src2 = 'http://static.clewm.net/static/images/1404477720_63c377a.png?v=20150518'
var result2 = loadImg(src2)
result1.then(function (img1) {
console.log('第一個(gè)圖片加載完成', img1.width)
return result2 // 重要J首摇!恋追!
}).then(function (img2) {
console.log('第二個(gè)圖片加載完成', img2.width)
}).catch(function (ex) {
console.log(ex)
})
如果是多個(gè)串聯(lián)凭迹,比如我們當(dāng)一個(gè) promise
返回成功之后,我們?nèi)シ祷亓硗庖粡垐D片的信息苦囱,我們可以如上代碼一樣嗅绸,返回 return result2
。這樣我們下一個(gè) then
得到的就是圖片2的信息撕彤。
Promise.all
與 Promise.race
也很好理解鱼鸠,他們都接受一個(gè)包含多個(gè)promise
對(duì)象的數(shù)組猛拴,前者是當(dāng)所有請(qǐng)求都完成時(shí),統(tǒng)一去執(zhí)行 then
操作蚀狰,而后者則是有一個(gè)請(qǐng)求完成就去執(zhí)行 then
操作愉昆。
如下圖:
4.4 Promise 標(biāo)準(zhǔn)
這里就簡(jiǎn)單的提兩點(diǎn):
- 一個(gè)是
promise
對(duì)象的改變是不可逆的,就只有是pending
到成功或者失敗麻蹋,而不能反著來(lái)跛溉,同時(shí)狀態(tài)也不能從成功變?yōu)槭 ?/li> - 還有一個(gè)就是
promise
必須要有一個(gè)then
方法。而且他必須接受兩個(gè)參數(shù)作為參數(shù)扮授,返回的也必須是一個(gè)promise
實(shí)例芳室。
相關(guān)鏈接:Promise 迷你書(shū),
5刹勃, 介紹一下 async/await(和 Promise 的區(qū)別堪侯、聯(lián)系)
async/await
是 es7
的語(yǔ)法。他不是替代 primise
的一個(gè)異步解決方案深夯,而是使用了 Promise
抖格,并沒(méi)有和 promise
沖突。
我們使用 promise
的時(shí)候咕晋,雖然看似是避免了回調(diào)地獄雹拄,但是 then
方法其實(shí)只是講 callback
拆分了而已。
而 async/await
他的出現(xiàn)則是最直接的同步寫(xiě)法掌呜。
loadImg
方法不變滓玖,我們使用 async/await
來(lái)完成代碼:
const load = async function() {
const result1 = await loadImg(src1)
console.log(result1);
const result2 = await loadImg(src2)
console.log(result2);
}
load();
因?yàn)?code>async/await 是屬于 es7
范疇的,我們需要使用 babel-polyfill
质蕉,我們可以使用 cdn
(babel-polyfill)势篡。
這樣我們也能得到和 promise
一樣的結(jié)果。
6模暗,總結(jié)
今天講的一些知識(shí)都是慕課網(wǎng)實(shí)戰(zhàn)視頻里面 js高級(jí)面試題
里面的講到的異步這一節(jié)內(nèi)容禁悠,同時(shí)加上了自己的一些思考,感覺(jué)對(duì)于 js
異步的知識(shí)點(diǎn)有一種全新的認(rèn)識(shí)兑宇,豁然開(kāi)朗碍侦。
希望這篇文章對(duì)大家學(xué)習(xí)小程序能有幫助,來(lái)自一個(gè)奔跑在前端路上的前端小白隶糕。