为什么要了解Event loop?(二)

转载于http://www.jianshu.com/p/1ee6c21f6efa

为什么要了解Event loop?

理解Event loop,对于浏览器(或者nodejs)处理事件的过程会有更透彻的理解,使用promise,nextTick, setImmediate,setTimeout等会更清晰。本文主要是基于浏览器端来理解的。

<small>有部分术语还是采用英文,看上去感觉比中文好理解</small>

参考自:

Tasks, microtasks, queues and schedules

What the heck is the event loop anyway?

Concurrency model and Event Loop

Macrotasks and Microtasks

  • 并发模型(Concurrency model)

首先我们知道,js是单线程的,要么执行脚本要么进行浏览器渲染。

执行脚本时,通常是这样子的:

runtime.png

举个例子(直接从mdn搬过来):

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);
  1. 当执行 g(21)的时候,创建了第一个frame,包含g(x)和它的局部变量,压入栈(stack);
  2. 当执行到f(m*x)的时候,创建了第二个frame,包含f(x)和它的局部变量,压入栈;
  3. 等到f(x)执行完毕,第二个frame出栈;
  4. 等到g(x)执行完毕,第一个frame出栈;
  • “线程” 与 Event loop

每个“线程”有自己的event loop,比如说每个web worker都维护了自己的event loop,可以分开来工作,彼此通过postMessage通信。如下图:

webworker.png

主线程和web worker的event loop的区别在于,主线程每次task完成后会进行视图更新,但是worker和dom无关,就没有这一步了.

  • Task

如上图,task就是在message queue里的message了(我是这么理解的),一次event loop里面可能会有多个task,task有自己的task source,比如说setTimeout来自于timer task source,又或者和用户交互相关的来自user interaction task source。

浏览器是可以选择先执行哪个task source 的。规范如下:

For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else. The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.

有点晕吧,至少我们弄清楚,在js运行过程中,起码有两个东西:

  • task队列

最简单的就是每次event loop查看task队列,找到最老的task,压入栈,执行之。等到task执行完了,就进行一次视图渲染。周而复始,直到所有task队列都执行完毕。

等等,这样的话,那setTimeout又是怎么做到的?还有promise,要是都是顺序执行的话,这些是怎么做到异步的?

  • Microtask 和 Macrotask

实际上我看到有些文章提到Macrotask,但也有些文章直接把Macrotask当成task,可以区分一下。

接着上文说的,promise是怎么做到异步的呢?

promise并不是task,它属于Microtask,什么是Microtask呢?

Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task.

感觉可以叫它微任务~ 跟在task末尾执行,像个小跟班。

实际上来说,Microtask并非每次都在task末尾才执行,如果一个函数执行完毕后,栈暂时空掉了,那么Microtask也会执行(晕了对不对,后面上代码详述~)

那么什么是Macrotask呢?setTimeout就是Macrotask,还有setImmediate。

对比着Microtask来,他们的区别就在于执行的时机,Macrotask在一次task执行完了,然后浏览器进行渲染,然后才执行Macrotask。

就叫宏任务吧~

所以它俩的区别就在于Microtask会影响IO回调,要是不断增加Microtask的话,就一直无法渲染视图了,看上去就会卡顿。但是Macrotask就没有这种危险。

按我的理解,可以把Macrotask直接当成task来看。

总结下,现在在js运行中,起码有三个东西:

  • task队列
  • Microtask队列
  • 用代码来感受

先来简单的:

  console.log(‘script start‘);

  setTimeout(function() {
    console.log(‘setTimeout‘);
  }, 0);

  Promise.resolve().then(function() {
    console.log(‘promise1‘);
  }).then(function() {
    console.log(‘promise2‘);
  });

  console.log(‘script end‘);

结果应当如下:

script start

script end

promise1

promise2

setTimeout

假设我们维护三个队列,分别是:tasks, stacks和Microtasks,上面的代码执行情况应该如下:

  1. 执行脚本,把这段脚本压入tasks,此时

tasks: [script],stacks: [],Microtasks: [];

  1. 执行脚本,打印script start,此时

tasks:[script]

stacks: [script]

Microtasks: [ ];

  1. 遇到setTimeout,由上文可知,setTimeout可当作一个task,所以此时

tasks: [script, setTimeout callback]

stacks: [script]

Microtasks: [ ];

  1. 遇到Promise, Promise是Microtask,所以此时

tasks: [script, setTimeout callback]

stacks: [script]

Microtasks: [Promise then];

  1. 执行脚本,打印script end,此时:

tasks: [script, setTimeout callback]

stacks: []

Microtasks: [Promise then];

  1. 在一个task执行完之后执行Microtask,此时:

tasks: [setTimeout callback]

stacks: [Promise callback]

Microtasks: [Promise then];

  1. 打印promise1, .Promise then后面还有一个Promise,压入Microtasks:

tasks: [setTimeout callback]

stacks: [ ]

Microtasks: [PromiseThen]

  1. 执行Promise 2号,此时:

tasks: [setTimeout callback]

stacks: [Promise callback]

Microtasks: [PromiseThen];

  1. 打印出promise 2,此时:

tasks: [setTimeout callback]

stacks: []

Microtasks: [];

  1. 进行浏览器渲染,然后执行setTimeout脚本,此时:

tasks: [setTimeout callback]

stacks: [setTimeout callback]

Microtasks: [];

  1. 打印出setTimeout,此时:

tasks: []

stacks: []

Microtasks: [];

  • 为什么不一样

如果你用的不是chrome浏览器的话,表现很可能就会不一样,甚至可能一段代码两次执行结果都不一样(比如说在ios 8的safri)

对的,因为这是规范== 浏览器会有差异。前途是光明的,而道路则是曲折的~

  • 继续感受代码

html如下:

<div class="outer">

<div class="inner"></div>

</div>

js如下:

// Let‘s get hold of those elements

var outer = document.querySelector(‘.outer‘);

var inner = document.querySelector(‘.inner‘);

  // Let‘s listen for attribute changes on the
  // outer element
  new MutationObserver(function() {
    console.log(‘mutate‘);
  }).observe(outer, {
    attributes: true
  });

  // Here‘s a click listener…
  function onClick() {
    console.log(‘click‘); 

    setTimeout(function() {
      console.log(‘timeout‘);
    }, 0); 

    Promise.resolve().then(function() {
      console.log(‘promise‘);
    }); 

    outer.setAttribute(‘data-random‘, Math.random());
  }

  // …which we‘ll attach to both elements
  inner.addEventListener(‘click‘, onClick);
  outer.addEventListener(‘click‘, onClick);

出来结果应该是:

click

promise

mutate

click

promise

mutate

timeout

timeout

  • why

参考event loop的处理模型,整个处理如下:

  1. 将脚本压入tasks,运行脚本,压入stacks。执行完毕清空;
  2. 用户点击,触发点击,将 onClick压入task,执行onClick,即压入stacks;
  3. 打印click
  4. 将setTimeout压入task;
  5. 将Promise压入Microtask;
  6. 设置outer属性,将MutationObserver压入Microtask;
  7. 一次onClick执行完毕,stacks清空了,这时候虽然后面还有冒泡触发,但是会先执行Microtask(真是见缝插针),Microtasks顺序执行,打印promisemutate
  8. 事件冒泡,执行outer的onClick,执行过程差不多。等到冒泡执行完毕,情况如下:

tasks: [setTimeout, setTimeout]

stacks: [ ]

Microtasks:[ ]

  1. 执行浏览器渲染
  2. 执行tasks,依次打印出setTimeout setTimeout,搞定。
  • 进阶版

前面我们使用交互来触发click 事件,如果我们在刚刚代码后面加inner.click(),整个过程就变化了,不妨思考。答案可以在Tasks, microtasks, queues and schedules找到

其实文章代码基本上挪用了Tasks, microtasks, queues and schedules,加了一些自己的理解,欢迎指正。


作者:博客专用马甲链接:http://www.jianshu.com/p/1ee6c21f6efa來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间: 2024-11-06 03:35:02

为什么要了解Event loop?(二)的相关文章

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

PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:“看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就可以猜到那是指同步操作(自然不走event loop了):至于watcher啥的,显然只是实现上的特色,即使用同一个queue实现也未尝不可” [原帖: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 作者:阮一峰] 一年前,我写了一篇<什么

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

一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正文之前,插播一条消息.我的新书<ECMAScript 6入门>出版了(版权页,内页1,内页2),铜版纸全彩印刷,非常

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

原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正文之前,

(转载)JavaScript 运行机制详解:再谈Event Loop

source: http://www.ruanyifeng.com/blog/2014/10/event-loop.html  一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正

从一个例子引发对JS运行机制之 Event Loop 的思考

栗子如下: for (var i = 0; i < 5; i++) { setTimeout(function() { console.log('i: ',i); //一秒之后输出几乎没有时间间隔依次输出5个5 }, 1000); } console.log(i); //立即输出5 想必很多人看到立马能看出答案吧,但是为什么定时器不能依次打印出1,2,3,4,5呢?答案稍后分晓. 那到底怎么才能依次输出我们想要的结果呢?大家可能都想到是利用闭包,或者是利ES6中的let声明,但是今天我们不讲这个

JavaScript 运行机制详解:Event Loop

转自: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个

从Javascript单线程谈Event Loop

假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件."但你能说出背后的原因吗? 先理解相关概念 线程与进程 进程:是系统资源分配和调度的单元.一个运行着的程序就对应了一个进程.一个进程包括了运行中的程序和程序所使用到的内存和系统资源. 线程:线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程.

深入理解 JavaScript 事件循环(一)— event loop

引言 相信所有学过 JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 .在初期许多人会把异步理解成类似多线程的编程模式,其实他们中有着很大的差别,要完全理解异步,就需要了解 JS 的运行核心——事件循环(event loop).在之前我对事件循环的认识也是一知半解的,直到我看了 Philip Roberts 的演讲 What the heck is the event loop anyway?,我才对事件循环有了一

什么是 Event Loop?

摘自:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定Ja