前段时间,老板交给了我一个任务:通过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. intellij idea 自动生成setter getter

    windows下: alt + insert,然后选择要生成的成员. mac下: command + N

  2. spring 注入属性

    一.注入对象类型的数据 1.配置文件 User类与UserService类均需要创建对象.所以都配置其相应的bean类,另外user需作为userService的属性注入,所以userService需 ...

  3. python初学者总结

    学习python首先配置好工作环境,因为不同版本之间的python是不兼容了 原创:01coding.com win7安装环境过程: 1:下载python 建议下载两个不同版本官方已给出 https: ...

  4. Spring声明式事务管理与配置介绍

    转至:http://java.9sssd.com/javafw/art/1215 [摘要]本文介绍Spring声明式事务管理与配置,包括Spring声明式事务配置的五种方式.事务的传播属性(Propa ...

  5. interface -- 接口类

    <?php /** *为了声明接口,需要使用关键字interface *interface IExampleInterface {} *说明(大多数开发人员选择在节后名称前加上大写字母I作为前缀 ...

  6. HIVE 总结

    http://blog.csdn.net/wisgood/article/details/17186181 常见错误 http://blog.csdn.net/sunnyyoona/article/d ...

  7. vim 正则表达式查找ip

    特别说明: \v \v 表示 very magic 这种模式下很多字符默认就表示一些特殊意义,而不用加 \ 如 : < 单词开头 > 单词结尾 ( 组开始 ) 组结束 { 次数开始 } 次 ...

  8. Redis高级进阶(一)

    一.redis中的事务 在关系型数据库中事务是必不可少的一个核心功能,生活中也是处处可见,比如我们去银行转账,首先需要将A账户的钱划走,然后存到B账户上,这两个步骤必须在同一事务中,要么都执行,要么都 ...

  9. 百度编辑器 Ueditor 如何增加字体 ?

    在百度编辑器 Ueditor 如何增加字体 ? 要修改两个文件: 第一个文件:editor-config.js: ,'fontfamily':[             { label:'',name ...

  10. CSS3按钮效果制作

    CSS3按钮效果制作 首先附上效果图,按下去有一种下沉的效果, 未按效果 按下去效果 原理:第一个按钮相对比较简单,就直接是一个双重阴影效果,然后鼠标按下去让他的margin-top值为-3px,阴影 ...