关于setTimeout

15,093 阅读6分钟
面试官:“你知道定时器吗?”
我:“知道”
面试官:“那你说说什么是定时器”
我:“定时器是可以用setTimeout来实现的”
面试官:“setTimeout(function () { console.log("1") },0);
console.log("2");那你说说控制台上输出顺序是什么?”
我:支支吾吾......

so....就有了这篇文章,唠叨了这么久,正文开始了!

setTimeout的语法功能:

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式

为了解释上面的那句话,来个简单的小例子:

<!DOCTYPE html>
<head>
    <title>setTimeout</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
    <h1>setTimeout</h1>
    <span id="content">测试</span>
    <script>
       setTimeout(function () {
           let content = document.getElementById('content');
           content.innerHTML = "<div>一秒后</div>";
       },1000);
    </script>
</body>
</html>

span标签里面的内容一秒之后由“测试”变成了“一秒后”。

记时器功能:

来实现了:

<!DOCTYPE html>
<head>
    <title>setTimeout</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
    <h1>setTimeout</h1>
    <span id="content">时间</span>
    <button onclick="start()">开始</button>
    <script>
        var x = 00,
            y = 00,
            z = 00;
        function start () {
            if (x<= 59 && x>=0 && y<=59 && y>=0 && z<=59 && z>=0) {
               let content = document.getElementById('content');
               content.innerHTML = z + ":" + y + ":" + x;
               console.log(x);
               x = x + 1;
            } else if (y<=59 && y>=0 && z<=59 && z>=0) {
                y = y + 1;
                x = 0;
            } else if (z<=59 && z>=0){
                z = z + 1;
                x = 0;
                y = 0;
            }
            setTimeout ("start()",1000);  //注意,这里调用要用引号包围
        }
    </script>
</body>
</html>

控制台输出:

这里点击之后执行start()函数,在函数里面setTimeout()函数又调用了start(),所以就是一秒钟调用一次start()函数。

setTimeout()的参数:

大家都知道setInterval()和setTimeout()可以接收两个参数,第一个参数是需要回调的函数,必须传入的参数,第二个参数是时间间隔,毫秒数,可以省略。但其实他可以接收更多的参数,那么这些参数是干什么用的呢?从第三个参数开始,依次用来表示传入回调函数的参数。

例子:

setTimeout(function(a,b){
   console.log(0+a+b);//这里打印的是:7
},1000,3,4);
注意:IE 9.0及以下版本,只允许setTimeout有两个参数,不支持更多的参数

如果想向回调函数传参,可以用bind()。
eg:

setTimeout( function(a,b){}.bind(3,4), 1000 );
那我怎么去除定时器呢(惆怅脸)?别急,别急,我来告诉你!

clearTimeout():

setTimeout函数,返回一个表示计数器编号的整数值,将该整数传入clearTimeout函数,就可以取消对应的定时器。

clearTimout()有以下语法: clearTimeout(timeoutID)
要使用 clearTimeout( ), 我们设定 setTimeout( ) 时, 要给予这 setTimout( ) 一个名称, 这名称就是 timeoutID , 我们叫停时, 就是用这 timeoutID来叫停, 这是一个自定义名称。

用法:

var id1 = setTimeout(f,1000);  //id1就是timeoutID
var id2 = setInterval(f,1000); //id2就是timeoutID

clearTimeout(id1);
clearInterval(id2);

setTimeout()的this指向:

对于javascript中的this指向问题,之前也是困扰了我好久,哎呀,哪儿有那么难嘛,其实一句话就是说:谁调用的就是指向谁啊!意思就是说调用的对象是谁this就是指向谁。

那这样说来个栗子咯:

var x = 1;
var obj = {
  x: 2,
  y: function(){
    console.log(this.x);
  }
};
setTimeout(obj.y,1000);  // 1

why?不是说了哪个对象调用的就是指向哪个对象的嘛,这里不是setTimeout函数调用了obj对象里面的y方法吗,那不还是被setTimeout调用了吗,对啊,没错啊,就是setTimeout调用的,但是setTimeout函数是属于window的,知道吧,所以setTimeout的对象是window,所以一切都明了了。

懂了吧,那就来考考你,准备接招啦!
function Animal(login) {
  this.login = login;
  this.sayHi = function() {
    console.log(this.login);  //undefined
  }
}
var dog = new Animal('John');
setTimeout(dog.sayHi, 1000);

哈哈哈,答对了吧,哇👋,但是没有奖励😂

等到dog.sayHi执行时,它是在全局对象中执行,但是this.login取不到值。

setTimeout()之延迟时间为0

要回答面试官问我的问题了😂。哇,血的教训,来来来 直接一点来栗子吧:

    console.log('a');
    setTimeout(function(){
    console.log('b');
    },0);
    console.log('c');
    console.log('d');

控制台输出:
a
c
d
b
我也不截图了。 知道为什么吗,理论上他延迟时间为0不是应该马上执行吗,不是的。因为setTimeout运行机制说过,必须要等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行setTimeout指定的任务。也就是说,setTimeout的真正作用是,在“任务队列”的现有事件的后面再添加一个事件,规定在指定时间执行某段代码。setTimeout添加的事件,会在下一次Event Loop执行。好吧,对事件循环不清楚的推荐看看阮一峰-avaScript 运行机制详解

到这里setTimeout还有什么没有说的呢,就是setTimeout的执行机制啊

事件循环中的setTimeout():

众所周知,Javascript引擎(以下简称JS引擎)是单线程的,在某一个特定的时间内只能执行一个任务,并阻塞其他任务的执行,也就是说这些任务是串行的。这样的话,用户不得不等待一个耗时的操作完成之后才能进行后面的操作,这显然是不能容忍的,但是实际开发中我们却可以使用异步代码来解决。

当异步方法比如这里的setTimeout(),或者ajax请求、DOM事件执行的时候,会交由浏览器内核的其他模块去管理。当异步的方法满足触发条件后,该模块就会将方法推入到一个任务队列中,当主线程代码执行完毕处于空闲状态的时候,就会去检查任务队列,将队列中第一个任务入栈执行,完毕后继续检查任务队列,如此循环。前提条件是主线程处于空闲状态,这就是事件循环的模型。

正经的讲了一波理论,来个栗子吧:

setTimeout(function () {
    console.log("b");
},0)
console.log("a");

控制台输出:
a
b
原理,就是上面两段话当中解释的,执行时把setTimeout()放入任务队列中去,主线程执行完主线程的任务之后去任务队列里面执行setTimeout出来执行。

一样的道理:

setTimeout(function(){
  console.log(1111);
},0)
while (true) {};

这里控制台是永远不会输出东西的,因为主线程已经造成了死循环,主线程一直是不会空闲的,他不会到任务队列里面去执行拿setTimeout函数来执行。
推荐一篇文章,更好的理解:彻底理解setTimeout()

总结:

说了这么多,setTimeout是不是很强大,啊哈哈。但是如不能熟练掌握,不建议多用。毕竟在某些情景之下毕竟在某些情景之下,setTimeout作为一个hack的方式而存在的。

到这里,首先,恭喜你,应该是收获满满了。其次可以点个赞😜