基本使用
setTimeout( function|string, number );
setTimeout 方法接收兩個(gè)參數(shù)泞歉,第一個(gè)參數(shù)為回調(diào)函數(shù)函數(shù)或字符串,第二個(gè)參數(shù)為觸發(fā)時(shí)間(單位:毫秒)
setTimeout( function() {
console.log('console after 1 second');
}, 1000 );
// console after 1 second
上面這段代碼將會(huì)在 1 秒后在控制臺(tái)打印出 console after 1 second
setTimeout(
'console.log("console after 2 seconds")',
2000
);
// console after 2 seconds
setTimeout的方式(注冊(cè)事件):有兩個(gè)參數(shù)映胁,第一個(gè)參數(shù)是函數(shù)阅悍,第二參數(shù)是時(shí)間值附鸽。調(diào)用setTimeout時(shí),把函數(shù)參數(shù)净宵,放到事件隊(duì)列中敲才。等主程序運(yùn)行完裹纳,再調(diào)用。
當(dāng)?shù)谝粋€(gè)參數(shù)為字符串紧武,而不是函數(shù)時(shí)會(huì)怎樣呢剃氧?setTimeout 方法會(huì)將這個(gè)字符串解析為一段 js 代碼,然后在 2 秒后執(zhí)行這段代碼阻星。如果這個(gè)字符串無法被解析為 js 代碼朋鞍,將會(huì)報(bào)錯(cuò)
setTimeout 的返回值
var timeout = setTimeout(function() {
console.log('this is a timeout')
}, 1000);
console.log(timeout, typeof timeout);
// 1, "number"
// this is a timeout
用變量 timeout 接收 setTimeout() 的返回值,然后將 timeout 打印出來妥箕,會(huì)發(fā)現(xiàn) timeout 的值是 1滥酥,類型是number
為什么 setTimeout() 的返回值會(huì)是一個(gè)數(shù)值類型呢?是不是每一個(gè) setTimeout() 的返回值都會(huì)是1畦幢?
var timeout1 = setTimeout(function() {
//
}, 1000);
var timeout2 = setTimeout(function() {
//
}, 1000);
var timeout3 = setTimeout(function() {
//
}, 1000);
console.log('timeout1---', timeout1);
console.log('timeout2---', timeout2);
console.log('timeout3---', timeout3);
// timeout1--- 1
// timeout2--- 2
// timeout3--- 3
從上面的代碼中能夠發(fā)現(xiàn)坎吻,每一個(gè) setTimeout()的返回值都不同,返回值并不都是1宇葱,而是都對(duì)應(yīng)著唯一的一個(gè)值
這個(gè)值其實(shí)就是對(duì)應(yīng)的setTimtout()的 ID瘦真,隨著當(dāng)前頁面定時(shí)器的不斷增多,當(dāng)需要對(duì)某一個(gè)定時(shí)器做操作時(shí)黍瞧,通過 ID 就能夠確定到該定時(shí)器
如何結(jié)束/阻止一個(gè) setTimeout 的執(zhí)行
實(shí)際項(xiàng)目中诸尽,添加一個(gè)定時(shí)器以后,在其回調(diào)函數(shù)還未執(zhí)行之前印颤,滿足某些條件時(shí)可能需要阻止該回調(diào)的執(zhí)行您机,也就是取消一個(gè)定時(shí)器,那這時(shí)候該怎么做呢年局?javascript 已經(jīng)為我們提供了現(xiàn)成的方法:
clearTimeout( timeout );
上面這個(gè)方法就可以阻止一個(gè)定時(shí)器的執(zhí)行际看,它接收一個(gè)參數(shù),這個(gè)參數(shù)就是需要取消的定時(shí)器的 ID某宪,也就是該定時(shí)器的返回值
var timeout = setTimeout(function() {
alert('this is a timeout')
}, 1000);
clearTimeout( timeout );
上面代碼中的定時(shí)器timeout在一秒后并不會(huì)執(zhí)行仿村,因?yàn)橐呀?jīng)通過clearTimeout( timeout )取消了它的執(zhí)行
因?yàn)閠imeout的值是1锐朴,所以clearTimeout( 1 )也能夠取消這個(gè)定時(shí)器的執(zhí)行
實(shí)現(xiàn)異步編程
在之前的文章中已經(jīng)講過異步編程的概念兴喂,我們使用異步編程很重要的一個(gè)目的就是為了不因?yàn)楹臅r(shí)任務(wù)而阻塞其他 js 代碼的執(zhí)行
我們知道alert會(huì)阻塞 js 代碼的執(zhí)行,這是因?yàn)?js 是單線程的焚志,彈出框出現(xiàn)后如果不對(duì)其進(jìn)行操作就無法執(zhí)行后面的代碼(類似的confirm也是)
alert('this is an alert box');
var test = 'this is a text string';
console.log(test);
上面的代碼在彈出框出現(xiàn)后如果不點(diǎn)擊確定衣迷,將永遠(yuǎn)不會(huì)執(zhí)行后面的代碼
setTimeout(function() {
alert('this is an alert box');
}, 1000);
var test = 'this is a text string';
console.log(test);
// this is a text string
上面的代碼將alert放在了一個(gè)延時(shí) 1 秒的定時(shí)器中,這樣就會(huì)先打印出test酱酬,過一秒后再顯示彈出框
或許你會(huì)說壶谒,本身alert就延時(shí)了 1 秒執(zhí)行,當(dāng)然不會(huì)阻塞其他的代碼執(zhí)行膳沽。那么你可以試著將延時(shí)1000改為0汗菜,這就表示彈出框應(yīng)該是沒有延時(shí)立即執(zhí)行让禀,但是你會(huì)發(fā)現(xiàn)實(shí)際上還是先打印出test,再執(zhí)行了alert陨界。為什么會(huì)這樣呢巡揍?我們下面再說
在實(shí)際項(xiàng)目中,我們可以利用setTimeout的異步特性菌瘪,解決一些問題腮敌,比如某個(gè)對(duì)象還未實(shí)例化,為了保證該對(duì)象在使用到時(shí)能夠確保已經(jīng)被實(shí)例化俏扩,就可以通過setTimeout來實(shí)現(xiàn)
setTimeout 回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)
現(xiàn)在我們來說說為什么延時(shí)設(shè)為 0 糜工,回調(diào)函數(shù)卻沒有立即執(zhí)行的問題
我們知道瀏覽器是基于事件循環(huán)的,其中會(huì)有多個(gè)隊(duì)列录淡,頁面的渲染是一個(gè)隊(duì)列捌木,js 代碼的執(zhí)行也是一個(gè)隊(duì)列
js 代碼執(zhí)行時(shí)會(huì)創(chuàng)建一系列的任務(wù),而這些任務(wù)秉承著先進(jìn)先出的原則被加入到隊(duì)列中嫉戚。但是setTimeout是特殊的钮莲,當(dāng)執(zhí)行到setTimeout時(shí),js會(huì)將其拿出來放到一個(gè)單獨(dú)的特殊隊(duì)列中彼水,這個(gè)隊(duì)列中的任務(wù)在 js 隊(duì)列還有未執(zhí)行完的任務(wù)時(shí)崔拥,永遠(yuǎn)不會(huì)被執(zhí)行
舉個(gè)不恰當(dāng)?shù)睦踝樱∶飨胪嬗螒蚍锔玻亲鳂I(yè)還沒做完链瓦,由于小明是單線程的,雖然現(xiàn)在很想玩游戲盯桦,但是他還是給自己設(shè)定了條件:作業(yè)不做完不能玩游戲慈俯,一做完立刻玩游戲(延時(shí)為0),于是玩游戲這個(gè)任務(wù)就被小明歸置到了一個(gè)特殊的任務(wù)隊(duì)列里面拥峦,在作業(yè)隊(duì)列所以任務(wù)完成之前不執(zhí)行特殊隊(duì)列里面玩游戲的任務(wù)贴膘,作業(yè)完成小明閑下來后立刻開始玩游戲(似乎有點(diǎn)啰嗦,但這不重要:)
所以只有瀏覽器的 js 引擎閑下來以后略号,才會(huì)執(zhí)行所有 setTimeout刑峡,即使延時(shí)為 0
var flag = true;
setTimeout(function() {
flag = false;
}, 1000);
while(flag) {}
alert('this is an alert box');
問:上面的代碼什么時(shí)候會(huì)顯示彈出框?
答:永遠(yuǎn)都不會(huì)
上面的代碼中玄柠,while是一個(gè)耗時(shí)函數(shù)突梦,雖然setTimeout只延時(shí)了一秒執(zhí)行,但是由于主隊(duì)列中的while會(huì)永遠(yuǎn)的執(zhí)行下去羽利,所以setTimeout所在的隊(duì)列永遠(yuǎn)不會(huì)被執(zhí)行宫患,代碼會(huì)永遠(yuǎn)阻塞在while循環(huán)這邊
當(dāng)然,上面的這種無限循環(huán)在項(xiàng)目中不可能出現(xiàn)这弧,而代碼執(zhí)行速度極快娃闲,只要不出現(xiàn)十分耗時(shí)的代碼虚汛,定時(shí)器幾乎還是能夠按照我們的意愿在指定時(shí)間執(zhí)行回調(diào)函數(shù)
通過 setTimeout 優(yōu)化用戶體驗(yàn)
既然setTimeout必須等到主隊(duì)列中的任務(wù)執(zhí)行完以后才會(huì)執(zhí)行,那我們?cè)谂龅揭恍┦趾臅r(shí)的代碼時(shí)皇帮,是不是可以通過它來放在頁面的阻塞呢泽疆?
當(dāng)然是可以的,將耗時(shí)代碼寫進(jìn)setTimeout的回調(diào)玲献,時(shí)間設(shè)置為 0殉疼,這樣只要 js 引擎空閑下來就回去執(zhí)行這些耗時(shí)代碼,就不會(huì)阻塞頁面捌年,給用戶造成卡頓的體驗(yàn)瓢娜,提升用戶體驗(yàn)
不易察覺的危險(xiǎn)——內(nèi)存泄漏
什么是內(nèi)存泄漏?
一塊內(nèi)存在分配使用完畢以后礼预,既不會(huì)被再次使用眠砾,又沒有被及時(shí)回收,直到程序執(zhí)行完畢都始終占據(jù)著這塊內(nèi)存
setTimeout 什么情況會(huì)導(dǎo)致內(nèi)存泄漏托酸?
setTimeout的第一個(gè)參數(shù)可以是函數(shù)褒颈,也可以是字符串。當(dāng)傳入字符串時(shí)励堡,就會(huì)有內(nèi)存泄漏產(chǎn)生谷丸。先看下面兩個(gè)例子
setTimeout(function test1() {
var a = 1;
console.log(a);
}, 0)
setTimeout((function test2() {
var b = 1;
console.log(b);
}).toString(), 0)
// 1
執(zhí)行代碼后,打開控制臺(tái)应结,分別輸入函數(shù)名test1和test2
test1
// Uncaught ReferenceError: test1 is not defined
test2
// ... (打印出 test2 的函數(shù)體)
會(huì)發(fā)現(xiàn)刨疼,當(dāng)?shù)谝粋€(gè)參數(shù)為函數(shù)時(shí),回調(diào)函數(shù)執(zhí)行完畢后鹅龄,test1函數(shù)被銷毀揩慕,其所使用內(nèi)存也被釋放;當(dāng)?shù)谝粋€(gè)參數(shù)為字符串時(shí)扮休,test2卻始終存在迎卤,它沒有被銷毀,始終占據(jù)著內(nèi)存玷坠,也就造成了內(nèi)存泄漏
所以讓我們需要使用 setTimeout時(shí)蜗搔,一定要注意,第一個(gè)參數(shù)必須傳入一個(gè)函數(shù)