久經前端開發沙場,會經歷各式各樣的需求,處理這些需求時候,會使用各類各樣的api和功能,這裏集中對setTimeout和Promise的異步功能進行討論一下。前端
這裏就使用Promise
做爲例子,來探究一下單獨使用它,會有哪些注意點。vue
1.最初的試探 api
執行代碼,Promise
的基本使用:瀏覽器
let fn = () => { console.log(1) let a = new Promise((resolve, reject) => { console.log(2) resolve(3) }) console.log(4) return a } // 執行 fn().then(data => console.log(data))
以上代碼,輸出結果爲:異步
1 // 同步 2 // 同步 4 // 同步 3 // 異步
注意 new Promise()
是同步方法,resolve
纔是異步方法。
此外,上面的方法,能夠有下面這種寫法,效果等同,主要是把Promise
精簡了一下:編輯器
let fn = () => { console.log(1) console.log(2) let a = Promise.resolve(3) console.log(4) return a } // 執行 fn().then(data => console.log(data))
由於如今討論的是Promise
的異步功能,因此下面均使用第二種寫法的Promise
函數
2.多個同級Promise
性能
編輯器中,輸入如下代碼,多個同級的單層的Promise
:code
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') }) Promise.resolve().then(() => { console.log('P-1.2') }) Promise.resolve().then(() => { console.log('P-1.3') }) console.log('同步-0.2')
則會依次輸出如下打印,毫無疑問的結果:server
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3
3.Promise
套Promise
複雜一下,新增行有註釋說明:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { // 新加行 console.log('P-2.1') // 新加行 }) // 新加行 }) Promise.resolve().then(() => { console.log('P-1.2') Promise.resolve().then(() => { // 新加行 console.log('P-2.2') // 新加行 }) // 新加行 }) Promise.resolve().then(() => { console.log('P-1.3') Promise.resolve().then(() => { // 新加行 console.log('P-2.3') // 新加行 }) // 新加行 }) console.log('同步-0.2')
輸出結果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3 P-2.1 P-2.2 P-2.3
可見,多層Promise
是一層一層執行的。
4.爲了最終確認,進行最後一次驗證,在第一個Promise
裏面多加一層:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { console.log('P-2.1') Promise.resolve().then(() => { // 新加行 console.log('P-3.1') // 新加行 }) // 新加行 Promise.resolve().then(() => { // 新加行 console.log('P-3.2') // 新加行 }) // 新加行 }) }) Promise.resolve().then(() => { console.log('P-1.2') Promise.resolve().then(() => { console.log('P-2.2') }) }) Promise.resolve().then(() => { console.log('P-1.3') Promise.resolve().then(() => { console.log('P-2.3') }) }) console.log('同步-0.2')
輸出結果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3 P-2.1 P-2.2 P-2.3 P-3.1 P-3.2
確認完畢,的確是一層一層的執行。
並且這裏能夠告訴你們,setTimeout
和setInterval
在單獨使用的時候,和Promise
是同樣的,一樣是分層執行,這裏再也不貼代碼了(友情提醒:setInterval
的話,須要第一次執行就把這個定時器清掉,不然就無限執行,卡死頁面秒秒鐘的事兒),
接下來纔是重點,下面將setTimeout
和Promise
進行混合操做。
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') }) setTimeout(() => { console.log('S-1.1') }); Promise.resolve().then(() => { console.log('P-1.2') }) setTimeout(() => { console.log('S-1.2') }); console.log('同步-0.2')
執行結果以下。。。問題暴露出來了:
同步-0.1 同步-0.2 P-1.1 P-1.2 S-1.1 S-1.2
爲何,在同級狀況下,是Promise
執行完了setTimeout
纔會執行?
是人性的泯滅,仍是道德的淪喪?
是由於JavaScript任務類型!
JavaScript的微任務和宏任務
敲黑板,標重點。
JavaScript的任務分爲微任務(Microtasks)和宏任務(task);
而Promise
是微任務,setTimeout
是宏任務。
因此上面的代碼中,代碼執行時會是以下場景:
開始執行當前宏任務代碼!遇到了
Promise
?好嘞,把它裏面的異步代碼,放在當前這個宏任務後面微任務裏面,而後繼續執行咱的;咦,有個
setTimeout
?是個宏任務,那在當前這個宏任務後面,建立第二個宏任務,而後把這個setTimeout
裏面的代碼塞進去,咱繼續執行;咦,又一個
Promise
?把他塞進後面的微任務裏。。。什麼?已經有代碼了?那有啥關係,繼續往裏塞,放在已有代碼的後面就行,咱繼續執行;天啊,又來一個
setTimeout
,如今後面已經有第二個宏任務了對吧?那就建立第三個宏任務吧,後面再遇到的話,繼續建立;報告!代碼執行到底了,當前這個宏任務執行完畢!
行,看一下咱的小尾巴---咱的微任務裏面有代碼嗎?有的話直接執行;報告,微任務裏面,那兩個
Promise
的異步代碼執行完了!
乾的漂亮。。。對了,剛剛微任務裏面,有沒有新的Promise
微任務?有的話,繼續在如今這個微任務後面放!對對,只看執行到的代碼,有多少放多少,一下子直接就執行了。。。若是遇到了setTimeout
知道該怎麼作吧?繼續開宏任務!報告,微任務所有執行完畢!
好!開始執行下一個宏任務!
因此,如今若是執行下面的代碼,結果也顯而易見吧:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { // 新加行 console.log('P-2.1') // 新加行 Promise.resolve().then(() => { // 新加行 console.log('P-3.1') // 新加行 }) // 新加行 }) // 新加行 }) setTimeout(() => { console.log('S-1.1') }); Promise.resolve().then(() => { console.log('P-1.2') }) setTimeout(() => { console.log('S-1.2') }); console.log('同步-0.2')
執行結果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-2.1 P-3.1 S-1.1 S-1.2
不管Promise
套用多少層,都會在下一個setTimeout
以前執行。
Dom
操做究竟是同步,仍是異步?這裏出現一個說不清道不明的疑問,Dom
操做究竟是同步操做仍是異步操做?
若是是同步操做,那vue
的nextTick
方法是作什麼用的?不就是在Dom更新完以後的回調方法嗎?
若是是異步操做,那在劇烈操做Dom後面的代碼,爲何會被阻塞?並且代碼看上去,也的確是按順序執行的?
這裏直接說明:js裏面的Dom操做代碼,是同步執行,但瀏覽器進行的Dom渲染,是異步操做。
瀏覽器渲染Dom和執行js,同時只能二選一,渲染一次Dom的時機是,當前宏任務和小尾巴微任務執行完,下一個宏任務開始前
vue
的nextTick
方法,則是使用H5的Api---MutationObserver
,監聽瀏覽器將Dom渲染完成的時機。若瀏覽器不支持此方法,則會使用
setTimeout
,把nextTick
回調函數的執行時機,做爲一個宏任務;上面也說了,瀏覽器渲染一次Dom,是下一個宏任務開始前,這樣使用了
setTimeout
,保證了Dom確實渲染完成。這裏也須要稍做提醒,js操做Dom是同步的,但操做Dom,畢竟超出了js自己語言的Api,每操做一次Dom,都須要消耗必定的性能,因此,在適合的狀況下,最好先把要修改的Dom的內容,以字符串或者虛擬Dom的形式拼接好,而後操做一次Dom,把組裝好的Dom字符串或虛擬Dom,一次性的塞進HTML頁面的真實Dom中。