JavaScript运行机制与setTimeout
前段时间,老板交给了我一个任务:通过setTimeout来延后网站某些复杂资源的请求。正好借此机会,将JavaScript运行机制和setTimeout重新认真思考一遍,并将我对它们的理解整理如下。
1.JavaScript运行机制
核心特征:单线程
JavaScript在浏览器中是单线程运行的。所谓单线程,就是同一时刻只执行一个任务,简单来讲,就是任何时刻JS主线程都只有一句JavaScript代码在运行。
那么为什么JavaScript一定要是单线程呢?如果同时有多个线程运行岂不更强大吗?关于这个问题,在JavaScript运行机制详解一文中有给出一个通俗易懂的解释:“假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?” 所以,与用户交互和操作DOM这些主要用途,这决定了JavaScript必须是单线程的。
任务队列
虽然JavaScript每个时刻只能做一个任务,但它并不是只有一个任务需要做。比如它有Ajax回调函数需要执行,它有用于监听click事件的监听函数需要执行,它有setTimeout的函数需要执行...这些在某个时机需要执行的东西,都是JavaScript需要完成的任务;JavaScript主线程当前正在执行的东西,也是它需要完成的一个任务。面对这么多任务,JavaScript一次又只能处理一个任务,用于管理任务执行的任务队列就应运而生了。
所有这些任务中首要的当然是那些被主线程顺序执行的一行一行的代码,即同步任务。主线程在执行同步任务的过程中,可能会接到未来需要执行某个其他任务的指示。那些未来需要执行的任务,即为异步任务。主线程由于手中还没忙完同步任务,就只能先把接到的异步任务的指示记录下来。
如何记录呢?那就是在合适的时候把相关异步任务推到任务队列中。这里有两点需要理解清楚。第一,并不是马上就把异步任务推到任务队列中,而是在恰当的时机再把异步任务推到队列中,这个时机比如Ajax请求成功返回的时候、click事件触发的时候、setTimeout设置的时间点到了的时候。第二,推到任务队列这个“推到”的动作并不意味着“马上执行”,它只是告诉主线程有这个任务在这里需要被执行,如果主线程有空就赶紧执行,如果主线程现在没有空,那么一旦有空就要考虑执行。
那么对于这个需要执行的任务而言,主线程什么时候才能称得上是有空了呢?第一,主线程已经执行完了同步任务;第二,主线程已经把异步队列中排在该任务之前的任务都执行完了。这时,主线程就可以放心大胆地执行该任务了。当然,该异步任务一旦被执行,对于主线程而言也就不再是异步而是同步的了。
一次正确的Event Loop执行顺序:
其实上述任务队列中的任务叫做宏任务(macrotask),又叫task。同步代码也是一种宏任务。
除了宏任务之外,还有一种微任务(microtask),又叫job。
- 宏任务包括:script(包括同步js代码)、setTimeout、 setInternal、setImmeidate(非标准)、I/O、UI rendering
- 微任务包括: promise等
一次正确的Event Loop执行顺序:
- (1)执行同步代码,这属于宏任务
- (2)执行栈为空(由执行环境,也叫执行上下文组成的),查询是否有微任务需要执行
- (3)执行所有微任务
- (4)必要的话渲染UI
- (5)开始下一轮Event Loop,执行下一个宏任务(task队列里的下一段异步代码)
2. setTimeout
在理解了JavaScript运行机制后,理解setTimeout就很容易了。
JavaScript主线程在遇到代码setTimeout(cb, n)时,要做的事情为:在延迟指定时间n后,将setTimeout回调函数cb加入任务队列中。
注意,经过时间n后,cb只是被加入任务队列中,而非被执行。
结合setTimeout,我们可以更清晰地理解JavaScript运行机制:
假设JavaScript主线程会先后遇到两句setTimeout代码,分别记为代码片setTimeout1和setTimeout2,其回调函数分别为cb1和cb2,延迟时间分别为delay1和delay2。则可以得到如所示的JavaScript任务执行情况图:
情况1:两个异步任务都在同步任务结束一段时间后才加入异步队列,且后一个异步任务加入时前一个异步任务已执行结束

这种情况最清晰明朗,几个任务互相没有干扰,就是说没有出现某个任务需要等待另一个任务执行结束才能执行的情况。
情况2:两个异步任务都在同步任务结束一段时间后才加入异步队列,但前一个异步任务的执行推迟了后一个异步任务的执行

在该情况下,cb2加入到异步任务队列的时候并不能马上执行,因为JS主线程还在执行cb1。待cb1执行结束后,cb2开始执行。
情况3:在同步任务还未执行结束时,已经有异步任务加入异步队列

如图所示,在同步任务还未执行完时,setTimeout1指定的delay1已经到时间了,将cb1加入异步任务队列。此时因为同步任务还未结束,所以cb1的执行会推迟到同步任务执行结束。
为了逐步增加所述情况的复杂程度,这里暂时将cb2加入异步队列的时间假定为在同步任务和cb1都已经执行完之后。
更复杂的情况
基于以上3种情况,不难得出更复杂的几种情况:
多个异步任务都是在同步任务还未执行结束时就已经加入异步队列:

有异步任务在同步任务还未结束时加入异步队列,也有异步任务在前一个异步任务还未结束时加入异步队列:

当然,还有情况是,后一个任务的加入时间早于前一个任务(比如delay1远大于delay2):

参考资料
setTimeout的那些事:
http://imweb.io/topic/56ac67fbe39ca21162ae6c78
JavaScript运行机制详解:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
JavaScript运行机制与setTimeout的更多相关文章
- 从setTimeout谈JavaScript运行机制
从setTimeout说起 众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执 ...
- 深入理解JavaScript运行机制
深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...
- JavaScript运行机制详解
JavaScript运行机制详解 var test = function(){ alert("test"); } var test2 = function(){ alert(& ...
- 深入浅出JavaScript运行机制
一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: console.log(1); setTimeout(function(){ console.log(3); ...
- javascript 运行机制 事件循环 浏览器缓存 (慕课网 前段跳槽面试必备 4-1,4-2,4-3)
4-1 渲染机制:-1-,什么是DOCTYPE及其作用?DTD(document type definition,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型,浏览器会使 ...
- Javascript 运行机制
先看一下下面这段js代码: console.log('1'); setTimeout(function(){ console.log('2'); },0); console.log('3'); 请问打 ...
- JavaScript 运行机制 & EventLoop
JavaScript 运行机制 & EventLoop 看阮老师博客和自己的理解,记录的学习笔记,js的单线程和 事件EventLoop 机制. 1. JavaScript是单线程 JavaS ...
- javascript运行机制
太久没更新博客了,Javascript运行机制 Record it 1.代码块 JavaScript中的代码块是指由<script>标签分割的代码段.例如: <script type ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
随机推荐
- 九度OJ 1204:农夫、羊、菜和狼的故事 (遍历、BFS)
时间限制:1 秒 内存限制:32 兆 特殊判题:是 提交:744 解决:502 题目描述: 有一个农夫带一只羊.一筐菜和一只狼过河. 果没有农夫看管,则狼要吃羊,羊要吃菜. 但是船很小,只够农夫带一样 ...
- OC常用函数及变量
1.OC常用的的函数及变量 (1)算术函数 [算术函数] 函数名 说明 int rand() 随机数生成.(例)srand(time(nil)); //随机数初期化int val = rand()P; ...
- PAT 1065. 单身狗(25)
“单身狗”是中文对于单身人士的一种爱称.本题请你从上万人的大型派对中找出落单的客人,以便给予特殊关爱. 输入格式: 输入第一行给出一个正整数N(<=50000),是已知夫妻/伴侣的对数:随后N行 ...
- 虚拟机 minimal 安装增强包
在虚拟机下安装了一个centos的minimal镜像,发现增强包不能安装,鼠标不能在虚拟机和物理机间自由切换.不能共享粘贴板,非常是不爽,这里摸索出在centos minimal OS下安装增强包的 ...
- memcached 不同客户端的问题
摘要: memcached-java客户端调用get方法获取数据失败 主要演示一下在memcached服务器端set数据之后,在客户端调用java api获取数据.不过此过程如果不慎会读取数据失败. ...
- R语言数据管理(四):数据导出
与read.*函数对应,导出函数为write.*函数. 比较常见的为write.csv和write.table. 一般格式: setwd("D:\\") write.table(y ...
- Hadoop2.x + eclipse 插件配置
http://blog.csdn.net/u012874209/article/details/52105304 搭建集群那些就不用说了,主要有几个关键的地方需要注意(自己的Hadoop版本是2.5. ...
- C#Winform之等待窗体
窗体主要代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ...
- Windows下Nginx的启动、停止等命令添加
Windows下Nginx的启动.停止等命令在Windows下使用Nginx,我们需要掌握一些基本的操作命令,比如:启动.停止Nginx服务,重新载入Nginx等,下面我就进行一些简单的介绍.1.启动 ...
- c# 继承小结
本文意在巩固基础知识,并不是对其进行深入剖析,如若对各位高手没有什么作用,请绕过.本文为原创文,所有示例均是博主测试过的,欢迎大家批评指正,如有转载请标明出处,谢谢.继承.封装和多态是面向对象编程的重 ...