0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

js基礎(chǔ)之setTimeout與setInterval原理分析

京東云 ? 來(lái)源:jf_75140285 ? 作者:jf_75140285 ? 2024-09-19 15:10 ? 次閱讀

setTimeout與setInterval概述

setTimeout與setInterval是JavaScript引擎提供的兩個(gè)定時(shí)器方法,分別用于函數(shù)的延時(shí)執(zhí)行和循環(huán)調(diào)用。前者的主要思想是通過(guò)一個(gè)定時(shí)器,讓函數(shù)在計(jì)時(shí)結(jié)束后再執(zhí)行;后者則是每隔一定的時(shí)間,就啟動(dòng)一次函數(shù)的執(zhí)行。

從原理來(lái)看,兩者似乎并不復(fù)雜。但由于JavaScript引擎是單線程的,這就讓上述兩個(gè)定時(shí)器的實(shí)際執(zhí)行變得稍微復(fù)雜了一些。下面我們來(lái)看一下兩者的運(yùn)行機(jī)制與需要注意的問(wèn)題。

基本原理

知識(shí)鋪墊

單線程模型:由于JavaScript被設(shè)計(jì)為用在瀏覽器環(huán)境,而該環(huán)境下存在大量可能發(fā)生沖突的DOM操作,為了避免進(jìn)行復(fù)雜的沖突處理(可能存在的沖突數(shù)量幾乎不可預(yù)測(cè)),JavaScript的設(shè)計(jì)者舍棄了java的多線程模型(該模型下,執(zhí)行引擎同時(shí)可以做幾件事,但要進(jìn)行線程同步),將其設(shè)計(jì)成了一門單線程語(yǔ)言(執(zhí)行引擎在同一時(shí)間只做一件事)。

注意:這里的單線程是指JavaScript的主線程只有一個(gè)。除了這個(gè)主線程,JavaScript還有一個(gè)I/O線程,通過(guò)事件循環(huán)來(lái)處理I/O問(wèn)題,但兩者之間相對(duì)獨(dú)立,不需要進(jìn)行狀態(tài)同步,因此我們?nèi)匀豢梢园袹avaScript看成一門單線程語(yǔ)言。

任務(wù)隊(duì)列:所謂任務(wù)隊(duì)列,就是用于存儲(chǔ)等待執(zhí)行的任務(wù)的隊(duì)列。由于JavaScript是一門單線程語(yǔ)言,如果當(dāng)前有一個(gè)任務(wù)需要執(zhí)行,但JavaScript引擎正在執(zhí)行其他任務(wù),那么這個(gè)任務(wù)就需要放進(jìn)一個(gè)隊(duì)列中進(jìn)行等待。等到線程空閑時(shí),就可以從這個(gè)隊(duì)列中取出最早加入的任務(wù)進(jìn)行執(zhí)行(類似于我們?nèi)ャy行排隊(duì)辦理業(yè)務(wù)。單線程相當(dāng)于說(shuō)這家銀行只有一個(gè)服務(wù)窗口,一次只能為一個(gè)人服務(wù),后面到的就需要排隊(duì),而任務(wù)隊(duì)列就是排隊(duì)區(qū),先到的就優(yōu)先服務(wù))。

注意:如果當(dāng)前線程空閑,并且隊(duì)列為空,那每次加入隊(duì)列的函數(shù)將立即執(zhí)行。

setTimeout與setInterval

setTimeout(func, delay, args) :設(shè)置超時(shí)調(diào)用。如對(duì)于setTimeout(func, 100, args),js引擎會(huì)為func函數(shù)設(shè)置一個(gè)計(jì)時(shí)器,100毫秒后,將func添加到任務(wù)隊(duì)列等待執(zhí)行。

setInterval(func, interval, args) :設(shè)置循環(huán)調(diào)用。對(duì)于語(yǔ)句setInterval(func, 100, args),js引擎每隔100毫秒就會(huì)把func添加到任務(wù)隊(duì)列一次。

相同點(diǎn):

兩者都會(huì)加入同一個(gè)隊(duì)列,等待線程空閑時(shí)執(zhí)行。

兩者都無(wú)法保證在何時(shí)執(zhí)行回調(diào),因?yàn)闊o(wú)法知道線程何時(shí)空閑。

不同點(diǎn)

setTimeout只會(huì)將函數(shù)添加到任務(wù)隊(duì)列一次,而setInterval則是循環(huán)往隊(duì)列中添加函數(shù)。

setTimeout可以保證函數(shù)在指定的時(shí)間間隔內(nèi)不會(huì)執(zhí)行,而setInterval無(wú)法保證(有可能出現(xiàn)接近連續(xù)執(zhí)行的情況,后面會(huì)分析原因)。

運(yùn)行機(jī)制

setTimeout

setTimeout的運(yùn)行機(jī)制相對(duì)簡(jiǎn)單,即在執(zhí)行該語(yǔ)句時(shí),設(shè)置一個(gè)定時(shí)器,定時(shí)時(shí)間置為所設(shè)置的延時(shí),當(dāng)計(jì)時(shí)結(jié)束后,將傳入的函數(shù)加入任務(wù)隊(duì)列,之后的執(zhí)行就交給任務(wù)隊(duì)列負(fù)責(zé)。

setTimeout函數(shù)本身會(huì)返回一個(gè)句柄,我們可以在函數(shù)執(zhí)行前通過(guò)向clearTimeout傳入該句柄取消函數(shù)的執(zhí)行。示例代碼如下:

function func(message){
	;
}
//設(shè)置100毫秒后執(zhí)行func函數(shù)
var timer = setTimeout(func, 100, "你好");

function cancel(){
	clearTimeout(timer);   //取消超時(shí)調(diào)用
}

上述代碼將在100毫秒后執(zhí)行func函數(shù),彈出一個(gè)內(nèi)容為"你好"的對(duì)話框。如果在100毫秒內(nèi)調(diào)用了cancel,就可以取消func函數(shù)的執(zhí)行。

setInterval

setInterval本質(zhì)上就是每隔一定的時(shí)間向任務(wù)隊(duì)列添加回調(diào)函數(shù)。但setInterval有一個(gè)原則:在向隊(duì)列中添加回調(diào)函數(shù)時(shí),如果隊(duì)列中存在之前由其添加的回調(diào)函數(shù),就放棄本次添加(不會(huì)影響之后的計(jì)時(shí))。另外也可以通過(guò)clearInterval方法移除定時(shí)器,使用方法同clearTimeout。

由于setInterval只負(fù)責(zé)定時(shí)向隊(duì)列中添加函數(shù),而不考慮函數(shù)的執(zhí)行,那么我們考慮一下下面的情況:

假設(shè)線程執(zhí)行完setInterval(func, 100, args)后處于完全空閑狀態(tài)(即只要向任務(wù)隊(duì)列添加函數(shù)就會(huì)立即執(zhí)行)。而func是一個(gè)相對(duì)復(fù)雜的函數(shù),執(zhí)行該函數(shù)需要90毫秒。那么函數(shù)的執(zhí)行過(guò)程就會(huì)變成下圖所示:

chaijie_default.png

從圖中可以看到,從上次函數(shù)執(zhí)行完畢,到下次開(kāi)始執(zhí)行,之間只間隔了10毫秒,而不是我們所希望的每隔100毫秒執(zhí)行一次(因?yàn)閟etInterval只關(guān)注任務(wù)添加,不關(guān)注任務(wù)執(zhí)行)。

由于上述機(jī)制,在很多情況下,setInterval都會(huì)遇到一些性能問(wèn)題。就拿上面的例子來(lái)說(shuō),我們的本意可能是每隔100毫秒執(zhí)行一次函數(shù),結(jié)果只等待了10毫秒就又執(zhí)行了一次。另外,對(duì)于復(fù)雜的實(shí)際情況,setInterval經(jīng)常出現(xiàn)兩次的執(zhí)行間隔相差甚遠(yuǎn)的情況,對(duì)于用戶能感知到的操作,這會(huì)帶來(lái)很不好的用戶體驗(yàn)。因此在實(shí)際編碼中,開(kāi)發(fā)者通常會(huì)使用setTimeout來(lái)模擬實(shí)現(xiàn)setInterval效果(下面會(huì)有舉例)。

而如果線程一開(kāi)始是繁忙的,直到150毫秒處才進(jìn)入空閑狀態(tài)(假設(shè)func執(zhí)行時(shí)長(zhǎng)為10毫秒),那么實(shí)際的運(yùn)行將變成下圖所示:

chaijie_default.png

這里在100毫秒處向隊(duì)列添加func時(shí),由于線程繁忙,上次添加的func還在隊(duì)列中等待,因此直接丟棄本次要添加的函數(shù),但在200毫秒時(shí)仍然重新向隊(duì)列中添加func。

應(yīng)用場(chǎng)景

setTimeout

setTimeout主要用于需要進(jìn)行延時(shí)調(diào)用的場(chǎng)景中。如之前一篇文章介紹的js基礎(chǔ)之函數(shù)的節(jié)流與防抖,就是setTimeout典型的應(yīng)用場(chǎng)景。此外,由于setInterval存在的性能問(wèn)題,在實(shí)際的編碼中,開(kāi)發(fā)人員通常會(huì)使用setTimeout來(lái)模擬setInterval,以防止出現(xiàn)函數(shù)連續(xù)執(zhí)行的情況。如對(duì)于下面的代碼:

function func(args){
  //函數(shù)本身的邏輯
  ...
}
var timer = setInterval(func, 100, args);

我們可以通過(guò)以下代碼來(lái)實(shí)現(xiàn):

var timer;
function func(args){
  //函數(shù)本身的邏輯
  ...
  //函數(shù)執(zhí)行完后,重置定時(shí)器
  timer = setTimeout(func, 100, args);
}
timer = setTimeout(func, 100, args);

利用setTimeout保證在指定的時(shí)間內(nèi)不會(huì)執(zhí)行的特點(diǎn),我們可以在執(zhí)行完上次的回調(diào)函數(shù)后,重置定時(shí)器,實(shí)現(xiàn)循環(huán)執(zhí)行func的效果,并且從上次執(zhí)行完畢到下次執(zhí)行開(kāi)始,至少會(huì)經(jīng)過(guò)100毫秒。這在實(shí)際的編碼中通常會(huì)帶來(lái)較大的性能提升,同時(shí)函數(shù)的執(zhí)行間隔也會(huì)相對(duì)穩(wěn)定。

setInterval

盡管存在上述性能問(wèn)題,setInterval的使用場(chǎng)景相對(duì)較少,但當(dāng)所使用的接口來(lái)自外部(即回調(diào)函數(shù)本身無(wú)法修改)時(shí),就必須通過(guò)setInterval來(lái)實(shí)現(xiàn)循環(huán)執(zhí)行了。此外,對(duì)于動(dòng)畫效果來(lái)說(shuō),我們通常會(huì)希望動(dòng)畫運(yùn)行的更加平滑(也就是希望函數(shù)運(yùn)行得更頻繁),這時(shí)使用setInterval往往更加流暢,具體請(qǐng)參考之前的文章使用原生js實(shí)現(xiàn)簡(jiǎn)單動(dòng)畫效果。

除了這類情況,開(kāi)發(fā)者一般不會(huì)使用setInterval方法進(jìn)行循環(huán)調(diào)用。

補(bǔ)充說(shuō)明

setTimeout與setInterval的第一個(gè)參數(shù)可以是一個(gè)匿名函數(shù),也可以是一個(gè)函數(shù)名,或者是一個(gè)字符串,如下面的寫法都是合法的:

function func(msg){
  ...
}
//傳入回調(diào)函數(shù)名
setTimeout(func, 100, "夕山雨");
//傳入匿名函數(shù)
setTimeout(function(name){
  ...
}, 100, "夕山雨");
//傳入字符串,js引擎會(huì)將其解析為函數(shù)體
setTimeout("", 100);

但是傳入如下的格式就可能報(bào)錯(cuò):

setTimeout(func("夕山雨"), 100);

因?yàn)檫@種寫法實(shí)際上是先調(diào)用func函數(shù),然后再將返回值添加到任務(wù)隊(duì)列。如果func的返回值不是函數(shù)(或可執(zhí)行的字符串),那么程序就會(huì)報(bào)錯(cuò);如果返回值是函數(shù),則會(huì)將返回的函數(shù)添加到任務(wù)隊(duì)列。該情況可以寫成下面的形式:

//將其作為字符串傳入,就可以被正確解析
setTimeout("func('夕山雨')", 100);

此外,當(dāng)給setTimeout傳入的延遲時(shí)間為0時(shí),并不代表回調(diào)函數(shù)會(huì)立即執(zhí)行。實(shí)際上瀏覽器規(guī)定的有一個(gè)默認(rèn)的最短計(jì)時(shí)時(shí)間,對(duì)于現(xiàn)代瀏覽器,這個(gè)時(shí)間一般為4毫秒(老版本的瀏覽器則會(huì)更長(zhǎng)一些)。也就是說(shuō),即使傳入的延遲時(shí)間為0,瀏覽器也會(huì)至少在4毫秒后才會(huì)執(zhí)行。

上述補(bǔ)充說(shuō)明同樣適用于setInterval。

總結(jié)

setTimeout與setInterval都是通過(guò)一個(gè)定時(shí)器控制回調(diào)函數(shù)的執(zhí)行,但由于javascript單線程的特點(diǎn),兩者都不能準(zhǔn)確控制函數(shù)的執(zhí)行時(shí)間點(diǎn),這點(diǎn)還請(qǐng)開(kāi)發(fā)者注意。如果函數(shù)只需要執(zhí)行一次,很顯然我們會(huì)使用setTimeout來(lái)實(shí)現(xiàn);如果是循環(huán)執(zhí)行的情況,如果我們希望函數(shù)執(zhí)行頻率不那么高,并且間隔更穩(wěn)定,通常是使用setTimeout模擬實(shí)現(xiàn)setInterval效果。

總的來(lái)說(shuō),雖然都被用于函數(shù)延遲執(zhí)行,但兩者的運(yùn)行機(jī)制有本質(zhì)上的區(qū)別,所以在使用的時(shí)候請(qǐng)注意區(qū)分。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 定時(shí)器
    +關(guān)注

    關(guān)注

    23

    文章

    3218

    瀏覽量

    113664
  • JS
    JS
    +關(guān)注

    關(guān)注

    0

    文章

    75

    瀏覽量

    17983
  • javascript
    +關(guān)注

    關(guān)注

    0

    文章

    515

    瀏覽量

    53656
  • 單線程
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    1759
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    推薦一個(gè)支持js的嵌入式設(shè)備開(kāi)發(fā)平臺(tái)

    可以通過(guò)vscode開(kāi)發(fā)js,實(shí)時(shí)推送js代碼到設(shè)備里運(yùn)行,無(wú)需編譯,支持屏幕,感興趣的可以看看 https://github.com/duoxianwulian/dxdop 提供很多js庫(kù),可以
    發(fā)表于 09-04 14:04

    【Intel Edison試用體驗(yàn)】16.node.js 入門setTimeout 的使用

    函數(shù)用delay是不可行的,所以我們會(huì)用到很多定時(shí)器,而在node.js中,使用setTimeout就可以完成定時(shí)器的任務(wù),在一定時(shí)間后再次運(yùn)行某函數(shù)。var mraa = require('mraa
    發(fā)表于 07-22 10:53

    【Intel Edison試用體驗(yàn)】[Demo] PWM-舵機(jī) 基于mraa設(shè)計(jì)高級(jí)函數(shù)庫(kù) [XDK-node.js](小k - NO.13)

    傳送門:【Intel Edison試用體驗(yàn)】[初見(jiàn)] 開(kāi)箱 and 測(cè)試 (小k - NO.01) 傳送門:【Intel Edison試用體驗(yàn)】[硬件] 底層硬件探索 and 啟動(dòng)過(guò)程分析(小k
    發(fā)表于 08-10 17:37

    【Intel Edison試用體驗(yàn)】[Demo] PWM-實(shí)現(xiàn)LED呼吸燈,原創(chuàng)二次函數(shù)方法 [XDK-node.js](小k - NO.14)

    傳送門:【Intel Edison試用體驗(yàn)】[初見(jiàn)] 開(kāi)箱 and 測(cè)試 (小k - NO.01) 傳送門:【Intel Edison試用體驗(yàn)】[硬件] 底層硬件探索 and 啟動(dòng)過(guò)程分析(小k
    發(fā)表于 08-10 23:21

    【Intel Edison試用體驗(yàn)】[Demo] PWM-實(shí)現(xiàn)蜂鳴器Bepp奏樂(lè) [XDK-node.js](小k - NO.15)

    傳送門:【Intel Edison試用體驗(yàn)】[初見(jiàn)] 開(kāi)箱 and 測(cè)試 (小k - NO.01) 傳送門:【Intel Edison試用體驗(yàn)】[硬件] 底層硬件探索 and 啟動(dòng)過(guò)程分析(小k
    發(fā)表于 08-10 23:49

    【Intel Edison試用體驗(yàn)】[Demo] ADC 光線傳感器 mraa和upm [XDK-node.js](小k - NO.17)

    node.js語(yǔ)言,通過(guò)兩種庫(kù)“mraa” 和upm分別實(shí)現(xiàn)光學(xué)系數(shù)采集 ·工程代碼,樓主自主原創(chuàng),由于對(duì)node.js剛接觸,不熟悉,有什么優(yōu)化的地方,可以一起學(xué)習(xí)探討。 ·準(zhǔn)備工作 ·Edsion
    發(fā)表于 08-11 10:18

    買電腦與JS(奸商)較量六大要點(diǎn)

    買電腦與JS(奸商)較量六大要點(diǎn) 一般用戶在購(gòu)機(jī)與JS面對(duì)面打交道的時(shí)候,一定要牢記一條真理:JS的目的就是賺錢,不會(huì)平
    發(fā)表于 01-19 17:21 ?469次閱讀

    node.jsjs要點(diǎn)總結(jié)

    Node.js是一個(gè)面向服務(wù)器的框架,立足于Chrome強(qiáng)大的V8 JS引擎。盡管它由C++編寫而成,但是它及其應(yīng)用是運(yùn)行在JS上的。本文為開(kāi)發(fā)者總結(jié)了4個(gè)Node.js要點(diǎn)。 1.
    發(fā)表于 10-13 10:39 ?0次下載

    鴻蒙系統(tǒng)中JS框架的逐行分析

    我在前文中曾經(jīng)介紹過(guò)鴻蒙的 Javascript 框架,這幾天終于把 JS 倉(cāng)庫(kù)編譯通過(guò)了,期間踩了不少坑,也給鴻蒙貢獻(xiàn)了幾個(gè) PR。今天我們就來(lái)逐行分析鴻蒙系統(tǒng)中的 JS 框架。 文中的所有代碼都
    的頭像 發(fā)表于 10-21 14:37 ?1893次閱讀

    使用鴻蒙JS框架寫出來(lái)的JS代碼長(zhǎng)什么樣

    鴻蒙 JS 框架是零依賴的,只在開(kāi)發(fā)打包過(guò)程中使用到了一些 npm 包。打包完的代碼是沒(méi)有依賴任何 npm 包的。
    的頭像 發(fā)表于 03-26 15:46 ?2180次閱讀

    Python怎么玩轉(zhuǎn)JS腳本

    本項(xiàng)目旨在讓大家了解如何用Python來(lái)執(zhí)行JS腳本,其主要目的是在進(jìn)行數(shù)據(jù) 分析時(shí),需要利用爬蟲獲取數(shù)據(jù),有時(shí)會(huì)遇到JS混淆加密反爬取難點(diǎn),此時(shí)我們需 要獲取網(wǎng)頁(yè)JS加密代碼將其
    的頭像 發(fā)表于 02-23 16:26 ?926次閱讀
    Python怎么玩轉(zhuǎn)<b class='flag-5'>JS</b>腳本

    如何破解JS加密?

    學(xué)習(xí)爬蟲最難之一無(wú)非就是如何破解JS加密,但是關(guān)于JS加密的網(wǎng)上資料非常零散雜亂,本人對(duì)這方面也略有研究,本篇文章在之前兩篇文章[Python玩轉(zhuǎn)JS腳本]
    的頭像 發(fā)表于 02-24 14:57 ?1806次閱讀
    如何破解<b class='flag-5'>JS</b>加密?

    簡(jiǎn)述javascript定時(shí)器工作原理

    說(shuō)到 javascript 中的定時(shí)器,我們肯定會(huì)想到 setTimeout() 和 setInterval() 這兩個(gè)函數(shù)。本文將從事件循環(huán)(Event Loop) 的角度來(lái)分析兩者的工作原理和區(qū)別。
    的頭像 發(fā)表于 04-21 14:32 ?704次閱讀
    簡(jiǎn)述javascript定時(shí)器工作原理

    python爬蟲某站JS加密逆向分析

    實(shí)現(xiàn)的目標(biāo):可以通過(guò)JS加密逆向后,得到加密參數(shù),請(qǐng)求獲取數(shù)據(jù)。此方法同樣適用于被前端JS加密的用戶名、密碼爆破。
    的頭像 發(fā)表于 05-05 15:40 ?1467次閱讀
    python爬蟲<b class='flag-5'>之</b>某站<b class='flag-5'>JS</b>加密逆向<b class='flag-5'>分析</b>

    settimeoutsetinterval有哪些區(qū)別?

    settimeoutsetinterval有哪些區(qū)別? setTimeoutsetInterval都是JavaScript中的定時(shí)器函數(shù),用于在指定的時(shí)間間隔后執(zhí)行一段代碼。盡管它
    的頭像 發(fā)表于 12-09 14:32 ?1300次閱讀