地址:前端面試題庫
繼續(xù)聊聊 js 的異步解決方案有哪些,以及各個方案的優(yōu)缺點是什么......
回調(diào)函數(shù)(callback)回調(diào)函數(shù)?簡單理解就是一個函數(shù)被作為參數(shù)傳遞給另一個函數(shù)。回調(diào)是早期最常用的一種異步解決方案。
回調(diào)并不一定就是異步,并沒有直接關(guān)系。
舉個簡單的例子:
function f1(cb) {
setTimeout(() =>{
cb && cb();
}, 2000);
}
f1(() =>{
console.log("1");
});
復(fù)制代碼
如上,我們使用?setTimeout
在函數(shù)?f1
中模擬了一個耗時?2s
的任務(wù),耗時任務(wù)結(jié)束后會拋出一個回調(diào),那么我們在調(diào)用時就可以做到在函數(shù)?f1
的耗時任務(wù)結(jié)束后執(zhí)行回調(diào)函數(shù)了。
采用這種方式,我們把同步操作變成了異步操作,f1
不會堵塞程序運行,相當(dāng)于先執(zhí)行程序的主要邏輯,將耗時的操作推遲執(zhí)行。
優(yōu)點:簡單、容易理解
缺點:代碼不優(yōu)雅,可讀性差,不易維護,高度耦合,層層嵌套造成回調(diào)地獄
事件監(jiān)聽(發(fā)布訂閱模式)發(fā)布訂閱模式?定義了對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將會得到通知。
其實我們都用過發(fā)布訂閱模式,比如我們在?DOM
節(jié)點上綁定一個事件函數(shù):
document.body.addEventListener('click', function () {
console.log('點擊');
})
復(fù)制代碼
但這只是對發(fā)布訂閱模式最簡單的使用,在很多場景下我們經(jīng)常會使用一些自定義事件來滿足我們的需求。
發(fā)布訂閱模式有很多種實現(xiàn)方式,下面我們用?class
來簡單實現(xiàn)下:
class Emitter {
constructor() {
// _listener數(shù)組,key為自定義事件名,value為執(zhí)行回調(diào)數(shù)組-因為可能有多個
this._listener = []
}
// 訂閱 監(jiān)聽事件
on(type, fn) {
// 判斷_listener數(shù)組中是否存在該事件命
// 存在將回調(diào)push到事件名對應(yīng)的value數(shù)組中,不存在直接新增
this._listener[type]
? this._listener[type].push(fn)
: (this._listener[type] = [fn])
}
// 發(fā)布 觸發(fā)事件
trigger(type, ...rest) {
// 判斷該觸發(fā)事件是否存在
if (!this._listener[type]) return
// 遍歷執(zhí)行該事件回調(diào)數(shù)組并傳遞參數(shù)
this._listener[type].forEach(callback =>callback(...rest))
}
}
復(fù)制代碼
如上所示,我們創(chuàng)建了一個?Emitter
類,并且添加了兩個原型方法?on
和?trigger
,使用如下:
// 創(chuàng)建一個emitter實例
const emitter = new Emitter()
emitter.on("done", function(arg1, arg2) {
console.log(arg1, arg2)
})
emitter.on("done", function(arg1, arg2) {
console.log(arg2, arg1)
})
function fn1() {
console.log('我是主程序')
setTimeout(() =>{
emitter.trigger("done", "異步參數(shù)一", "異步參數(shù)二")
}, 1000)
}
fn1()
復(fù)制代碼
我們先創(chuàng)建一個?emitter
實例,接著注冊事件,再觸發(fā)事件,也解決了異步問題。
優(yōu)點:比較符合模塊化思想,我們自寫監(jiān)聽器時可以做很多優(yōu)化從而更好地監(jiān)控程序運行。
缺點:整個程序變成了事件驅(qū)動,流程上或多或少都會有點影響,每次使用還得注冊事件監(jiān)聽再進行觸發(fā)挺麻煩的,代碼也不太優(yōu)雅。
PromiseES2015(ES6)標(biāo)準(zhǔn)化和引入了?Promise
對象,它是異步編程的一種解決方案。
簡單來說就是用同步的方式寫異步的代碼,可用來解決回調(diào)地獄問題。
Promise
對象狀態(tài)一旦改變,就不會再變,只有兩種變化可能:
- 從?
Pending
變?yōu)?Resolved
- 從?
Pending
變?yōu)?Rejected
我們用?setTimeout
模擬異步操作:
function analogAsync(n) {
return new Promise((resolve) =>{
setTimeout(() =>resolve(n + 500), n);
});
}
function fn1(n) {
console.log(`step1 with ${n}`);
return analogAsync(n);
}
function fn2(n) {
console.log(`step2 with ${n}`);
return analogAsync(n);
}
function fn3(n) {
console.log(`step3 with ${n}`);
return analogAsync(n);
}
復(fù)制代碼
用?Promise
來實現(xiàn):
function fn() {
let time1 = 0;
fn1(time1)
.then((time2) =>fn2(time2))
.then((time3) =>fn3(time3))
.then((res) =>{
console.log(`result is ${res}`);
});
}
fn();
復(fù)制代碼
Promise 優(yōu)缺點優(yōu)點:Promise
用同步的方式寫異步的代碼,避免了層層嵌套的回調(diào)函數(shù),可讀性更好。鏈?zhǔn)讲僮鳎梢栽?then
中繼續(xù)寫?Promise
對象并返回,然后繼續(xù)調(diào)用?then
來進行回調(diào)操作。
缺點:Promise
對象一旦新建就會立即執(zhí)行,無法中途取消。若不設(shè)置回調(diào)函數(shù),Promise
內(nèi)部會拋出錯誤,不會流到外部。
Generator
其實是一個函數(shù),只不過是一個特殊的函數(shù)。普通函數(shù),你執(zhí)行了這個函數(shù),函數(shù)內(nèi)部不會停,直到這個函數(shù)結(jié)束。Generator
這個函數(shù)特殊之處就是中間可以停。
示例:
function *generatorFn() {
console.log("a");
yield '1';
console.log("b");
yield '2';
console.log("c");
return '3';
}
let it = generatorFn();
it.next();
it.next();
it.next();
it.next();
復(fù)制代碼
上面這個示例就是一個?Generator
函數(shù),它有如下特點:
Generator 優(yōu)缺點
- 不同于普通函數(shù),
Generator
函數(shù)在?function
后面,函數(shù)名之前有個?*
- 函數(shù)內(nèi)部有?
yield
字段- 調(diào)用后其函數(shù)返回值使用了?
next
方法
優(yōu)點:優(yōu)雅的流程控制方式,可以讓函數(shù)可中斷執(zhí)行
缺點:Generator
函數(shù)的執(zhí)行必須靠執(zhí)行器,只針對異步處理來說,還是不太方便
ES2017 標(biāo)準(zhǔn)引入了?async
函數(shù),使得異步操作變得更加方便。async
是異步的意思,而?await
是?async wait
的簡寫,即異步等待,async/await
的出現(xiàn),被很多人認為是 js 異步操作的最終且最優(yōu)雅的解決方案。
async
函數(shù)返回的是一個?Promise
對象,如果在?async
函數(shù)中直接?return
一個直接量,async
會把這個直接量通過?Promise.resolve()
封裝成?Promise
對象返回。
await
等待的是一個表達式,這個表達式的計算結(jié)果是?Promise
對象或者其它值(換句話說,就是沒有特殊限定,啥都行)。
- 如果?
await
后面不是?Promise
對象,直接執(zhí)行- 如果?
await
后面是?Promise
對象會阻塞后面的代碼,Promise
對象?resolve
,然后得到?resolve
的值,作為?await
表達式的運算結(jié)果await
只能在?async
函數(shù)中使用
上述用?setTimeout
模擬異步操作,我們用?async/await
來實現(xiàn):
async function fn() {
let time1 = 0;
let time2 = await fn1(time1);
let time3 = await fn2(time2);
let res = await fn3(time3);
console.log(`result is ${res}`);
}
fn();
復(fù)制代碼
輸出結(jié)果和上面用?Promise
實現(xiàn)是一樣的,但這個?async/await
代碼結(jié)構(gòu)看起來清晰得多,幾乎跟同步寫法一樣,十分優(yōu)雅。
優(yōu)點:內(nèi)置執(zhí)行器,更好的語義,更廣的適用性
缺點:濫用?await
可能會導(dǎo)致性能問題,因為?await
會阻塞代碼
以上就是筆者對于 js 異步解決方案的概述,以及對它們的優(yōu)缺點的分析,如有不足歡迎大家指出,如果大家覺得還不錯的話,不要忘了點贊呦~
大廠面試題分享 面試題庫 前端面試題庫 (面試必備)? ?推薦:★★★★★地址:前端面試題庫
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
新聞名稱:【面試題】聊聊js異步解決方案-創(chuàng)新互聯(lián)
鏈接URL:http://vcdvsql.cn/article2/jiooc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、微信小程序、網(wǎng)站建設(shè)、軟件開發(fā)、做網(wǎng)站、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容