一债鸡、setTimeout和setInterval定時器的參數(shù)
setTimeout和setInterval函數(shù)用來指定某個函數(shù)或某段代碼显拜,在多少毫秒之后插入異步隊列贫堰。它返回一個整數(shù),表示定時器的編號毯欣,以后可以用來取消這個定時器盲厌。
var timeoutID = scope.setTimeout(function[, delay, param1, param2])
定時器可以接受三個參數(shù)署照,delay表示執(zhí)行的代碼祸泪,delay表示延遲的時間(可選,省略默認為0)建芙,param1没隘、param2 回調(diào)函數(shù)接收的參數(shù)。
1岁钓、第一個參數(shù): 執(zhí)行的代碼
執(zhí)行的代碼一般為一個函數(shù)升略,使用時需要注意函數(shù)this指向問題。下面函數(shù)打印出來的name為undefined屡限,因為this指向window,
原因為setTimeout中沒有保存obj.sayName的執(zhí)行環(huán)境
var obj = {
name: 'hc3001',
sayName: function() {
console.log(this.name)
}
}
setTimeout(obj.sayName, 1000) //undefined
// 利用閉包直接調(diào)用obj.sayName()
var obj = {
name: 'hc3001',
sayName: function() {
console.log(this.name)
}
}
setTimeout(function() {
obj.sayName()
}, 1000) // 'hc3001'
//用bind解決
setTimeout(obj.sayName.bind(obj), 1000)
// 'hc3001'
2炕倘、第二個參數(shù): 時間钧大,時間單位為毫秒,此參數(shù)比較具有迷惑性罩旋。
a啊央、當時間為0時,并不代表立即執(zhí)行涨醋,后面setTimeout 瓜饥、setInterval運行機制會詳細講述。下面用一個函數(shù)直觀看下浴骂,當時間為0時乓土,一分鐘內(nèi)執(zhí)行的次數(shù)(‘Javascript 異步編程’中提到)。
const timer = function() {
var times = 1000 + Date.now()
var count = 0
var i = setInterval(function() {
if(Date.now() > times) {
clearInterval(i)
console.log('count', count)
}
count++
}, 0)
}
timer()
上述代碼條件是:同步任務的主線程執(zhí)行時間接近為0溯警,異步隊列中無其他程序執(zhí)行趣苏。
瀏覽器環(huán)境:count 252 也就是表示3.96毫秒執(zhí)行一次。
node環(huán)境:count 706 1.41毫秒執(zhí)行一次梯轻。
所以定時器設置0秒不管哪個環(huán)境0毫秒都是上達不到的食磕。根據(jù)[HTML5標準],setTimeOut推遲執(zhí)行的時間喳挑,最少是4毫秒彬伦。如果小于這個值,會被自動增加到4伊诵。這是為了防止多個setTimeout(f,0)語句連續(xù)執(zhí)行单绑,造成性能問題。
b日戈、當時間不為0時询张,表示在規(guī)定時間后將代碼插入到異步隊列中,而并不是在規(guī)定時間后執(zhí)行代碼浙炼。至于代碼具體執(zhí)行的時間要看異步隊列中有無其他程序份氧。利用時間為0時唯袄,我們可以實現(xiàn)函數(shù)的異步操作。
var fs = require('fs')
//異步執(zhí)行函數(shù)
const async = function(func) {
setTimeout(function(
func()
) , 0)
}
console.log('異步讀寫')
const sync = function() {
var readData = fs.readFileSync('data1.txt', 'utf8')
var writeData = fs.writeFileSync('data2.txt', readData)
console.log('同步寫入完成')
}
async(sync)
上面代碼蜗帜,把本來是同步的readFileSync 和 writeFileSync 放入setTimeout中實現(xiàn)異步操作恋拷,不用等待,這樣可以解決異步回調(diào)地獄的問題厅缺。
c蔬顾、此參數(shù)有范圍限制[0, 2^31 - 1],如果超過這個范圍則會初始化為 0湘捎。
3诀豁、第三個參數(shù): param
param為回調(diào)函數(shù)接收的參數(shù),目前IE9不支持此參數(shù)
setTimeout(function(a,b){
console.log(a+b);
},1000,1,1);
二窥妇、setTimeout舷胜、setInverval運行機制
1、定時器的運行機制是將指定的代碼在規(guī)定的時間內(nèi)插入下一輪異步隊列中活翩,并且在同步任務主線程代碼執(zhí)行完畢烹骨,異步隊列無其他程序的情況下才開始執(zhí)行。
setTimeout(function(){
console.log(1)
});
console.log(0)
//其他代碼需要100毫秒
上述代碼中同步任務主線程代碼需要執(zhí)行100毫秒材泄,再執(zhí)行setTimeout中的代碼(已在0秒放入異步隊列)沮焕,此時的客觀上表現(xiàn)為100毫秒后執(zhí)行的setTimeout中代碼。這就是0秒不會立即執(zhí)行的原因之一(主線程執(zhí)行時間就算接近0毫秒拉宗,也不會立即執(zhí)行異步隊列中的代碼峦树,參數(shù)部分已經(jīng)詳述)。
2簿废、這樣的運行機制會在使用setInterval時產(chǎn)生的一些問題空入。以下面代碼和圖片為例(高級程序設計)
var btn = document.querySelector('.my-btn')
btn.addEventListener((e)=> {
var t = setInterval(()=> {
console.log('success')
}, 200)
})
事件處理時間(黃色300毫秒部分)、代碼插入起始時間255毫秒族檬、第一個插入代碼執(zhí)行起始時間300毫秒歪赢、代碼執(zhí)行總時間325毫秒(圖中未標識)。
產(chǎn)生的問題:某些間隔會被跳過单料、多個定時器的代碼執(zhí)行之間的間隔可能會比預期的小埋凯。
原因:
(1)、第1 個定時器是在205ms 處添加到隊列中的扫尖,但是直到過了300ms 處才能夠執(zhí)行白对。
(2)、在把指定的代碼移到下一輪異步隊列之前换怖,如果Event Loop還存在上一輪的代碼沒有執(zhí)行甩恼。這種情況下,js是不會代碼移入異步隊列的。
(3)条摸、如果是625毫秒執(zhí)行完一輪代碼悦污,第二輪代碼就會立即執(zhí)行,因為第二輪代碼已經(jīng)在405毫秒插入異步隊列钉蒲,這就在直觀上造成了間隔時間跳過切端,代碼是連續(xù)執(zhí)行的。
解決方案:
setTimeout(function() {
//處理中100毫秒
setTimeout(arguments.callee, interval)
}, interval)
這種setTimeout方案一定要處理100毫秒代碼才調(diào)用‘插入到異步隊列的setTimeout代碼’顷啼。
3踏枣、這樣的運行機制和循環(huán)結(jié)合,會產(chǎn)生不少疑惑的事情钙蒙,如下面常見的所謂‘閉包’面試題茵瀑。
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log('i', i)
}, 1000)
}
//立即輸出5個5
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function() {
console.log('i', i)
}, 1000)
})(i)
}
//立即輸出0、1躬厌、2瘾婿、3、4
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function() {
console.log('i', i)
}, 1000*i)
})(i)
}
//每隔一秒輸出0烤咧、1、2抢呆、3煮嫌、4
三倚舀、定時器回調(diào)在異步隊列中的順序
1惫叛、在異步任務中有不同的觸發(fā)形式把回調(diào)代碼插入異步隊列(Even Queue)中,比如:new Promise()言疗、setTimeout恳邀,他們插入代碼是有一定順序的懦冰。
setTimeout(function() {
console.log('setTimeout')
})
new Promise(function(resolve) {
console.log('promise')
resolve()
}).then(function() {
console.log('then')
})
console.log('console')
//輸出結(jié)果順序:Promise、console谣沸、 then刷钢、setTimeout
setTimeout(function() {
console.log('setTimeout')
})
const readFilepromise = function() {
const ajax = new Promise(function(resolve, rejected) {
console.log('readFile')
const fs = require('fs')
const options = {
encoding: 'utf8'
}
fs.readFile('test1.txt', options, function(error, content) {
if (error !== null) {
reject(error)
} else {
resolve(content)
}
})
})
return ajax
}
readFilepromise().then(
function(d) {
console.log('d', d)
}
)
console.log('console')
//輸出結(jié)果順序:readFile、console乳附、 setTimeout内地、文件內(nèi)容
順序的關鍵是判斷then和setTimeout哪個先輸出。異步任務中完成指定的事所花時間是不定的赋除,在異步任務中setTimeout 雖然是0秒但還是要花一定時間把回調(diào)函數(shù)插入到異步隊列中阱缓,而promise.then此時是不需要時間插入異步隊列的(有的文章把setTimeout當宏任務、promise.then當微任務來理解举农。https://juejin.im/post/5b498d245188251b193d4059)荆针。但是當promise.then是讀取文件時,這個讀取文件的時間就不定了(依據(jù)文件大小來),導致promise.then后插入異步隊列中航背。這部分涉及到JavaScript執(zhí)行機制可以參考下圖喉悴,具體細節(jié)可以看下軟一峰老師的‘JavaScript 運行機制詳解:再談Event Loop’。
四沃粗、clearTimeout() 和 clearInterval()
setTimeout和setInterval函數(shù)粥惧,都會返回一個表示計數(shù)器編號的隨機整數(shù)值,將該整數(shù)傳入clearTimeout和clearInterval函數(shù)最盅,就可以取消對應的定時器突雪。
var id1 = setTimeout(f,1000)
var id2 = setInterval(f,1000)
clearTimeout(id1)
clearInterval(id2)
1、利用clearTimeout() 我們可以做一個簡單的倒計時功能涡贱。
<div>
倒計時:
<span class="timing"></span>
</div>
<script>
var accountTime = 3
document.querySelector('.timing').innerHTML = accountTime
var timing = function() {
var t = setTimeout((function() {
if(accountTime > 0) {
accountTime--
document.querySelector('.timing').innerHTML = accountTime
setTimeout(timing, 1000)
} else {
accountTime = 0
document.querySelector('.timing').innerHTML = accountTime
clearTimeout(t)
}
}), 1000)
}
timing()