探索setTimeout

其实说起JavaScript中的定时器(Timer)中的 setTimeout() 方法,从事开发的同学想必都不会陌生,觉得这些东西很简单很基础。但是有时候恰恰是基础简单的东西,才越容易被忽略。先看一段代码:

console.log("start");

setTimeout(function(){
    console.log("world")
},200);

setTimeout(function(){
    console.log("Hello ")
},100);

console.log("end");

上面这段代码大家都知道会输出什么,结果如下:

下面我们稍微改动一下代码,在中间添加一个循环,如下:

console.log("start");

setTimeout(function(){
    console.log("world")
},200);

for(let i = 0; i<1000; i++){
    console.log("我就是来耗时间的!")
}

setTimeout(function(){
    console.log("Hello ")
},100);

这次的结果有点意外,延迟200毫秒的”world”先输出来了,结果如下:

这就有点奇怪了,js代码是怎么执行的呢?

JS代码是怎么执行的

那首先应该从JS代码执行说起,JS引擎会在内存中分配堆区(heap)和栈区(stack),那么JS又是以怎样的顺序执行的呢?先看一段很简单的代码:

function A(){
    var a = 3;
    B(3);
}
function B(num){
    var newNum = num * num;
    console.log(newNum);
}

当代码运行的时候,为便于理解我们假设栈底有一个main()方法(类似于Java中的入口Main方法)作为运行的开始。

1.代码开始执行:

2.运行A()方法,将A()入栈,此时A上下文中存在变量 a = 3

3.调用B()方法,将B()入栈,此时B上下文中存在变量 num = 3,newNum = 9

4.调用B()方法中的console.log(),入栈,控制台打印 9

5.继续运行,依次将console.log(),和B()方法出栈

6.A()方法运行完毕出栈

7.代码运行完成,清空栈

这就是一段简单的代码在堆栈中的执行情况,看明白了就能开始介绍Javascript引擎的另外一个机制。

Event Loop

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

举个特殊栗子——计算机CPU,我们可以听着音乐的同时愉快的码代码,看起来播放音乐和编辑代码是并行的,其实不然。在计算机中并没有绝对意义上的并行,从微观上来看,单核心的CPU其实在同一个时间片内只能处理单一的任务,一旦某个进程的时间片结束,CPU会马上调度另一个进程执行,先前的进程则处于挂起状态等待获得时间片后继续执行,如此反复,宏观上看起来这些任务就是并行处理的。

回到我们熟悉的JS引擎,为实现这样的特性,这里就需要引申出一个重要的东西,Event Loop(事件循环)。

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

SetTimeout

明白了上面的东西,那理解setTimeout的机制就容易得多了,看下面一段代码:

console.log("start");

setTimeout(function(){
    console.log("hello")
},200);

setTimeout(function(){
    console.log("world")
},100);

console.log("end");

为了让大家更直观的看到执行的顺序,做了一个GIF

图上可以看出,首先依然是main()开始,首先第一个console.log()入栈执行,执行完毕控制台打印’start’后出栈,紧接着执行到setTimeout定时器,此时JS引擎会将定时器交给浏览器的另一个模块去管理(为方便理解这里把它叫做Timer模块),然后主线程继续向下执行,紧接着将第二个定时器也交给Timer模块,然后执行到第二个console.log(),控制台打印’end’,执行完毕后清空执行栈。但是并没有结束,在主线程执行的同时,Timer模块会检查其中的异步代码,一旦满足触发条件,就会将它添加到任务队列中。Timer2延迟100ms,所以会早于Timer1被添加到队列排头。而主线程此时处于空闲状态,所以会检查任务队列是否有待执行的任务。此时会将Timer2回调中的console.log()执行,控制台打印’world’,然后执行栈空闲后继续检查任务队列,将Timer1的代码压入执行栈中执行,控制台打印’hello’,清空执行栈,此时任务队列为空,执行结束。

控制台依次打印出:

这个时候我们在看下面这个程序,就很好懂了:

console.log("start");

//Timer1
setTimeout(function(){
    console.log("hello");
},200);

//Timer2
setTimeout(function(){
    console.log("world");
},300);

//耗时运算
for(let i = 0; i<10000; i++){
    console.log("我就是来耗时间的");
}

//Timer3
setTimeout(function(){
    console.log("I am run");
},100);

console.log("end");

和第一段不同的是,在最后一个定时器前加了一段for循环,(注:此处仅用来模拟一段比较耗时的运算,假设时间大于1秒,代码真正执行时间不必深究)。chrome控制台运行结果是:

前面的那张Gif图看懂了以后,其实从’world’之前的打印应该都是没有问题的。

但是奇怪的地方就是,Timer3仅仅延迟了100ms,反而在另外两个Timer之后执行了。其实这里原因很简单,因为在Timer1和Timer2加入到执行队列中后,主线程依然还在执行for循环中的代码,处于阻塞状态。队列中的Timer1和Timer2并不会得以执行。当for循环结束,这时才将Timer3交由Timer模块去管理,继续执行后续代码打印’end’,清空执行栈。虽然在这里Timer3的延迟时间最短,但是加入任务队列后还是会排在Timer1和Timer2的后面,所以此时按顺序执行任务队列中的代码,依次打印’hello’、’world’、’I am run’。同时需要注意的是,这种情况下的三个定时器延迟执行的时间已经远远超过了指定的时间。

还有一点值得特别注意的是,有些同学可能会写过这样的代码:

setTimeout(function(){},0);

其实JS引擎在处理这段代码的时候,并不是真正的延迟0ms执行。不同的浏览器会默认有一个最小的延迟时间,低于这个时间间隔会按照默认最小的时间间隔来处理。

总结

我们发现不论事件循环(Event Loop)模型还是setTimeout机制,其实并不是难点,但却是很多开发同学容易忽略的点。很多问题的产生可能就是因为忽略了一些简单的原理导致的。所以这些基本的知识点需要掌握扎实,才能更好的驾驭Javascript。

时间: 2024-10-27 08:15:50

探索setTimeout的相关文章

关于JavaScript中的setTimeout()链式调用和setInterval()探索

http://www.cnblogs.com/Wenwang/archive/2012/01/06/2314283.html http://www.cnblogs.com/yangjunhua/archive/2012/04/12/2444106.html 下面的参考:http://evantre.iteye.com/blog/1718777 1.选题缘起 在知乎上瞎逛的时候看到一个自问自答的问题: 知乎上,前端开发领域有哪些值得推荐的问答?,然后在有哪些经典的 Web 前端或者 JavaScr

腾讯优测优分享 | 探索react native首屏渲染最佳实践

腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实

HTML5 摇一摇加强版之一次失败的探索

最近在看设备传感器的API,当然也少不了研究一下让微信称神的“摇一摇”了.关于“摇一摇”的实现,网上很多资料所以不详细说了,但总是有布局.效果不全等各种问题,所以作为一名资深copypaster,代码肯定是要贴的: 源码在此 核心代码是这一段: this.deviceMotionHandler = function(eventData) { var acceleration = eventData.acceleration; var curTime = new Date().getTime();

探索react native首屏渲染最佳实践

1.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化.目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现.作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实践. 2.首屏耗时计算方法 2.1我们关注的耗时 优化首屏渲染耗时,需要先定义首屏耗时的衡量方法.将react nat

setTimeout(fn, 0)引发的JavaScipt线程的思考

起因 周五改一个checkbox的display属性被错误地设置为none的bug. 经debug发现, 有两个地方修改了display属性: 1) checkbox的controller; 2) checkbox的parent(container). 前者先将display属性更新为block(正确), 后者再次更新为none(错误). 普通的思路是, 修改checkbox的container的代码, 使其能正确更新display值. 但另有一种更巧妙的方法, 就是修改checkbox的con

javascript线程解释(setTimeout,setInterval你不知道的事)

原文:http://www.iamued.com/qianduan/1645.html 今天看到这篇文章,学到了不少东西 特此发出来 和大家分享 JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout( function(){ alert('你好!'); } , 0); setInterval( callbackFunction , 100); 认为se

setTimeout和setImmediate以及process.nextTick的区别

在javascript中我们了解到了setTimeout和setInterVal函数事件队列(任务队列)的相关知识,除了setTimeout和setInterval这两个方法外,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate.它们可以帮助我们加深对"任务队列"的理解. setTimeout() 首先我们看看setTimeout(setInterVal和setTimeout函数区别只是执行次数)函数,

以setTimeout来聊聊Event Loop

平时的工作中,也许你会经常用到setTimeout这个方法,可是你真的了解setTimeout吗?本文想通过总结setTimeout的用法,顺便来探索javascript里面的事件执行机制. setTimeout基本用法 1. setTimeout(code,millisec) setTimeout函数接受两个参数,第一个参数code是将要推迟执行的函数名或者一段代码,第二个参数millisec是推迟执行的毫秒数. 例如: setTimeout(‘console.log(2)’,100); se

setTimeout,setInterval运行原理

function a() { setTimeout(function(){alert(1)},0); alert(2); } a(); 和其他的编程语言一样,Javascript中的函数调用也是通过堆栈实现的.在执行函数a的时候,a先入栈,如果不给alert(1)加setTimeout,那么alert(1)第2个入栈,最后是alert(2).但现在给alert(1)加上setTimeout后,alert(1)就被加入到了一个新的堆栈中等待,并"尽可能快"的执行.这个尽可能快就是指在a的