Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

理解事件循环与任务队列 #1

Open
hyelimwu opened this issue Dec 10, 2017 · 0 comments
Open

理解事件循环与任务队列 #1

hyelimwu opened this issue Dec 10, 2017 · 0 comments

Comments

@hyelimwu
Copy link
Owner

hyelimwu commented Dec 10, 2017

JS是单线程的

JS是单线程的,也就是它一次只能执行一段代码。JS中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言。JS的设计初衷就没有考虑这些,针对JS这种不具备并行任务处理的特性,我们称之为“单线程”。

虽然JS运行在浏览器中是单线程的,但是浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中。浏览器中很多异步行为都是由浏览器新开一个线程去完成,一个浏览器至少实现三个常驻线程:

  • JS引擎线程
  • GUI渲染线程
  • 事件触发线程

JS引擎

JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中,比如最出名的就是Chrome浏览器的V8引擎,如下图所示,JS引擎主要有两个组件构成:

  • 堆-内存分配发生的地方
  • 栈-函数调用时会形一个个栈帧(frame)

image

调用栈

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

image

  • 调用一个函数时,返回地址(return address)、参数(arguments)、本地变量(local variables)等都会被推入栈中。当函数执行完毕弹出堆栈的时候,局部变量(简单数据类型)也会跟着弹出,复杂的数据类型的话则是弹出相应的指针。
  • 只有简单的数据类型(Number,String,Boolean,Undefined,Null,Symbol)是存放在栈中,复杂的数据类型譬如对象,数组,只是把对应的指针存放在栈中,真正的值是存放在Heap中的,当这个对象没有用处的时候,由垃圾回收机制进行释放空间。
  • 当一个函数嵌套另一个函数时,则这个函数的相关参数也会被推入栈顶。

事件循环与任务队列

事件循环可以简单描述为:

  1. 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
  2. 在此期间WebAPIs完成这个事件,把回调函数放入CallbackQueue中等待;
  3. 当执行栈为空时,Event Loop把Callback Queue中的一个任务放入Stack中,回到第1步。

Javascript Event Loop Visual Representation

  • Event Loop是由javascript宿主环境(像浏览器)来实现的;
  • WebAPIs是由C++实现的浏览器创建的线程,处理诸如DOM事件、http请求、定时器等异步事件;
  • JavaScript 的并发模型基于"事件循环";
  • Callback Queue(Event Queue 或者 Message Queue) 任务队列,存放异步任务的回调函数
var start=new Date();
setTimeout(function cb(){
	console.log("时间间隔:",new Date()-start+'ms');
},500);
while(new Date()-start<1000){};
  1. main()入栈,局部变量start初始化;
  2. setTimeout入栈,出栈,丢给WebAPIs,开始定时500ms;
  3. while循环入栈,开始阻塞1000ms;
  4. 500ms过后,WebAPIs把cb()放入任务队列,此时while循环还在栈中,cb()等待;
  5. 又过了500ms,while循环执行完毕从栈中弹出,main()弹出,此时栈为空,Event Loop,cb()进入栈,log()进栈,输出'时间间隔:1003ms',出栈,cb()出栈

Microtasks和Macrotasks

macro-task(Task)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task(Job)包括:process.nextTick, Promises.then(), Object.observe(已被废弃), MutationObserver

根据 WHATVG 的说明,在一个事件循环的周期(cycle)中一个 (macro)task 应该从 macrotask 队列开始执行。当这个 macrotask 结束后,所有的 microtasks 将在同一个 cycle 中执行。在 microtasks 执行时还可以加入更多的 microtask,然后一个一个的执行,直到 microtask 队列清空。

setTimeout(function cb() {
	console.log(4);
}, 0);
new Promise(function executor (resolve) {
	console.log(1);
	for(var i = 0; i < 10000; i++) {
	  i == 9999 && resolve();
	}
	console.log(2);
}).then(function onFulfilled() {
	console.log(5);
});
console.log(3);
//执行结果:1 2 3 5 4

或者可以简单写成这样:

setTimeout();
var promise = new Promise(executor);
promise.then(callback);
console.log(3);
  1. main()入栈;
  2. setTimeout入栈,出栈,丢给WebAPIs,开始定时0ms(实际上不一定是多少,总之大于0),到时之后,将回调函数cb()放入macrotask queue;
  3. Promise构造函数executor()入栈,log(1)入栈,输出1,出栈;
  4. for循环入栈,当i=9999时,resolve()入栈,Promise实例的状态变为fulfilled(完成),resolve()出栈。构造函数执行完后,我们得到了promise(它是resolved);
  5. promise.then入栈,onFulfilled(then方法绑定的resolved状态的回调函数)放入microtask queue;
  6. log(2)入栈,输出2,出栈;
  7. executor()出栈;
  8. log(3)入栈,输出3,出栈,main()出栈;
  9. 此时栈为空,microtask queue中的任务可以进栈了,onFulfilled()入栈,log(5)入栈,输出5,出栈;
  10. 此时Stack和microtask queue都为空,Event Loop,将macrotask queue中的cb()入栈,log(4)入栈,输出4,log(4)出栈,cb()出栈

参考资料:

  1. Tasks, microtasks, queues and schedules . https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
  2. Understanding Javascript Function Executions — Call Stack, Event Loop , Tasks & more — Part 1 . https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec
  3. How JavaScript works: an overview of the engine, the runtime, and the call stack . https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
  4. 理解事件循环一(浅析) . 44.理解事件循环一(浅析) ccforward/cc#47
  5. 理解事件循环二(macrotask和microtask . 45.理解事件循环二(macrotask和microtask) ccforward/cc#48
  6. 从Promise来看JavaScript中的Event Loop、Tasks和Microtasks . 从Promise来看JavaScript中的Event Loop、Tasks和Microtasks creeperyang/blog#21
  7. 理解 Node.js 事件循环 . http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html
  8. Philip Roberts: What the heck is the event loop anyway? . https://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html
  9. http://latentflip.com/loupe
  10. Promises/A+ . https://promisesaplus.com/
@hyelimwu hyelimwu changed the title 理解事件循环机制与任务队列 理解事件循环与任务队列 Dec 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant