热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

面试:JavaScript中的setTimeout到底是什么?

本篇文章给大家带来的内容是关于面试:JavaScript中的setTimeout到底是什么?,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
本篇文章给大家带来的内容是关于面试:Javascript中的setTimeout到底是什么?,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

在刷笔试题的时候,经常会碰到setTimeout的问题,只知道这个是设置定时器;但是考察的重点一般是在一个方法中包含了定时器,定时器中的打印和方法中打印的执行顺序问题,也许我说的有点儿难懂,下面就来看看setTimeout到底是什么吧!

定时器的介绍

js中有哪些定时器?

周期定时器:setInterval()

介绍

setInterval()是按照指定的周期来调用定时器,方法会不断的调用定时器,直到使用clearInterval()停止或者窗口关闭。

语法

setInterval(code,millisec,lang)

  • code:要执行的方法体(必选)

  • millisec:每隔多少毫秒执行一次(单位是毫秒,如果设置为5000,即每5秒执行一次)(必选)

  • lang:指使用的语言(可选)

实例

通过setInterval实现时钟效果








效果图:

‘一次性’定时器:setTimeout()

介绍

  顾名思义,这个定时器只会执行一次,和setInterval()的区别就在这儿了,正是因为如此,setInterval()才需要使用clearInterval方法去取消定时器

语法

  setTimeout(code,millisec,lang)    ps:每个参数的含义和setInterval()的均相同

实例

  点击按钮3秒后弹出“Hello”








点击按钮,在等待 3 秒后弹出 "Hello"。

效果图:

取消定时器

介绍

  使用计时器ID来取消计时器回调的发生,每个计时器都会返回一个id,是为了取消定时器的方法可以获取到相应的计数器。

  • clearInterval(id)

  • clearTimeout(id)

实例
//设置超时调用
var timeoutId = setTimeout(function (){
    alert("hello World");
    },1000);
//取消掉用的代码
clearTimeout(timeoutId);

setTimeout的执行顺序到底是怎样的?

  我们都知道,js是单线程语言,所有的多线程都是假象,都是单线程模拟出来的。浏览器是多进程的,而浏览器的内核(渲染进程)是多线程的。
  渲染进程中有一个js引擎线程,这个线程是用来处理Javascript脚本的(例如chrome的V8引擎),而我们一直说的Javascript是单线程的就是因为这个。
  那么问题来了,既然js是单线程的,那setTimeout的异步是怎么实现的呢?js在解析脚本的时候,会将任务分为两大类,同步任务和异步任务,它们在解析时会进入不同的场所执行。

  • 同步任务:会进入主线程的执行栈,也就是js引擎线程管理的地方,按照顺序执行

  • 异步任务:进入Event Table中,并注册函数,当回调函数的条件满足时,就会将回调函数放进Event Queue中,也就是任务队列中。

  当主线程中的任务执行完毕后,也就是执行栈为空时,就会去任务队列中看有没有事件,如果有的话,就进入主线程执行,一直这样循环下去,这就是事件循环机制了,可以参照下面的图理解一下:

  也许你对事件循环机制的过程还是不太明白,那么我再解释清楚一点。例如下面这个例子:

   console.log('start')
   setTimeout(function(){
     console.log('setTimeout')
   },5000)
   console.log('end')

执行过程:

  • 开始解析,遇到console.log,是同步任务,进入主线程,直接执行,打印start

  • 往下走,遇到setTimeout,是异步任务,进入Event Table,并注册回调函数;

  • 再往下走,遇到console.log,直接执行,打印end

  • 5s后,将回调函数放进Event Queue,此时执行栈刚好为空,主线程会去任务队列中取出这个回调函数,执行,打印setTimeout

  ps:

  • 第1,3步都是js引擎线程干的事情,主线程执行任务;

  • 第2步是渲染进程中的事件触发线程(专门管理任务队列的)管理;

  • 第4步是定时器线程控制的(也就是setTiemout和setInterval所在的进程),定时器线程专门用来控制什么时候将回调函数放进任务队列。

  如果看懂了上面的例子,就知道其实setTimeout的第二个参数其实并不能准确的控制多少秒后执行里面的函数,而是控制多少秒后将这个函数放进任务队列中;这样也就同样可以解释,为什么有时候明明设置的是2秒之后执行,却要等不止2秒(因为很有可能定时线程将回调函数放进任务队列后,主线程还在执行执行栈中的任务,需要执行栈中的任务全部执行完后才会去任务队列中取任务)。
  这样就会引发一个问题,我们知道setInterval是隔一定的时间执行一次,现在理解了原理后,就知道其实是隔一定的时间定时器线程将回调函数放进任务队列中。如果已经将回调函数放进任务队列,但是主线程正在执行一个非常耗时的任务,当这个任务执行完毕后,主线程去任务队列中取任务,这个时候,就会出现连续执行的情况,也就是说setInterval相当于失效了。

setTimeout基础篇

  这一部分主要是针对在事件循环机制中setTimeout调顺序进行举例子,如果能够轻松的将例子看懂,就说明你是真的懂了事件循环机制的一部分,为什么说是一部分呢,因为还有一个宏任务和微任务的知识点还没有涉及到,后面的进阶篇就会涉及到啦!

例1

  console.log('start')
  setTimeout(function(){
    console.log('setTimeout')
  },0)
  console.log('end')

打印结果:(如果前面看懂了的同学应该就会明白为什么)

分析:其实和上面那个例子时一样的,只是这个0会给我们一种会立即执行的假象,这个0是说明定时器线程会立即将回调函数放进任务队列而已,主线程还是会将执行栈中的两个同步任务执行完成后再去任务队列中取任务,所以执行顺序和这里的秒数无关。而且即使执行栈为空,也不会0秒就执行,因为HTML的标准规定,setTimeout不超过4ms按照4ms来计算。

例2

  console.log('start')
  setTimeout(function(){
    console.log('setTimeout')
  }(),0)
  console.log('end')

打印结果:(仔细对比与例1的区别)

分析:细心的同学会发现,我将回调函数改成了立即执行函数,就改变了执行的顺序。首先我们需要明确的是setTimeout的第一个参数是指函数的返回值,这里回调函数为立即执行函数时,返回值就是undefined了,所以会直接执行立即执行函数,也就是立即打印setTimeout,而真正的setTimeout函数就相当于没起作用。

例3

setTimeout(() => {
   console.log('setTimeout')
},3000)

sleep(10000000)//伪代码,表示这个函数要执行很久很久

打印结果:
这个结果不说也知道,肯定会打印出setTimeout的,但是重点却不在这儿~
重点在于,这个setTimeout是隔很久很久打印出来的,远远超过了3秒,这个例子也是很明确的体现了js的事件循环机制。

setTimeout进阶篇

  这一部分相对于基础篇,加上了作用域以及其他也是比较难以理解的东西,可能还需要补充一些其他知识才会明白,我会尽量讲清楚,也会把我看的参考文章放在下面。
  受到一篇文章的启发,我们以循序渐进的方式来阐述

难度:O

问题:以下代码输出的是什么?

for(var i = 0;i <5;i++){
    console.log(i)
  }

答案:没错,你没有看错,就是一个简单的循环,就像你想的那样,连续输出0,1,2,3,4

难度:OO

问题:以下代码输出的是什么?如果把时间改为1000*i输出的又是什么?

for(var i = 0;i <5;i++){
    setTimeout(function(){
      console.log(i)
    },1000)
  }

答案:
  时间为1000时,1秒后会连续输出5个5;时间为1000*i时,会每隔一秒输出一个5,一共5个5
分析:
  由上面的事件循环机制我们知道,setTimeout是异步事件,会放在事件队列中等着主线程来执行,这个时候for循环中的i已经变成了5,由于定时器线程是在1秒后直接将5个setTimeout事件放进事件队列中,所以主线程在执行的时候就没有间隔了;当时间乘上一个i时,定时器会隔1秒将setTimeout事件放入队列,就会出现每隔一秒输出一个5的情况。

难度:OOO

问题:如果想输出0,1,2,3,4应该怎么改?
分析:
  出现上一题的情况主要是因为在setTimeout的回调函数中并没有保存每次循环i的值,最后执行的时候,得到的i就是最后更新的i了(即为5),所以要解决这个问题,思路是要在回调函数中保存每次for循环中的i值。

解决方案1:使用es6中let代替var
分析:let是es6中新增的内容,作用和var一样,都是用来定义变量,但是最大的差别就是let会形成块级作用域,在本例中,就是每次循环,都会产生一个作用域,在该作用域中的变量是一个固定值,下次i变化时不会对这个i产生影响,也就是达到了我们的目标。

for(let i = 0;i <5;i++){
    setTimeout(function(){
      console.log(i)
    },1000*i)
  }

解决方案2:使用闭包
分析:就是直接在setTimeout函数的外面套一层立即执行函数,并将i值作为参数传到匿名函数中(这里的匿名函数也可以是命名函数),然后由于setTimeout中回调函数用到了匿名函数中的i,就会形成闭包。

for(var i = 0;i <5;i++){
    (function(i){
      setTimeout(function(){
        console.log(i)
      }, 1000 * i)
    }) (i) 
  }

延伸:将代码变成下面这样会输出什么?(去掉匿名函数中的i)
分析:这里会输出5个5,也就是闭包没有起作用,根本原因是i并没有传进去,打印的还是最后的i

for (var i = 0; i <5; i++) {
      (function () {
        setTimeout(function () {
          console.log(i)
        }, i * 1000)
      })(i);
    }

解决方案3:将回调函数改成立即执行函数
分析:这个解决方案其实不是太好,如果要求是每隔1秒输出一个数字,这个方法就不适用了;这个方法会立马输出0,1,2,3,4,原因结合基础篇应该就明白了

for (var i = 0; i <5; i++) {
        setTimeout((function (i){
          console.log(i);
        })(i), i * 1000)
    }

难度:OOOO

  这一部分会涉及到promise,事件循环机制,宏任务和微任务的内容,算是比较难的部分了,如果觉得比较难看懂,最好先去补一下基础知识,我这里就简单介绍一下。

promise对象

我这里就不详细讲了。

宏任务和微任务

  • 宏任务:可以理解成将代码块走一遍的过程,setTimeout和promise都是宏任务,现在不理解没关系,后面会通过例子帮助理解

  • 微任务:是在宏任务执行完成之后执行的,也是有相应的微任务队列存放微任务,比如promise中的then就是微任务

问题:以下代码输出的是什么?

    setTimeout(function () {
      console.log(1)
    }, 0);
    new Promise(function executor(resolve) {
      console.log(2);
      for (var i = 0; i <10000; i++) {
        i == 9999 && resolve();
      }
      console.log(3);
    }).then(function () {
      console.log(4);
    });
    console.log(5);

答案:(是不是很懵,为什么会是这样,下面看我的分析你就知道了)

  这些词都是用来描述事件的,只是从不同的角度来描述,就像是胖子矮子与男生女生之间的联系

以上就是面试:Javascript中的setTimeout到底是什么?的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • ECMA262规定typeof操作符的返回值和instanceof的使用方法
    本文介绍了ECMA262规定的typeof操作符对不同类型的变量的返回值,以及instanceof操作符的使用方法。同时还提到了在不同浏览器中对正则表达式应用typeof操作符的返回值的差异。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • Javascript中带有加号 - 减号(±)的极坐标曲线方程 - Polar curve equation with plus-minus sign (±) in Javascript
    IamtryingtodrawpolarcurvesonHTMLcanvasusingJavascript.WhatshouldIdowhenIwanttoco ... [详细]
  • 本文介绍了使用Word和Chrome翻译PDF文件的步骤,包括用Word打开PDF、另存为HTML文件以及用Chrome打开HTML并进行翻译的方法。通过这些步骤,您可以方便地将PDF文件翻译成其他语言。详细的操作步骤将在本文中进行说明。 ... [详细]
  • 2016 linux发行版排行_灵越7590 安装 linux (manjarognome)
    RT之前做了一次灵越7590黑苹果炒作业的文章,希望能够分享给更多不想折腾的人。kawauso:教你如何给灵越7590黑苹果抄作业​zhuanlan.z ... [详细]
author-avatar
廖小廖童鞋
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有