這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 非同步編程的實現方式? JavaScript中的非同步機制可以分為以下幾種: 回調函數 的方式,使用回調函數的方式有一個缺點是,多個回調函數嵌套的時候會造成回調函數地獄,上下兩層的回調函數間的代碼耦合度太高,不利於代碼的可維護。 Pro ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
1. 非同步編程的實現方式?
JavaScript中的非同步機制可以分為以下幾種:
- 回調函數 的方式,使用回調函數的方式有一個缺點是,多個回調函數嵌套的時候會造成回調函數地獄,上下兩層的回調函數間的代碼耦合度太高,不利於代碼的可維護。
- Promise 的方式,使用 Promise 的方式可以將嵌套的回調函數作為鏈式調用。但是使用這種方法,有時會造成多個 then 的鏈式調用,可能會造成代碼的語義不夠明確。
- generator 的方式,它可以在函數的執行過程中,將函數的執行權轉移出去,在函數外部還可以將執行權轉移回來。當遇到非同步函數執行的時候,將函數執行權轉移出去,當非同步函數執行完畢時再將執行權給轉移回來。因此在 generator 內部對於非同步操作的方式,可以以同步的順序來書寫。使用這種方式需要考慮的問題是何時將函數的控制權轉移回來,因此需要有一個自動執行 generator 的機制,比如說 co 模塊等方式來實現 generator 的自動執行。
- async 函數 的方式,async 函數是 generator 和 promise 實現的一個自動執行的語法糖,它內部自帶執行器,當函數內部執行到一個 await 語句的時候,如果語句返回一個 promise 對象,那麼函數將會等待 promise 對象的狀態變為 resolve 後再繼續向下執行。因此可以將非同步邏輯,轉化為同步的順序來書寫,並且這個函數可以自動執行。
2. setTimeout、Promise、Async/Await 的區別
(1)setTimeout
console.log('script start') //1. 列印 script start setTimeout(function(){ console.log('settimeout') // 4. 列印 settimeout }) // 2. 調用 setTimeout 函數,並定義其完成後執行的回調函數 console.log('script end') //3. 列印 script start // 輸出順序:script start->script end->settimeout
(2)Promise
Promise本身是同步的立即執行函數, 當在executor中執行resolve或者reject的時候, 此時是非同步操作, 會先執行then/catch等,當主棧完成後,才會去調用resolve/reject中存放的方法執行,列印p的時候,是列印的返回結果,一個Promise實例。
console.log('script start') let promise1 = new Promise(function (resolve) { console.log('promise1') resolve() console.log('promise1 end') }).then(function () { console.log('promise2') }) setTimeout(function(){ console.log('settimeout') }) console.log('script end') // 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout
當JS主線程執行到Promise對象時:
- promise1.then() 的回調就是一個 task
- promise1 是 resolved或rejected: 那這個 task 就會放入當前事件迴圈回合的 microtask queue
- promise1 是 pending: 這個 task 就會放入 事件迴圈的未來的某個(可能下一個)回合的 microtask queue 中
- setTimeout 的回調也是個 task ,它會被放入 macrotask queue 即使是 0ms 的情況
(3)async/await
async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start'); async1(); console.log('script end') // 輸出順序:script start->async1 start->async2->script end->async1 end
async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的非同步操作完成,再執行函數體內後面的語句。可以理解為,是讓出了線程,跳出了 async 函數體。
例如:
async function func1() { return 1 } console.log(func1())
func1的運行結果其實就是一個Promise對象。因此也可以使用then來處理後續邏輯。
func1().then(res => { console.log(res); // 30 })
await的含義為等待,也就是 async 函數需要等待await後的函數執行完成並且有了返回結果(Promise對象)之後,才能繼續執行下麵的代碼。await通過返回一個Promise對象來實現同步的效果。
3. 對Promise的理解
Promise是非同步編程的一種解決方案,它是一個對象,可以獲取非同步操作的消息,他的出現大大改善了非同步編程的困境,避免了地獄回調,它比傳統的解決方案回調函數和事件更合理和更強大。
所謂Promise,簡單說就是一個容器,裡面保存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取非同步操作的消息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。
(1)Promise的實例有三個狀態:
- Pending(進行中)
- Resolved(已完成)
- Rejected(已拒絕)
當把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。
(2)Promise的實例有兩個過程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒絕)
註意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。
Promise的特點:
- 對象的狀態不受外界影響。promise對象代表一個非同步操作,有三種狀態,
pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態,這也是promise這個名字的由來——“承諾”; - 一旦狀態改變就不會再變,任何時候都可以得到這個結果。promise對象的狀態改變,只有兩種可能:從
pending
變為fulfilled
,從pending
變為rejected
。這時就稱為resolved
(已定型)。如果改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果。這與事件(event)完全不同,事件的特點是:如果你錯過了它,再去監聽是得不到結果的。
Promise的缺點:
- 無法取消Promise,一旦新建它就會立即執行,無法中途取消。
- 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
- 當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
總結:
Promise 對象是非同步編程的一種解決方案,最早由社區提出。Promise 是一個構造函數,接收一個函數作為參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者rejected 狀態,並且狀態一經改變,就凝固了,無法再被改變了。
狀態的改變是通過 resolve() 和 reject() 函數來實現的,可以在非同步操作結束後調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態的改變註冊回調函數。這個回調函數屬於微任務,會在本輪事件迴圈的末尾執行。
註意:在構造 Promise
的時候,構造函數內部的代碼是立即執行的
4. Promise的基本用法
(1)創建Promise對象
Promise對象代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve
和reject
。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 非同步操作成功 */){ resolve(value); } else { reject(error); } });
一般情況下都會使用new Promise()
來創建promise對象,但是也可以使用promise.resolve
和 promise.reject
這兩個方法:
- Promise.resolve
Promise.resolve(value)
的返回值也是一個promise對象,可以對返回值進行.then調用,代碼如下:
Promise.resolve(11).then(function(value){ console.log(value); // 列印出11 });
resolve(11)
代碼中,會讓promise對象進入確定(resolve
狀態),並將參數11
傳遞給後面的then
所指定的onFulfilled
函數;
創建promise對象可以使用new Promise
的形式創建對象,也可以使用Promise.resolve(value)
的形式創建promise對象;
- Promise.reject
Promise.reject
也是new Promise
的快捷形式,也創建一個promise對象。代碼如下:
Promise.reject(new Error(“我錯了,請原諒俺!!”));
就是下麵的代碼new Promise的簡單形式:
new Promise(function(resolve,reject){ reject(new Error("我錯了,請原諒俺!!")); });
下麵是使用resolve方法和reject方法:
function testPromise(ready) { return new Promise(function(resolve,reject){ if(ready) { resolve("hello world"); }else { reject("No thanks"); } }); }; // 方法調用 testPromise(true).then(function(msg){ console.log(msg); },function(error){ console.log(error); });
上面的代碼的含義是給testPromise
方法傳遞一個參數,返回一個promise對象,如果為true
的話,那麼調用promise對象中的resolve()
方法,並且把其中的參數傳遞給後面的then
第一個函數內,因此列印出 “hello world
”, 如果為false
的話,會調用promise對象中的reject()
方法,則會進入then
的第二個函數內,會列印No thanks
;
(2)Promise方法
Promise有五個常用的方法:then()、catch()、all()、race()、finally。下麵就來看一下這些方法。
- then()
當Promise執行的內容符合成功條件時,調用resolve
函數,失敗就調用reject
函數。Promise創建完了,那該如何調用呢?
promise.then(function(value) { // success }, function(error) { // failure });
then
方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved
時調用,第二個回調函數是Promise對象的狀態變為rejected
時調用。其中第二個參數可以省略。
then
方法返回的是一個新的Promise實例(不是原來那個Promise實例)。因此可以採用鏈式寫法,即then
方法後面再調用另一個then方法。
當要寫有順序的非同步事件時,需要串列時,可以這樣寫:
let promise = new Promise((resolve,reject)=>{ ajax('first').success(function(res){ resolve(res); }) }) promise.then(res=>{ return new Promise((resovle,reject)=>{ ajax('second').success(function(res){ resolve(res) }) }) }).then(res=>{ return new Promise((resovle,reject)=>{ ajax('second').success(function(res){ resolve(res) }) }) }).then(res=>{ })
那當要寫的事件沒有順序或者關係時,還如何寫呢?可以使用all
方法來解決。
2. catch()
Promise對象除了有then方法,還有一個catch方法,該方法相當於then
方法的第二個參數,指向reject
的回調函數。不過catch
方法還有一個作用,就是在執行resolve
回調函數時,如果出現錯誤,拋出異常,不會停止運行,而是進入catch
方法中。
p.then((data) => { console.log('resolved',data); },(err) => { console.log('rejected',err); } ); p.then((data) => { console.log('resolved',data); }).catch((err) => { console.log('rejected',err); });
3. all()
all
方法可以完成並行任務, 它接收一個數組,數組的每一項都是一個promise
對象。當數組中所有的promise
的狀態都達到resolved
的時候,all
方法的狀態就會變成resolved
,如果有一個狀態變成了rejected
,那麼all
方法的狀態就會變成rejected
。
javascript let promise1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(1); },2000) }); let promise2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(2); },1000) }); let promise3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(3); },3000) }); Promise.all([promise1,promise2,promise3]).then(res=>{ console.log(res); //結果為:[1,2,3] })
調用all
方法時的結果成功的時候是回調函數的參數也是一個數組,這個數組按順序保存著每一個promise對象resolve
執行時的值。
(4)race()
race
方法和all
一樣,接受的參數是一個每項都是promise
的數組,但是與all
不同的是,當最先執行完的事件執行完之後,就直接返回該promise
對象的值。如果第一個promise
對象狀態變成resolved
,那自身的狀態變成了resolved
;反之第一個promise
變成rejected
,那自身狀態就會變成rejected
。
let promise1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ reject(1); },2000) }); let promise2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(2); },1000) }); let promise3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(3); },3000) }); Promise.race([promise1,promise2,promise3]).then(res=>{ console.log(res); //結果:2 },rej=>{ console.log(rej)}; )
那麼race
方法有什麼實際作用呢?當要做一件事,超過多長時間就不做了,可以用這個方法來解決:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
5. finally()
finally
方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代碼中,不管promise
最後的狀態,在執行完then
或catch
指定的回調函數以後,都會執行finally
方法指定的回調函數。
下麵是一個例子,伺服器使用 Promise 處理請求,然後使用finally
方法關掉伺服器。
server.listen(port) .then(function () { // ... }) .finally(server.stop);
finally
方法的回調函數不接受任何參數,這意味著沒有辦法知道,前面的 Promise 狀態到底是fulfilled
還是rejected
。這表明,finally
方法裡面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。finally
本質上是then
方法的特例:
promise .finally(() => { // 語句 }); // 等同於 promise .then( result => { // 語句 return result; }, error => { // 語句 throw error; } );
上面代碼中,如果不使用finally
方法,同樣的語句需要為成功和失敗兩種情況各寫一次。有了finally
方法,則只需要寫一次。
5. Promise解決了什麼問題
在工作中經常會碰到這樣一個需求,比如我使用ajax發一個A請求後,成功後拿到數據,需要把數據傳給B請求;那麼需要如下編寫代碼:
let fs = require('fs') fs.readFile('./a.txt','utf8',function(err,data){ fs.readFile(data,'utf8',function(err,data){ fs.readFile(data,'utf8',function(err,data){ console.log(data) }) }) })
上面的代碼有如下缺點:
- 後一個請求需要依賴於前一個請求成功後,將數據往下傳遞,會導致多個ajax請求嵌套的情況,代碼不夠直觀。
- 如果前後兩個請求不需要傳遞參數的情況下,那麼後一個請求也需要前一個請求成功後再執行下一步操作,這種情況下,那麼也需要如上編寫代碼,導致代碼不夠直觀。
Promise
出現之後,代碼變成這樣:
let fs = require('fs') function read(url){ return new Promise((resolve,reject)=>{ fs.readFile(url,'utf8',function(error,data){ error && reject(error) resolve(data) }) }) } read('./a.txt').then(data=>{ return read(data) }).then(data=>{ return read(data) }).then(data=>{ console.log(data) })
這樣代碼看起了就簡潔了很多,解決了地獄回調的問題。
6. Promise.all和Promise.race的區別的使用場景
(1)Promise.all
Promise.all
可以將多個Promise
實例包裝成一個新的Promise實例。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果數組,而失敗的時候則返回最先被reject失敗狀態的值。
Promise.all中傳入的是數組,返回的也是是數組,並且會將進行映射,傳入的promise對象返回的值是按照順序在數組中排列的,但是註意的是他們執行的順序並不是按照順序的,除非可迭代對象為空。
需要註意,Promise.all獲得的成功結果的數組裡面的數據順序和Promise.all接收到的數組順序是一致的,這樣當遇到發送多個請求並根據請求順序獲取和使用數據的場景,就可以使用Promise.all來解決。
(2)Promise.race
顧名思義,Promse.race就是賽跑的意思,意思就是說,Promise.race([p1, p2, p3])裡面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。當要做一件事,超過多長時間就不做了,可以用這個方法來解決:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
7. 對async/await 的理解
async/await其實是Generator
的語法糖,它能實現的效果都能用then鏈來實現,它是為優化then鏈而開發出來的。從字面上來看,async是“非同步”的簡寫,await則為等待,所以很好理解async 用於申明一個 function 是非同步的,而 await 用於等待一個非同步方法執行完成。當然語法上強制規定await只能出現在asnyc函數中,先來看看async函數返回了什麼:
async function testAsy(){ return 'hello world'; } let result = testAsy(); console.log(result)
所以,async 函數返回的是一個 Promise 對象。async 函數(包含函數語句、函數表達式、Lambda表達式)會返回一個 Promise 對象,如果在函數中 return
一個直接量,async 會把這個直接量通過 Promise.resolve()
封裝成 Promise 對象。
async 函數返回的是一個 Promise 對象,所以在最外層不能用 await 獲取其返回值的情況下,當然應該用原來的方式:then()
鏈來處理這個 Promise 對象,就像這樣:
async function testAsy(){ return 'hello world' } let result = testAsy() console.log(result) result.then(v=>{ console.log(v) // hello world })
那如果 async 函數沒有返回值,又該如何?很容易想到,它會返回 Promise.resolve(undefined)
。
聯想一下 Promise 的特點——無等待,所以在沒有 await
的情況下執行 async 函數,它會立即執行,返回一個 Promise 對象,並且,絕不會阻塞後面的語句。這和普通返回 Promise 對象的函數並無二致。
註意:Promise.resolve(x)
可以看作是 new Promise(resolve => resolve(x))
的簡寫,可以用於快速封裝字面量對象或其他對象,將其封裝成 Promise 實例。
8. await 到底在等啥?
await 在等待什麼呢?一般來說,都認為 await 是在等待一個 async 函數完成。不過按語法說明,await 等待的是一個表達式,這個表達式的計算結果是 Promise 對象或者其它值(換句話說,就是沒有特殊限定)。
因為 async 函數返回一個 Promise 對象,所以 await 可以用於等待一個 async 函數的返回值——這也可以說是 await 在等 async 函數,但要清楚,它等的實際是一個返回值。註意到 await 不僅僅用於等 Promise 對象,它可以等任意表達式的結果,所以,await 後面實際是可以接普通函數調用或者直接量的。所以下麵這個示例完全可以正確運行:
function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); } test();
await 表達式的運算結果取決於它等的是什麼。
- 如果它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。
- 如果它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等著 Promise 對象 resolve,然後得到 resolve 的值,作為 await 表達式的運算結果。
來看一個例子:
function testAsy(x){ return new Promise(resolve=>{setTimeout(() => { resolve(x); }, 3000) } ) } async function testAwt(){ let result = await testAsy('hello world'); console.log(result); // 3秒鐘之後出現hello world console.log('cuger') // 3秒鐘之後出現cug } testAwt(); console.log('cug') //立即輸出cug
這就是 await 必須用在 async 函數中的原因。async 函數調用不會造成阻塞,它內部所有的阻塞都被封裝在一個 Promise 對象中非同步執行。await暫停當前async的執行,所以'cug''最先輸出,hello world'和‘cuger’是3秒鐘後同時出現的。
9. async/await的優勢
單一的 Promise 鏈並不能發現 async/await 的優勢,但是,如果需要處理由多個 Promise 組成的 then 鏈的時候,優勢就能體現出來了(很有意思,Promise 通過 then 鏈來解決多層回調的問題,現在又用 async/await 來進一步優化它)。
假設一個業務,分多個步驟完成,每個步驟都是非同步的,而且依賴於上一個步驟的結果。仍然用 setTimeout
來模擬非同步操作:
/** * 傳入參數 n,表示這個函數執行的時間(毫秒) * 執行的結果是 n + 200,這個值將用於下一步驟 */ function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(n) { console.log(`step2 with ${n}`); return takeLongTime(n); } function step3(n) { console.log(`step3 with ${n}`); return takeLongTime(n); }
現在用 Promise 方式來實現這三個步驟的處理:
function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 500 // step3 with 700 // result is 900 // doIt: 1507.251ms
輸出結果 result
是 step3()
的參數 700 + 200
= 900
。doIt()
順序執行了三個步驟,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeEnd()
計算的結果一致。
如果用 async/await 來實現呢,會是這樣:
async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt();
結果和之前的 Promise 實現是一樣的,但是這個代碼看起來是不是清晰得多,幾乎跟同步代碼一樣
10. async/await對比Promise的優勢
- 代碼讀起來更加同步,Promise雖然擺脫了回調地獄,但是then的鏈式調⽤也會帶來額外的閱讀負擔
- Promise傳遞中間值⾮常麻煩,⽽async/await⼏乎是同步的寫法,⾮常優雅
- 錯誤處理友好,async/await可以⽤成熟的try/catch,Promise的錯誤捕獲⾮常冗餘
- 調試友好,Promise的調試很差,由於沒有代碼塊,你不能在⼀個返回表達式的箭頭函數中設置斷點,如果你在⼀個.then代碼塊中使⽤調試器的步進(step-over)功能,調試器並不會進⼊後續的.then代碼塊,因為調試器只能跟蹤同步代碼的每⼀步。
11. async/await 如何捕獲異常
async function fn(){ try{ let a = await Promise.reject('error') }catch(error){ console.log(error) } }
12. 併發與並行的區別?
- 併發是巨集觀概念,我分別有任務 A 和任務 B,在一段時間內通過任務間的切換完成了這兩個任務,這種情況就可以稱之為併發。
- 並行是微觀概念,假設 CPU 中存在兩個核心,那麼我就可以同時完成任務 A、B。同時完成多個任務的情況就可以稱之為並行。
13. 什麼是回調函數?回調函數有什麼缺點?如何解決回調地獄問題?
以下代碼就是一個回調函數的例子:
ajax(url, () => { // 處理邏輯 })
回調函數有一個致命的弱點,就是容易寫出回調地獄(Callback hell)。假設多個請求存在依賴性,可能會有如下代碼:
ajax(url, () => { // 處理邏輯 ajax(url1, () => { // 處理邏輯 ajax(url2, () => { // 處理邏輯 }) }) })
以上代碼看起來不利於閱讀和維護,當然,也可以把函數分開來寫:
function firstAjax() { ajax(url1, () => { // 處理邏輯 secondAjax() }) } function secondAjax() { ajax(url2, () => { // 處理邏輯 }) } ajax(url, () => { // 處理邏輯 firstAjax() })
以上的代碼雖然看上去利於閱讀了,但是還是沒有解決根本問題。回調地獄的根本問題就是:
- 嵌套函數存在耦合性,一旦有所改動,就會牽一發而動全身
- 嵌套函數一多,就很難處理錯誤
當然,回調函數還存在著別的幾個缺點,比如不能使用 try catch
捕獲錯誤,不能直接 return
。
14. setTimeout、setInterval、requestAnimationFrame 各有什麼特點?
非同步編程當然少不了定時器了,常見的定時器函數有 setTimeout
、setInterval
、requestAnimationFrame
。最常用的是setTimeout
,很多人認為 setTimeout
是延時多久,那就應該是多久後執行。
其實這個觀點是錯誤的,因為 JS 是單線程執行的,如果前面的代碼影響了性能,就會導致 setTimeout
不會按期執行。當然了,可以通過代碼去修正 setTimeout
,從而使定時器相對準確:
let period = 60 * 1000 * 60 * 2 let startTime = new Date().getTime() let count = 0 let end = new Date().getTime() + period let interval = 1000 let currentInterval = interval function loop() { count++ // 代碼執行所消耗的時間 let offset = new Date().getTime() - (startTime + count * interval); let diff = end - new Date().getTime() let h = Math.floor(diff / (60 * 1000 * 60)) let hdiff = diff % (60 * 1000 * 60) let m = Math.floor(hdiff / (60 * 1000)) let mdiff = hdiff % (60 * 1000) let s = mdiff / (1000) let sCeil = Math.ceil(s) let sFloor = Math.floor(s) // 得到下一次迴圈所消耗的時間 currentInterval = interval - offset console.log('時:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代碼執行時間:'+offset, '下次迴圈間隔'+currentInterval) setTimeout(loop, currentInterval) } setTimeout(loop, currentInterval)
接下來看 setInterval
,其實這個函數作用和 setTimeout
基本一致,只是該函數是每隔一段時間執行一次回調函數。
通常來說不建議使用 setInterval
。第一,它和 setTimeout
一樣,不能保證在預期的時間執行任務。第二,它存在執行累積的問題,請看以下偽代碼
function demo() { setInterval(function(){ console.log(2) },1000) sleep(2000) } demo()
以上代碼在瀏覽器環境中,如果定時器執行過程中出現了耗時操作,多個回調函數會在耗時操作結束以後同時執行,這樣可能就會帶來性能上的問題。
如果有迴圈定時器的需求,其實完全可以通過 requestAnimationFrame
來實現:
function setInterval(callback, interval) { let timer const now = Date.now let startTime = now() let endTime = startTime const loop = () => { timer = window.requestAnimationFrame(loop) endTime = now() if (endTime - startTime >= interval) { startTime = endTime = now() callback(timer) } } timer = window.requestAnimationFrame(loop) return timer } let a = 0 setInterval(timer => { console.log(1) a++ if (a === 3) cancelAnimationFrame(timer) }, 1000)
首先 requestAnimationFrame
自帶函數節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),並且該函數的延時效果是精確的,沒有其他定時器時間不准的問題,當然你也可以通過該函數來實現 setTimeout
。