前端面试题——深入探究 setTimeout

我们经常会碰到一些需要定时执行的需求,比如轮播图、延迟消失等,这时候我们就会用到 setTimeout 这个方法了,设定一个回调函数和等候执行的时间,就可以实现定时或延时的需求

但是 setTimeout 的功能并不只是这么简单,同时使用它时,需要注意的问题也并不少,下面就来深入探究一下 setTimeout

基本使用

setTimeout( function|string, number );

setTimeout 方法接收两个参数,第一个参数为回调函数函数或字符串,第二个参数为触发时间(单位:毫秒)

setTimeout( function() {
    console.log('console after 1 second');
}, 1000 );
// console after 1 second

上面这段代码将会在 1 秒后在控制台打印出 console after 1 second

setTimeout( 
    'console.log("console after 2 seconds")',
    2000 
);
// console after 2 seconds

当第一个参数为字符串,而不是函数时会怎样呢?setTimeout 方法会将这个字符串解析为一段 js 代码,然后在 2 秒后执行这段代码。如果这个字符串无法被解析为 js 代码,将会报错

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 打印出来,会发现 timeout 的值是 1,类型是number

为什么 setTimeout() 的返回值会是一个数值类型呢?是不是每一个 setTimeout() 的返回值都会是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

从上面的代码中能够发现,每一个 setTimeout()的返回值都不同,返回值并不都是1,而是都对应着唯一的一个值

这个值其实就是对应的setTimtout()的 ID,随着当前页面定时器的不断增多,当需要对某一个定时器做操作时,通过 ID 就能够确定到该定时器

如何结束/阻止一个 setTimeout 的执行

实际项目中,添加一个定时器以后,在其回调函数还未执行之前,满足某些条件时可能需要阻止该回调的执行,也就是取消一个定时器,那这时候该怎么做呢?javascript 已经为我们提供了现成的方法:

clearTimeout( timeout );

上面这个方法就可以阻止一个定时器的执行,它接收一个参数,这个参数就是需要取消的定时器的 ID,也就是该定时器的返回值

var timeout = setTimeout(function() {
    alert('this is a timeout')
}, 1000);

clearTimeout( timeout );

上面代码中的定时器timeout在一秒后并不会执行,因为已经通过clearTimeout( timeout )取消了它的执行

因为timeout的值是1,所以clearTimeout( 1 )也能够取消这个定时器的执行

实现异步编程

在之前的文章中已经讲过异步编程的概念,我们使用异步编程很重要的一个目的就是为了不因为耗时任务而阻塞其他 js 代码的执行

我们知道alert会阻塞 js 代码的执行,这是因为 js 是单线程的,弹出框出现后如果不对其进行操作就无法执行后面的代码(类似的confirm也是)

alert('this is an alert box');
var test = 'this is a text string';
console.log(test);

上面的代码在弹出框出现后如果不点击确定,将永远不会执行后面的代码

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放在了一个延时 1 秒的定时器中,这样就会先打印出test,过一秒后再显示弹出框

或许你会说,本身alert就延时了 1 秒执行,当然不会阻塞其他的代码执行。那么你可以试着将延时1000改为0,这就表示弹出框应该是没有延时立即执行,但是你会发现实际上还是先打印出test,再执行了alert。为什么会这样呢?我们下面再说

在实际项目中,我们可以利用setTimeout的异步特性,解决一些问题,比如某个对象还未实例化,为了保证该对象在使用到时能够确保已经被实例化,就可以通过setTimeout来实现

setTimeout 回调函数的执行时机

现在我们来说说为什么延时设为 0 ,回调函数却没有立即执行的问题

我们知道浏览器是基于事件循环的,其中会有多个队列,页面的渲染是一个队列,js 代码的执行也是一个队列

js 代码执行时会创建一系列的任务,而这些任务秉承着先进先出的原则被加入到队列中。但是setTimeout是特殊的,当执行到setTimeout时,js会将其拿出来放到一个单独的特殊队列中,这个队列中的任务在 js 队列还有未执行完的任务时,永远不会被执行

举个不恰当的栗子,小明想玩游戏,但是作业还没做完,由于小明是单线程的,虽然现在很想玩游戏,但是他还是给自己设定了条件:作业不做完不能玩游戏,一做完立刻玩游戏(延时为0),于是玩游戏这个任务就被小明归置到了一个特殊的任务队列里面,在作业队列所以任务完成之前不执行特殊队列里面玩游戏的任务,作业完成小明闲下来后立刻开始玩游戏(似乎有点啰嗦,但这不重要:)

所以只有浏览器的 js 引擎闲下来以后,才会执行所有 setTimeout,即使延时为 0

var flag = true;
setTimeout(function() {
    flag = false;
}, 1000);
while(flag) {}
alert('this is an alert box');

问:上面的代码什么时候会显示弹出框?

答:永远都不会

上面的代码中,while是一个耗时函数,虽然setTimeout只延时了一秒执行,但是由于主队列中的while会永远的执行下去,所以setTimeout所在的队列永远不会被执行,代码会永远阻塞在while循环这边

当然,上面的这种无限循环在项目中不可能出现,而代码执行速度极快,只要不出现十分耗时的代码,定时器几乎还是能够按照我们的意愿在指定时间执行回调函数

通过 setTimeout 优化用户体验

既然setTimeout必须等到主队列中的任务执行完以后才会执行,那我们在碰到一些十分耗时的代码时,是不是可以通过它来放在页面的阻塞呢?

当然是可以的,将耗时代码写进setTimeout的回调,时间设置为 0,这样只要 js 引擎空闲下来就回去执行这些耗时代码,就不会阻塞页面,给用户造成卡顿的体验,提升用户体验

不易察觉的危险——内存泄漏

什么是内存泄漏?

一块内存在分配使用完毕以后,既不会被再次使用,又没有被及时回收,直到程序执行完毕都始终占据着这块内存

setTimeout 什么情况会导致内存泄漏?

setTimeout的第一个参数可以是函数,也可以是字符串。当传入字符串时,就会有内存泄漏产生。先看下面两个例子

setTimeout(function test1() {
    var a = 1;
    console.log(a);
}, 0)

setTimeout((function test2() {
    var b = 1;
    console.log(b);
}).toString(), 0)

// 1

执行代码后,打开控制台,分别输入函数名test1test2

test1
// Uncaught ReferenceError: test1 is not defined

test2
// ... (打印出 test2 的函数体)

会发现,当第一个参数为函数时,回调函数执行完毕后,test1函数被销毁,其所使用内存也被释放;当第一个参数为字符串时,test2却始终存在,它没有被销毁,始终占据着内存,也就造成了内存泄漏

所以让我们需要使用 setTimeout时,一定要注意,第一个参数必须传入一个函数

不要在项目中频繁大量的使用

上面说了那么多关于 setTimeout的内容,但最后还是要说一下的时,能不使用就不要使用setTimeout,异步编程实现的方式有很多,Promise 和 Generator 都能够实现,而频繁的使用 setTimeout会导致程序的生命周期混乱,虽然会带来一时的便利,但它也会带来很多意想不到的麻烦

所以,除非不得不使用定时器,否则就不要使用

文章中如有任何错误,欢迎指出,虚心受教感激不尽

扫一扫关注微信公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容