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

JavaScript运行机制 & Event Loop #12

Open
JTangming opened this issue May 26, 2019 · 5 comments
Open

JavaScript运行机制 & Event Loop #12

JTangming opened this issue May 26, 2019 · 5 comments

Comments

@JTangming
Copy link
Owner

参考文章

@JTangming
Copy link
Owner Author

JTangming commented Jun 15, 2019

event loop

结合以下描述,看图理解。

@JTangming JTangming changed the title Event Loop 总结 JavaScript运行机制 & Event Loop Jul 8, 2019
@JTangming
Copy link
Owner Author

JTangming commented Jul 8, 2019

JavaScript 是单线程、非阻塞的脚本语言,单线程指同一个时间只能做一件事。

JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。

JavaScript 的任务分两种:一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有某个异步任务可以执行了,该任务才会进入主线程执行。

理解 Event Loop

JavaScript 的运行机制如下:

  • 所有同步任务都在主线程上从头开始执行,将当前的执行环境压入执行栈(execution context stack)等待执行,如果是一个方法,则加入执行栈的是当前方法的执行环境(私有作用域、上层作用域的指向、参数、变量和this等)
  • 在主线程之外,存在一个"事件队列",当某个异步任务达到可执行的状态时,就推入到"事件队列"之中
  • 当"执行栈"中的所有同步任务执行完毕,系统就会读取"事件队列"。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行

主线程不断重复上面的第三步。所以整个的这种运行机制又称为 Event Loop(事件循环)。只要主线程空了,就会去读取"事件队列",这就是 JavaScript 的运行机制。

事件队列包括:Task Queue 和 MicroTask Queue

javascript 代码运行分两个阶段:

  • 预解析,把所有的函数定义提前,所有的变量声明提前,变量的赋值不提前
  • 执行,从上到下执行(按照js运行机制)

微任务(Microtask)与宏任务(Macrotask)

异步任务分为宏任务和微任务,宏任务队列可能有多个,而微任务队列只有一个:

  • 宏任务包括:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • 微任务包括: Promise(), process.nextTick, Object.observe(已废弃), MutationObserver(html5新特性)

以上仅仅讲了大致的执行流程,下面结合微任务和宏任务细说一下执行 JavaScript 代码的具体流程:

  • 执行全局 Script 同步代码,当所有同步任务执行完毕之后,清空执行栈,接下来先执行微任务队列里的任务
  • 从微任务队列(microtask queue)中取出位于队首的任务,放入执行栈中执行,执行完后 microtask queue 长度减 1,直到所有微任务执行完毕
  • 微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务
  • 执行栈空后,再次读取微任务队列里的任务,依次类推不断的重复执行。

tips:执行宏任务的过程中,遇到微任务,依次加入微任务队列,会在下一次微任务中被执行。如果在执行 microtask 的过程中,又产生了 microtask,那么会加入到队列的末尾,会在当前这个周期被调用执行。

event loop-2

@JTangming
Copy link
Owner Author

浏览器为了能够使得 JS 内部 Macrotask 与 DOM 任务能够有序的执行,会在一个 Macrotask 执行结束后,在下一个 Macrotask 执行开始前切换至 UI 线程,对页面进行重新渲染,流程如下:

(macro)task->渲染->(macro)task->...

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,执行完当前所有微任务,开始检查渲染,然后 GUI 线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

@JTangming
Copy link
Owner Author

JTangming commented May 8, 2021

node环境下的事件循环机制

事件循环的模型:

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

node中事件循环的实现是依靠的libuv引擎。

chrome v8 引擎将 js 代码解释后去调用对应的 node api,而这些 api 最后由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。

因此实际上 node 中的事件循环存在于 libuv 引擎中。

Reference:

@JTangming
Copy link
Owner Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant