JS中的單線程指的是在同一個時間內(nèi)只能做一件事情溜徙。
好處:避免DOM渲染的沖突冰木。瀏覽器根據(jù)HTML文件初始化需要渲染DOM,JS也可以修改DOM饮六,為了避免兩種方式對同一個DOM元素的處理發(fā)生沖突羡洁,JS執(zhí)行時玷过,渲染要暫停處理,除此之外筑煮,兩段JS代碼也不能同時執(zhí)行辛蚊。
引入的問題:造成頁面的卡頓。當(dāng)頁面渲染過程中真仲,執(zhí)行JS加載某一段數(shù)據(jù)袋马,需要等到數(shù)據(jù)加載完畢后瀏覽器才能繼續(xù)渲染操作,導(dǎo)致用戶體驗很差秸应。
如何解決單線程處理的缺陷虑凛?——異步
1碑宴、setTimeOut(callback , [time])
直接跳過該段代碼,繼續(xù)執(zhí)行下面的主線程代碼桑谍,time毫秒之后把callback函數(shù)放入到異步隊列中(或者省略掉time宏怔,直接把callback函數(shù)放入到異步隊列)拧烦。當(dāng)主線程的代碼執(zhí)行完畢后沃粗,再在異步隊列中取函數(shù)執(zhí)行墙懂。
2、$.ajax( {url:'xxxxx' 盈罐,success:callback })
當(dāng)遇到ajax代碼需要從其他頁面加載數(shù)據(jù)時,也是跳過這段代碼闪唆,繼續(xù)在主線程中執(zhí)行JS代碼盅粪,當(dāng)url地址的數(shù)據(jù)加載完畢后,把callback函數(shù)放入異步隊列悄蕾,等待主線程執(zhí)行完畢后的回調(diào)票顾。
好處:釋放了加載數(shù)據(jù)這段空期,代碼能夠繼續(xù)執(zhí)行帆调。
引入的問題:異步的操作沒有按照書寫的方式執(zhí)行奠骄,可讀性差。而且番刊,callback中不容易模塊化含鳞,當(dāng)新增需求,只能在callback函數(shù)中補充功能的實現(xiàn)芹务,這就違反了開放封閉原則蝉绷,也不利于測試和維護。
異步的本質(zhì)——實現(xiàn)方式——event-loop
同步代碼在主線程中按次立即執(zhí)行枣抱,遇到異步函數(shù)時則略過熔吗,當(dāng)某些事件被觸發(fā)(比如定時器或者數(shù)據(jù)加載完畢),則把異步函數(shù)放入到異步隊列中佳晶。最后桅狠,當(dāng)同步代碼執(zhí)行完畢,則啟用事件輪詢機制轿秧,如果在隊列中有異步函數(shù)中跌,則取出來放入到主線程中運行,如果是空隊列淤刃,則繼續(xù)輪詢監(jiān)聽異步隊列中是否有新的異步函數(shù)被插入晒他。
如何解決異步引入的問題?——deferred
這里從jQuery1.5版本前后ajax的特性說起逸贾。
jQuery1.5版本前的ajax語法:
var ajax = $.ajax({
? ? ? url:'xxxxxxx'陨仅,
? ? ? success:function(){
? ? ? ? ? xxxxxxxxxxxxxxx
? ? ? },
? ? error:function(){
? ? ? ? ? xxxxxxxxxxxxxxx
? ? ? }? ?
})
//返回的是一個XHR對象
使用這種方式津滞,當(dāng)需要回調(diào)函數(shù)success的代碼時,不得不在success代碼內(nèi)部添加灼伤,嚴(yán)重影響了代碼的安全性触徐。所以,在之后的jQuery1.5版本以后狐赡,對這種行為進行了規(guī)避:
var ajax = $.ajax('url')
ajax.done(function(){xxxxxx}).fail(function(){xxxxxx}).done(function(){......})......
//返回一個deferred對象
或者:
ajax.then(function(){xxx}撞鹉,function(){xxx}).then(function(){xxx},function(){xxx})...
//這邊引入的ajax.then格式就和之后提出的promise標(biāo)準(zhǔn)很相近了
使用ajax鏈?zhǔn)讲僮鲾U展success或者error代碼颖侄。
jQuery1.5的變化鸟雏,從本質(zhì)上來說它無法改變JS異步和單線程的本質(zhì),實際上它是一種語法糖的形式览祖,但是解耦了代碼孝鹊,從寫法上杜絕了callback這種形式,很好地體現(xiàn)了開放封閉原則展蒂。
如何使用jQuery Deferred又活?
如下是一段簡單的異步操作代碼,不利于擴展操作:
var wait = function () {
? ? ? ? ? var task = function () {
? ? ? ? ? ? ? ? ? console.log('執(zhí)行完畢')
? ? ? ? ? }
? ? ? ? ? setTimeOut(task锰悼,2000)
}
wait()
現(xiàn)在新增需求柳骄,需要在執(zhí)行完畢之后進行某些特別復(fù)雜的操作,而且可能分為好幾個步驟箕般,這里就需要引入deferred對象來進行拓展:
function waitHandle () {
? ? ? //創(chuàng)建一個deferred對象
? ? ? ? var dtd = $.Deferred()
? ? ? //要求傳入一個deferred對象
? ? ? ? var wait = function (dtd) {
? ? ? ? ? ? ? ? ? var task = function () {
? ? ? ? ? ? ? ? ? ? ? ? ? console.log('執(zhí)行完畢')
? ? ? ? ? ? ? ? ? ? ? ? //表示異步任務(wù)已經(jīng)完成
? ? ? ? ? ? ? ? ? ? ? ? ? dtd.resolve()
? ? ? ? ? ? ? ? ? ? ? ? ? //表示異步任務(wù)失敗或出錯
? ? ? ? ? ? ? ? ? ? ? ? ? dtd.reject()
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? setTimeOut(task耐薯,2000)
? ? ? ? ? ? ? ? //要求返回deferred對象
? ? ? ? ? ? ? ? return dtd
? ? ? ? }
? ? ? ? //注意:這里一定要有返回值
? ? ? ? return wait(dtd)
}
//返回deferred對象
var w = waitHandle()
//調(diào)用deferred對象的then方法。then方法傳入兩個函數(shù)
w.then(function(){
? ? ? ? ? ? console.log(‘ok 1’)
}隘世,function(){
? ? ? ? ? ? console.log(‘error 1’)
}).then(function(){
? ? ? ? ? ? console.log(‘ok 2’)
}可柿,function(){
? ? ? ? ? ? console.log(‘error 2’)
})
promise對象最初的引入——Deferred.promise()——分離API
如上述代碼所示,dtd的API分為兩大類丙者,它們的用意不同复斥。
第一類是主動觸發(fā)的:dtd.resolve(),dtd.reject()
第二類是被動監(jiān)聽的:dtd.then()械媒,dtd.done()目锭,dtd.fail()
如果在then前面主動觸發(fā)resolve或者reject,后面負(fù)責(zé)監(jiān)聽的API會根據(jù)距離最近的監(jiān)聽結(jié)果來執(zhí)行自己相應(yīng)的代碼纷捞,發(fā)生意想不到的結(jié)果痢虹。
為了把這兩類API分開,引入deferred.promise()對象:
var wait = function (dtd) {
? ? ? ? ? ? ? ? ? var task = function () {
? ? ? ? ? ? ? ? ? ? ? ? ? console.log('執(zhí)行完畢')
? ? ? ? ? ? ? ? ? ? ? ? ? dtd.resolve()
? ? ? ? ? ? ? ? ? ? ? ? ? dtd.reject()
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? setTimeOut(task主儡,2000)
? ? ? ? ? ? ? ? //注意這里返回的是promise奖唯,而不是deferred對象
? ? ? ? ? ? ? ? return dtd.promise(()
? ? ? ? }
? ? ? ? ? ? ? ? ..............
//經(jīng)過上面的改動,w接收的是一個promise對象
var w = waitHandle()
$.when(w)
? .then(function(){
? ? ? ? ? console.log(‘ok 1’)
? })
? .then(function(){
? ? ? ? ? console.log(‘ok 2’)
? })
//此處若執(zhí)行w.reject會報錯糜值,因為promise對象中沒有這個方法
ES6中的Promise
淺談Promise的實現(xiàn):
Promise 對象用于表示一個異步操作的最終狀態(tài)(完成或失敺峤荨)坯墨,以及其返回的值
一些舊版瀏覽器不支持Promise,可以在<script>中引入bluebird
異常捕獲規(guī)定:then()只能接收一個參數(shù)病往,最后統(tǒng)一由catch捕獲異常捣染。
catch不僅能捕獲程序邏輯之外語法的錯誤,還能捕獲業(yè)務(wù)邏輯之內(nèi)錯誤停巷,reject需要傳入?yún)?shù)耍攘,不應(yīng)該為空參數(shù)
result.then(function (img) {
? ? ? ? alert(img.width)
}).then(function(img) {
? ? ? ? alert(img.height)
}).catch(function(ex) {
? ? ? ? alert(ex)
})
多個串聯(lián)
應(yīng)用場景:按照順序執(zhí)行
var src1 = './imgDemo1.png'
var result1 = loadImg(src1)
var src2 = './imgDemo2.png'
var result2 = loadImg(src2)
result1.then(function (img) {
? ? ? ? alert(img.width)
? ? ? ? return result2
}).then(function(img) {
? ? ? ? alert(img.height)
}).catch(function(ex) {
? ? ? ? alert(ex)
})
Promise.all & Promise.race
Promise.all接收一個promise對象的數(shù)組
待全部完成之后,統(tǒng)一執(zhí)行success:
Promise.all([ result1 ], [ result2 ]).then(datas=> {
? ? //接收到的datas是一個數(shù)組畔勤,依次包含多個promise返回的內(nèi)容
? ? console.log(datas[ 0 ])
? ? console.log(datas[ 1 ])
})
Promise.all可以將多個Promise實例包裝成一個新的Promise實例蕾各。同時,成功和失敗的返回值是不同的庆揪,成功的時候返回的是一個結(jié)果數(shù)組示损,而失敗的時候則返回最先被reject失敗狀態(tài)的值。
Promise.race接收一個包含多個promise對象的數(shù)組
只要有一個完成嚷硫,就執(zhí)行success
Promise.all([ result1 ], [ result2 ]).then(data=> {
//接收到的data是最先執(zhí)行完的promise的返回值
? ? console.log(data)
})