前段时间,老板交给了我一个任务:通过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的更多相关文章

  1. 从setTimeout谈JavaScript运行机制

    从setTimeout说起 众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执 ...

  2. 深入理解JavaScript运行机制

    深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...

  3. JavaScript运行机制详解

    JavaScript运行机制详解   var test = function(){ alert("test"); } var test2 = function(){ alert(& ...

  4. 深入浅出JavaScript运行机制

    一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: console.log(1); setTimeout(function(){ console.log(3); ...

  5. javascript 运行机制 事件循环 浏览器缓存 (慕课网 前段跳槽面试必备 4-1,4-2,4-3)

    4-1 渲染机制:-1-,什么是DOCTYPE及其作用?DTD(document type definition,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型,浏览器会使 ...

  6. Javascript 运行机制

    先看一下下面这段js代码: console.log('1'); setTimeout(function(){ console.log('2'); },0); console.log('3'); 请问打 ...

  7. JavaScript 运行机制 & EventLoop

    JavaScript 运行机制 & EventLoop 看阮老师博客和自己的理解,记录的学习笔记,js的单线程和 事件EventLoop 机制. 1. JavaScript是单线程 JavaS ...

  8. javascript运行机制

    太久没更新博客了,Javascript运行机制 Record it 1.代码块 JavaScript中的代码块是指由<script>标签分割的代码段.例如: <script type ...

  9. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

随机推荐

  1. 打包合并多个dll

    复杂项目中会引用大量的第三方dll文件,为了便于管理会尝试把相关打包合并成一个dll文件. 推荐使用ILMerge,如需使用网上自行下载. 使用方法: cd 安装目录 ILmerge /target: ...

  2. UIApplicationDelegate 各方法回调时机

    本篇文章主要介绍一些UIApplicationDelegate中几个常用的回调方法的调用时机.以帮助你判断哪些方法倒底放到哪个回调中去实现. 1. – (void)applicationDidFini ...

  3. centos出现-bash: /usr/bin/php: 没有那个文件或目录解决方法

    造成这个的原因是因为找不到php的执行文件导致的,原先我是安装的php5.4,然后卸载了重新安装php7,导致php可执行文件没有放到$PATH中,可以在终端测试:php -v,如果报错bash: / ...

  4. centos修改mysql密码或者进入mysql后解决Access denied for user ''@'localhost' to database 'mysql错误

    原因是MySQL的密码有问题 用mysql匿名用户可以进入数据库,但是看不见mysql数据库. 解决办法:具体操作步骤:关闭mysql:# service mysqld stop然后:# mysqld ...

  5. mathjax

    MathJax.Hub.Typeset() method. This will cause the preprocessors (if any were loaded) to run over the ...

  6. ionic添加scss

    Setup Sass Automatically 在进行以下操作之前要确保node比较新,以便正确安装node-sass 或 改用cnpm install node-sass安装(淘宝源) $ ion ...

  7. java搭建 SpringMVC+Mybatis(SMM)+mybatis-generate

    搭建SSM系统,首先要了解整个过程: 1.创建spring-mvc项目 2.在maven中添加要引用的jar包(使用框架都是较新的版本:) 3. jdbc.xml +spring-mybatis.xm ...

  8. elk示例-精简版

    作者:Danbo 2016-03-09 1.Grok正则捕获 input {stdin{}} filter { grok { match => { "message" =&g ...

  9. mysql设置有外键的主键自增及其他

    有外键的主键设置自增. ; ALTER TABLE `<table>` MODIFY COLUMN `id` ) NOT NULL AUTO_INCREMENT FIRST; 创建数据库, ...

  10. 虚拟机ubuntu14.04系统设置静态ip

    ubuntu14.04 设置静态ip vim /etc/network/interfaces 原来只有 auto lo iface lo inet loopback 修改成如下: auto lo if ...