Skip to content

JS-异步编程-EventLoop: Macro tasks and micro tasks #107

Open
@yaofly2012

Description

@yaofly2012

什么是“JS”

JavaScript is a single-threaded non-blocking asynchronous concurrent language

一、js为啥是单线程执行

1.1 单线程

单线程是指只在一个线程里执行JS代码,但浏览器是多线程的。

1.2 单线程的好处

简单,处理DOM时不会出现并发竞争问题。这估计也是WebWorker无法操作DOM的原因。

1.3 异步的必要性

让用户体验更流畅,相对于CPU的处理速度,I/O是很慢的。

二、如何实现异步:Event Loop

2.1 相关概念

  1. 同步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

  1. 异步任务

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

  1. 消息队列

可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。

  1. 回调函数

那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

  • 所谓“绑定事件”其实就是告诉浏览器如果触发了某个事件就执行相关的“回调函数”。
  • 当浏览器监听到触发了指定事件时,就向“任务队列”插一条消息,告诉JS主线程某个“回调函数”可以执行了。
  1. 调用栈(call Stack)
    单线程只能有一个call stack, 并且每次只能执行一个任务。
    栈是有尺寸的,即在一次执行中函数的嵌套调用数量是有限的,如果超过这个限制就会报错。JS执行时遇到这种错误"Uncaught RangeError: Maximum call stack size exceeded"就是说明call statck溢出了。回调函数上限数量取决于statck本身的大小以及statck元素的大小->见参考
function computeMaxCallStackSize1() {
    try {
        return 1 + computeMaxCallStackSize1();
    } catch (e) {
        // Call stack overflow
        return 1;
    }
}

// 多了形参p,call stack元素就多占了内存
function computeMaxCallStackSize2(p) {
    try {
        return 1 + computeMaxCallStackSize2();
    } catch (e) {
        // Call stack overflow
        return 1;
    }
}

console.log(computeMaxCallStackSize1()); // 两个输出结果不一样
console.log(computeMaxCallStackSize2());

2.2 Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
clipboard.png

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

2.3 阻塞 Event Loop

event loop实现了异步回调,但回调只能一个一个的执行,但前面的一个回调非常耗时时就会阻塞后面的回调执行,用户交互也可能出现卡死。

        function sleep(ms) {
			var curr = Date.now();
			while(Date.now() - curr < ms){};
		}
		console.log(1)
		setTimeout(function(){ // 回调依旧被阻塞
			console.log(2) 
		}, 0)
		sleep(2000)

最好开启新线程执行耗时的运算(使用web worker)或者放在服务端运算。

参考:

  1. 阮一峰:JavaScript 运行机制详解:再谈Event Loop
  2. The JavaScript Event Loop: Explained
  3. JSConf 2014 到底什么是Event Loop
  4. 模拟Event Loop执行过程
  5. Node.js 事件循环一: 浅析

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions