setInterval和setTimeout是定義在window上的兩個函數(shù)
這兩個函數(shù)接受兩個參數(shù):
1. 第一個參數(shù):接受一個回調(diào)函數(shù) callback
2. 第二個參數(shù):表示推遲執(zhí)行的毫秒數(shù)。 time
//每隔100毫秒輸出一個1
setInterval(function(){
console.log(1);
},100);
//100毫秒后輸出1,僅輸出一次
setTimeout(function(){
console.log(1);
},100);
不同的是:
1. setTimeout表示定時器,在指定毫秒數(shù)后執(zhí)行回調(diào)函數(shù),僅執(zhí)行一次衩椒;而setInterval表示定時循環(huán)器,每間隔指定毫秒數(shù)就執(zhí)行一遍回調(diào)函數(shù),若是計時器不被清除填物,回調(diào)函數(shù)會執(zhí)行無數(shù)遍
2. 清除定時器時,setInterval對應clearInterval()霎终,setTimeout對應clearTimeout()
clearInterval和clearTimeout也都是定義在window上的函數(shù)滞磺,接受一個參數(shù)表示定時器的名字,根據(jù)這個名字清除對應的定時器
使用定時器時注意幾點:
1. 若在創(chuàng)建定時器時沒有名字莱褒,則定時器無法清除击困;
2. 定時器定義在全局對象window上,內(nèi)部函數(shù)this指向的window广凸;
3. setInterval里面?zhèn)鬟f的毫秒數(shù)只會在第一次的時候識別阅茶,之后不能改了
4. setTimeout, setInterval是異步任務
有些同學可能還不知道異步任務是什么?
其實要深入理解異步谅海,還得理解js的執(zhí)行機制
我們先來看個例子吧
//下面代碼的執(zhí)行結(jié)果是什么
var p = new Promise(resolve => {
console.log(4);
resolve(5);
});
function func1() {
console.log(1)
}
function func2() {
setTimeout(() => {
console.log(2)
});
func1();
console.log(3);
p.then(resolved => {
console.log(resolved)
}).then(() => {
console.log(6)
});
}
func2();
答案:4 1 3 5 6 2
不知道你答對了沒脸哀?
如果答對了,說明你對JS執(zhí)行機制有較深的了解扭吁,繼續(xù)往下看可以復習和鞏固撞蜂;如果不知道或者答錯了盲镶,也別傷心,本文下面部分會給你答案
我們知道js的一大特點就是單線程蝌诡,同一時間只能做一件事情溉贿。但是為什么要設計成單線程呢?
其實跟它的用途有關(guān)浦旱,js作為瀏覽器的腳本語言顽照,主要用途就是于用戶互動和操作DOM。想想如果被設計成多線程闽寡,一個線程在某個DOM節(jié)點上添加內(nèi)容代兵,另外一個線程在刪除這個節(jié)點,那瀏覽器該聽誰的爷狈?
所以植影,為了避免多線程帶來的復雜問題,從設計之初就決定了JS就是單線程涎永,這稱為這門語言的核心特性思币,將來也不會改變。
為了利用多核CUP的計算能力羡微,HTML5提出了Web Worker標準谷饿,允許JS 創(chuàng)建多個線程,但是子線程完全受主線程的控制博投,且子線程不能操作DOM。所以捧挺,這個新標準并沒有改變js單線程的本質(zhì)。
單線程就意味著翅睛,所有任務需要排隊,前一個任務結(jié)束爬骤,才會執(zhí)行后一個任務。如果前一個任務耗時很長坷剧,后一個任務就不得不一直等著。但是如果有些任務很慢時(比如Ajax操作從網(wǎng)絡讀取數(shù)據(jù)),我還是要等結(jié)果在執(zhí)行后一個任務嗎偏序?這樣不好吧
于是,有了一種異步任務。
同步任務指的是冲呢,在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢恩尾,才能執(zhí)行后一個任務信柿;而異步任務指的是进鸠,不進入主線程霞幅、而進入"任務隊列"(task queue)的任務绍傲,只有主線程執(zhí)行完畢猎塞,主線程去通知"任務隊列"淡诗,某個異步任務可以執(zhí)行了,該任務才會進入主線程執(zhí)行插爹。
所以其實js多線程的實現(xiàn)就是通過異步的方式來實現(xiàn)的
運行機制如下:
(1)所有同步任務都在主線程上執(zhí)行气嫁,形成一個執(zhí)行棧(Call Stack)
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結(jié)果庶香,就在"任務隊列"之中放置一個事件
(3)一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢甲棍,系統(tǒng)就會讀取"任務隊列",看看里面有哪些事件感猛。那些對應的異步任務,于是結(jié)束等待狀態(tài)唱遭,進入執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復上面的第三步牺堰。
執(zhí)行棧用于組織JS代碼恨搓,保障JS代碼的有序執(zhí)行。每當調(diào)用一個函數(shù)時筏养,都會將該函數(shù)壓入執(zhí)行棧中辉浦,執(zhí)行完彈出,接著調(diào)用下一個函數(shù)
第二步中茎辐,異步任務的運行結(jié)果其實背后借助了瀏覽器的其他線程
瀏覽器內(nèi)核常駐的線程:
- js引擎線程
用于解釋執(zhí)行js代碼盏浙、用戶輸入、網(wǎng)絡請求等 - GUI渲染線程
繪制用戶界面荔茬,與JS主線程互斥(因為js可以操作DOM废膘,進而會影響到GUI的渲染結(jié)果) - http異步網(wǎng)絡請求線程
處理用戶的get、post等請求慕蔚,等返回結(jié)果后將回調(diào)函數(shù)推入到任務隊列 - 定時觸發(fā)器線程
setInterval丐黄、setTimeout等待時間結(jié)束后,會把執(zhí)行函數(shù)推入任務隊列中 - 瀏覽器事件處理線程
將click孔飒、mouse等交互事件發(fā)生后灌闺,將要執(zhí)行的回調(diào)函數(shù)放入到事件隊列中
任務隊列"是一個先進先出的數(shù)據(jù)結(jié)構(gòu)艰争,排在前面的事件,優(yōu)先被主線程讀取桂对。
主線程空了甩卓,才會再去讀取"任務隊列",但是任務隊列在不同的宿主環(huán)境中有所差異蕉斜,大部分宿主環(huán)境會將任務隊列分成macrotask(宏任務) 和 microtask(微任務)
宏任務主要包含:script( 整體代碼)逾柿、setTimeout、setInterval宅此、I/O机错、UI 交互事件、setImmediate(Node.js 環(huán)境)
微任務主要包含:Promise then父腕、async await弱匪、MutaionObserver、process.nextTick(Node.js 環(huán)境)
當執(zhí)行棧清空時璧亮,JS引擎首先會將微任務中的所以任務依次執(zhí)行結(jié)束萧诫,如果沒有微任務了,則執(zhí)行宏任務枝嘶。
"主線程的讀取過程基本上是自動的财搁,只要執(zhí)行棧一清空,"任務隊列"上第一位的事件就自動進入主線程躬络。但是定時器首先要檢查是否到了執(zhí)行時間尖奔,到了規(guī)定的時間,才進入主線程執(zhí)行穷当,執(zhí)行完再去任務隊列中讀取下一個事件提茁,這個過程是不斷循環(huán)的,我們把這種循環(huán)的機制稱為Event Loop(事件循環(huán))馁菜,即
主線程運行的時候茴扁,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用各種外部API(即各種函數(shù))汪疮,它們在"任務隊列"中加入各種事件(click峭火,load,done)智嚷。只要棧中的代碼執(zhí)行完畢卖丸,主線程就會去讀取"任務隊列",依次執(zhí)行那些事件所對應的回調(diào)函數(shù)盏道。
再來看定時器
如果將setTimeout()的第二個參數(shù)設為0稍浆,表示當執(zhí)行棧清空以后,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。
setTimeout(function(){console.log(1);}, 0);
console.log(2);
結(jié)果是2,1衅枫,因為只有在執(zhí)行完第二行以后嫁艇,系統(tǒng)才會去執(zhí)行"任務隊列"中的回調(diào)函數(shù)。
HTML5標準規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔)弦撩,不得低于4毫秒步咪,如果低于這個值,就會自動增加益楼。在此之前猾漫,老版本的瀏覽器都將最短間隔設為10毫秒。另外偏形,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分)静袖,通常不會立即執(zhí)行觉鼻,而是每16毫秒執(zhí)行一次俊扭。
需要注意的是,setTimeout()只是將事件插入了"任務隊列"坠陈,必須等到當前代碼(執(zhí)行棧以及排在前面的微任務)執(zhí)行完萨惑,主線程才會去執(zhí)行定時器中指定的回調(diào)函數(shù)。要是當前代碼耗時很長仇矾,有可能要等很久庸蔼,所以并沒有辦法保證,回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行贮匕。
最后回來看看上面的那道題目:
//下面代碼的執(zhí)行結(jié)果是什么
var p = new Promise(resolve => {
console.log(4);
resolve(5);
});
function func1() {
console.log(1)
}
function func2() {
setTimeout(() => {
console.log(2)
});
func1();
console.log(3);
p.then(resolved => {
console.log(resolved)
}).then(() => {
console.log(6)
});
}
func2();
執(zhí)行
第一步:new Promis壓放入執(zhí)行棧中姐仅,然后執(zhí)行里面的代碼,打印4刻盐,執(zhí)行resolve(5);注意這里跟new普通函數(shù)一樣是正常執(zhí)行的掏膏,不會加入到宏任務中;
第二步:執(zhí)行func2()敦锌,
遇到setTimeout馒疹,將它放入宏任務中;
接著執(zhí)行func1();打印出1
接著console.log(3);打印3
遇到Promise對象執(zhí)行then()時乙墙,這里是異步操作颖变,會將里面回調(diào)函數(shù)放入微任務中,等待執(zhí)行
當執(zhí)行棧被清空時听想,執(zhí)行微任務中的console.log(resolved)腥刹,打印出5,接著再去微任務中找到下個事件汉买,打印出6肛走;
當微任務清空后,再執(zhí)行宏任務,即setTimeout到時間后會答應出2,
所以最后答案為:4 1 3 5 6 2
參考資料:
深入理解定時器系列第一篇——理解setTimeout和setInterval
setTimeout和setInterval的深入理解
UI多線程-深入剖析Js執(zhí)行機制
深入理解JavaScript事件循環(huán)機制
2分鐘了解 JavaScript Event Loop
JavaScript微任務與宏任務朽色、異步邻吞、事件循環(huán)與消息隊列理解
深度剖析JavaScript事件循環(huán)機制(未完善)
為什么javascript是單線程?
JavaScript 運行機制詳解:再談Event Loop—作者:阮一峰
瀏覽器內(nèi)核常駐線程