假如面试回答js的运行机制时,你可能说出这么一段话:“Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件。”但你能说出背后的原因吗?

先理解相关概念

线程与进程

进程:是系统资源分配和调度的单元。一个运行着的程序就对应了一个进程。一个进程包括了运行中的程序和程序所使用到的内存和系统资源。

线程:线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程。

同步和异步

同步和异步关注的是:消息结果通信机制

同步:发出调用后,在没有得到结果前,该调用不返回。但是一旦调用返回,就得到返回值

异步:发出调用后,调用直接返回,没有返回结果。但结果由回调函数给出,至于什么时候给出,不知道。(这个回调函数肯定是在当前js执行完后才执行)

阻塞与非阻塞

阻塞和非阻塞关注的是:程序在等待调用结果时的状态.

阻塞调用:调用结果返回之前,当前线程被挂起。调用线程只有在得到结果后才会返回。
非阻塞调用:在不能立刻得到结果之前,该调用不会阻塞当前线程。

为什么JavaScript是单线程?

JavaScript是单线程,程序按照顺序排列,前面的必须处理好,后面的才会执行。JavaScript的设计初衷是作为浏览器脚本语言,主要是简单用户交互、操作DOM等,所以这门语言要围绕单线程来设计,否则出现复杂的同步问题。

由于JavaScript是单线程的,对于耗时的或者时间不确定的操作,我们可以使用异步编程,因为异步可以实现非阻塞操作。当然也可以用HTML5标准的Web Worker。本文不作讨论,详细参考MDN文档:点击这里

既然js是单线程执行的,那谁去轮询大的任务队列?这不矛盾了吗?

Js的单线程与异步矛盾吗?

不矛盾!!!首先记住这句话:Js执行是单线程,但浏览器是多线程

1)JS的单线程

一个浏览器进程中只有一个JS的执行线程,同一时刻内只会有一段代码在执行

2)浏览器多线程

查阅资料,有些文章也说是模块,本文就以浏览器是多线程来说,它有以下常驻线程:

渲染引擎线程:负责页面的渲染

JS引擎线程:负责JS的解析和执行(本文说的主线程就指js引擎线程)

定时器触发线程:处理定时事件,比如setTimeout, setInterval

事件触发线程:处理DOM事件

异步http请求线程:处理http请求

......

浏览器是Js的使用场景,浏览器本身是典型的 GUI 工作线程(GUI 工作线程在绝大多数系统中都实现为事件处理,避免阻塞交互)。故浏览器是事件驱动的(Event driven),浏览器中很多行为是异步,会创建事件并放入任务队列中。

由于Javascript 是单线程,它需要借助异步完成耗时或者时间不确定的操作,这些操作由浏览器的其它线程执行,这形成了异步事件驱动。异步事件驱动往往由浏览器的两个或以上常驻线程共同完成的。例如ajax异步请求是由JS执行线程和异步http请求线程,事件触发线程共同完成的。

事件循环机制(Event Loop)

相关概念

函数调用形成一个栈帧。

 function foo(b) {
let a = 10;
return a + b + 11;
} function bar(x) {
let y = 3;
return foo(x * y);
} console.log(bar(7));

当调用 bar 时,创建了第一个帧 ,帧中包含了 bar 的参数和局部变量。

当 bar 调用 foo 时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了 foo 的参数和局部变量。

当 foo 返回时,最上层的帧就被弹出栈(剩下 bar 函数的调用帧 )。

当 bar 返回的时候,栈就空了。

对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。

每一个线程只有一个栈,每一个程序只有一个堆。

队列

一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。

当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数。

当栈再次为空的时候,也就意味着消息处理结束。

任务队列消息队列

任务队列是一个先进先出的数据结构,当主线程执行栈一清空,任务队列的回调函数就自动进入主线程。任务分成两种:

1、同步任务:在主线程上排队执行的任务。只有执行完当前任务,才能执行后一个任务。

2、异步任务:该任务不进入主线程、而进入任务队列。当执行栈清空后,才去执行任务队列中的任务。

异步执行的运行机制

由于JavaScript只能一次执行一段代码(由于其单线程性质),这些代码块中的每一个都“阻止”其他异步事件的进度。这意味着当异步事件发生时(如鼠标点击,定时器触发或XMLHttpRequest完成),它将排队等待稍后执行(这种排队实际发生的确定会因浏览器到浏览器而异)。

1、所有同步任务都在主线程上执行,形成一个执行栈

2、当遇到异步任务时(IO设备操作等),就在任务队列中添加一个事件,这个事件对应着该异步任务的回调函数。

3、执行栈中的所有同步任务执行完毕,系统就会读取任务队列,进入执行栈,开始执行。

4、主线程不断重复第三步。这就形成了事件循环

结论:Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件。

事件和回调函数的概念必要说明

  • 工作线程:是本文对除了js引擎线程之外的其它线程的统称
  • 回调函数:在一个函数中调用另外一个函数。这里指异步场景下为了非阻塞那些被主线程挂起来的代码。
  • 主线程读取任务队列,就是读取里面有哪些事件,执行对应的回调函数。

工作线程完成一项任务,就向任务队列中添加一个事件。这里的完成任务是指完成操作(click、mouse、touch,ajax的数据完全请求回来......),并非指执行它的回调函数

a.onclick = function () {
console.log("roro")
}

如上段代码,仅是操作了click,但并没有执行回调函数打印roro

事件循环

事件循环是:主线程重复从任务队列中取消息(事件),执行对应回调函数的过程。

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在任务队列中加入各种事件(click,load,done)。只要执行引擎栈栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。

定时器

首先参考这篇外国人的文章:how-javascript-timers-work,定时器的执行原理及细节。

setTimeout()怎么执行?

setTimeout(function () {
console.log('a');
}, 5000)

Javascript执行引擎(主线程)运行的时候,产生堆和栈。程序中代码依次进入栈中等待执行,当调用setTimeout()方法时,在浏览器的定时器线程下处理延时方法,当setTimeout方法执行5秒后,到达触发条件,方法被添加到用于回调的任务队列。

当执行引擎的执行栈为空,执行引擎开始轮询检查任务队列是否有任务需要被执行,当检查到已经符合执行条件的延时方法时,将延时方法console.log('a')压入执行栈,引擎发现调用了log()方法,于是又将log()方法入栈。然后对执行栈依次出栈执行,输出‘a’,清空执行栈,整个执行完毕。

setTimeout(fn,0)是立即执行吗?

在javascript权威指南中:当setTimeout的延迟时间设置为0的时候,回调函数不会马上执行,而是进入 事件队列。

btn.onclick = function () {
setTimeout(function () {
console.log('a')
}, 0);
}

setTimeout(fn,0)的含义是:指定某个任务在主线程的空闲时间下,尽可能早地执行。它被添加进任务队列,因此要等到同步任务和任务队列中的前一个事件都处理完,才会执行。

HTML5标准规定了setTimeout()的第二个参数的最小值不得低于4ms,如果低于这个值,就会自动增加。老版本的浏览器允许最短间隔设为10ms。详细参考MDN文档:最小延迟和超时嵌套

所以setTimeout(fn,0)并不是立即执行。假若你想实现0ms 的timeout可以用window.postMessage(),本文不作讨论。

两道经典的面试题:

一)以下代码输出什么?

function foo() {
console.log('a')
setTimeout(function () {
console.log('b');
}, 500)
} for (let i = 0; i < 10000; i++) {
foo()
}

执行结果:首先全部输出a,中间等待500ms,然后全部输出b。上图是个人理解,不恰当的地方请指出!

二)ajax异步请求是否真的异步?

1、JS的执行线程(主线程)发起异步请求,浏览器会开一条新的HTTP请求线程来执行请求,继续执行栈中剩下的任务,

2、在新线程(HTTP请求线程)中,在执行请求的同时,浏览器会正常处理其他任务的执行。

3、在未来的某一时刻,当数据完全请求回来以后,事件触发线程监视到之前发起的HTTP请求已完成,会将指定的回调函数放入任务队列中。

4、当浏览器执行栈空闲时,去扫描任务队列中的回调函数,依次压入执行栈中处理。

所以:ajax请求是异步。由浏览器新开一个线程请求,事件回调的时候放入Event loop任务队列等候处理。详细的例子,可以参考MDN文档对ajax的描述:同步和异步

误解:事件循环类似栈或队列

这里顺带提一下:事件循环虽然涉及到类似队列的结构,并不是采用栈的方式处理任务。事件循环作为一个进程被划分为多个阶段,每个阶段处理一些特定任务,各阶段轮询调度。这些阶段可以是定时器处理,dom事件处理,ajax异步处理......

结语

JavaScript引擎只有一个线程,强制异步事件排队等待执行,Javascript语言的事件循环,是浏览器的处理和行为。另外,本文是我个人的学习笔记,通篇结合个人的理解,在某些地方表述不严谨,如有错误,希望指出。

从Javascript单线程谈Event Loop的更多相关文章

  1. 深入理解Javascript单线程谈Event Loop

    假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后 ...

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

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

  3. javascript运行机制详解: 再谈Event Loop(转)

    作者: 阮一峰 日期: 2014年10月 8日 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts ...

  4. 【repost】JavaScript 运行机制详解:再谈Event Loop

    一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck i ...

  5. [转载]JavaScript 运行机制详解:再谈Event Loop

    https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5e ...

  6. 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

    PS: 我先旁观下大师们的讨论,得多看书了~   别人说的:“看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就 ...

  7. [转] JavaScript 运行机制详解:再谈Event Loop

    一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

  8. JavaScript事件循环(Event Loop)机制

    JavaScript 是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决定 ...

  9. 浅谈 Event loop (事件循环)

    从Event Loop谈JS的运行机制 先来理解一个概念: JS分为同步任务和异步任务 同步任务都在主线程上执行,形成一个执行栈 Execute Content Stack 主线程之外,事件触发线程管 ...

随机推荐

  1. 百度统计API的使用

    百度统计API的使用 在搭建自己博客的时候,希望自己能有个日志系统,能够看到PV.UV等信息,同时自己也搭建了个ELK系统,可惜服务器配置太低(1GHZ+1G内存),根本运行不起来.只能使用第三方的日 ...

  2. vim的tab键设定

    多在windows上编程的童鞋可能习惯于感受tab键为4个空格的长度,不过在linux系统中一般默认设定tab键为8个空格长度来显示.事实上tab也确实是8个空格的长度.不过由于习惯问题,某些童鞋还是 ...

  3. docker~为什么没人说说.dockerignore

    回到目录 最近一直专注于docker的开发之中,而在使用Dockerfile时发现有个问题,当你的发布目录只能是obj\Docker\publish,而指向其它目录dockefile并不认它,只有如何 ...

  4. hdu_3336: Count the string(KMP dp)

    题目链接 题意:求给定字符串中,可以与某一前缀相同的所有子串的数量 做这道题需要明白KMP算法里next[]数组的意义 首先用一数组nex[](这里与之前博客中提到的next明显不同)存储前缀后缀最长 ...

  5. 51nod_1264:线段相交(计算几何)

    题目链接 关于判断线段相交,具体算法见 点击打开链接 ,先进行快速排斥试验,若不能判断出两个线段不相交,再进行跨立试验. //吐槽1,long long 会溢出... //吐槽2,只进行跨立试验的虽然 ...

  6. 基于python的二元霍夫曼编码译码详细设计

    一.设计题目 对一幅BMP格式的灰度图像(个人证件照片)进行二元霍夫曼编码和译码 二.算法设计 (1)二元霍夫曼编码: ①:图像灰度处理: 利用python的PIL自带的灰度图像转换函数,首先将彩色图 ...

  7. Spring初学

    一.spring体系结构spring核心组件 1.Beans(包装应用程序自定义对象Object,Object中存有数据) 2.Core (资源加载,资源抽象,建立维护与bean之间的一些关系所需的一 ...

  8. Windows远程linux服务器执行shell命令

    一.前言 借用百度百科关于putty的描述:PuTTY是一个Telnet.SSH.rlogin.纯TCP以及串行接口连接软件.较早的版本仅支持Windows平台,在最近的版本中开始支持各类Unix平台 ...

  9. jquery html5 file 上传图片显示图片

    jquery js 的代码:不同浏览器下的路径 //建立一個可存取到該file的url function getObjectURL(file) {     var url = null ;     i ...

  10. Hibernate一对多实例

    本文向大家介绍Hibernate实例一对多的情况,可能好多人还不了解Hibernate实例一对多,没有关系,下面通过一个实例来帮助您理解Hibernate实例一对多,希望本文能教会你更多东西. 先看由 ...