美文网首页Js前端开发Web前端之路
前端面试题——深入探究 setTimeout

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

作者: ac68882199a1 | 来源:发表于2017-03-19 00:03 被阅读1200次

我们经常会碰到一些需要定时执行的需求,比如轮播图、延迟消失等,这时候我们就会用到 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会导致程序的生命周期混乱,虽然会带来一时的便利,但它也会带来很多意想不到的麻烦

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

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

扫一扫关注微信公众号

相关文章

网友评论

  • 水念:你好,博主。我把代码:
    ```
    var flag = true;
    setTimeout(function() {
    flag = false;
    }, 1000);
    while(flag) {}
    alert('this is an alert box');
    ```
    复制到 jsBin 中,确实能够弹出 alert('this is an alert box');
    示例代码移步这里: https://jsbin.com/bogumigimi/edit?html,js,console,output

    ---
    另外,今天我面试的时候也遇到过类似的问题,代码是这样的:
    ```
    var flag = true;

    setTimeout(function () {
    console.log('coming...');
    flag = false;
    }, 0);

    var index = 1;
    while (flag) {
    console.log(index ++);
    }
    ```
    确实终止循环了,求博主解释一波。
    水念:@君未来我已老 好。用的 chrome 60+ 版本, win7 系统。博主,你可以在 jsbin 中你试一下。
    ac68882199a1:@水念 因为偷懒,我把上面的两段代码直接粘贴到了控制台,它们先后导致当前窗口无反应且无法关闭、整个浏览器无响应。这样看来,从理论和实际上来说,这两段代码在现代浏览器中都会造成阻塞。所以能否把你当时代码的执行环境告知一下?或者评论中出现的只是一段代码,那可能需要你提供下完整的代码。如果觉得评论不方便沟通,也可以添加我的微信/QQ:1045132927.感谢提出你的疑问~~~
  • 时修七年:setTimeout(function test1() {
    var a = 1;
    console.log(a);
    }, 0)

    function test2() {
    var b = 1;
    console.log(b);
    }

    setTimeout(test2.toString(), 0)

    // 1

    test2被定义在全局,怎么会销毁,是我理解错了嘛
    ac68882199a1:@夏小北666 2 是定义在全局下的一个函数,他的销毁和 setTimeout 没关系。这里的确是有点问题,很容易就会误导读者,应该把 test2 定义在 setTimeout 中再 toString
    已经更正,感谢指出问题哦!是我太粗心了:smile:
    时修七年:@夏小北666 2不消恢和setTimeout有关系嘛?
    ac68882199a1:@夏小北666 :smile::smile::smile:你是不是看错啦?文中写的是 1 会被销毁,2并不会被销毁的哦
  • 蛋黄肉:终于理解了为什么循环体里的setTimeout中的console.log会输出循环结束之后的i了,感谢,另外您的脾气真好。
    ac68882199a1:@黄杏 感谢支持,你是不是看评论了才说我脾气好🤣🤣🤣欢迎以后继续关注哦
  • 4e5f67d086be:很详细 不错😊
    ac68882199a1:@逝水流光 感谢支持,欢迎继续关注哦
  • 83a45ad3f1f3:既然会带来麻烦,让人尽量不要使用,你还拿来写文章。这不是在说一本邪书,劝大家不要看,可是你却还时不时拿出来在大家面前晃悠,一边晃悠还一边说:“千万不要看,这是本邪书,看了会万劫不复的。”
    83a45ad3f1f3: @_流浪人 我本来就不知道有那个东西,是你把那个有毒的东西拿到我面前的
    ac68882199a1:@_流浪人 而且邪书中的理论本就是错误的,自然不能宣扬;而我这篇文章却是基于事实,为了让不了解的小伙伴更了解它。一个东西必有两面性,难道因为它不好而直接否定它的存在?我想这才是更为“邪”的东西吧
    ac68882199a1:@_流浪人 就好像有个人跟你说那个东西不能吃,却不告诉你原因,你一定会很好奇好奇到忍不住去尝,尝过以后才知道那东西有毒所以不能吃。那还不如在告诉你不能吃的同时也告诉你为什么不能吃,这样你才会彻底不动吃它的念头。

    知其然更要知其所以然,我告诉你要少用,更要告诉你为什么要少用。而且这篇文章是面试题的探究,难道面试官问到定时器的时候,你就说反正少用就行了?他问你为什么要少用,而你回答他,别人只说要少用没告诉为什么,你不觉得这很可笑么?

    不确定你是不是在挑刺,我只是分享出我所知道的和总结的,你可以说我有地方写错了写的不好批评我给我建议,我都很乐意接受,但请不要评论我写这篇文章的立意,OK?学习任何东西都不要只知其结论而不去深究其原理,OK?
  • 27b4a30242ce:感谢分享,学到了🐳
    ac68882199a1:@oceanaly 感谢你的回复啊!共同进步!欢迎继续关注

本文标题:前端面试题——深入探究 setTimeout

本文链接:https://www.haomeiwen.com/subject/idapnttx.html