Event Loop - JavaScript和node运行机制

JavaScript 运行机制

单线程(单一主线程)

想必你们都了解,JavaScript语言最大的特色之一就是单线程,什么是单线程呢?vue

就是同一时间只能执行一件事情,这么设计的主要缘由是为了防止用户在操做时出现冲突,例如两个线程同时处理一个DOM那么以谁为准呢?node

在H5中新增了一个后台运行的线程Web Workers,可是这个线程是受主线程控制的而且不能操做DOM。web

Event Loop(事件环)

图片转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》 ajax

JS在运行时造成一个heap(堆)和一个stack(执行栈),

存放在堆(heap)内存中的都是对象,栈里面的变量实际保存的是一个指针,这个指针指向堆(heap)内存中的对象。promise

执行栈里面的代码开始执行,栈里面的方法可能调用webAPI(操做DOM, ajax, 定时器)
将他们的回调加入callback queue(任务队列)
例如 在stack里面执行了ajax,当ajax运行完成后在callback queue里面加ajax的回调,
定时器同样,必须得定时器到时间才会将其回调函数加入callback queue
任务队列里面的回调都是异步的回调浏览器

console.log(1);
let fn = () => { console.log(2) }
setTimeout(fn)
console.log(3)
// 执行 console.log(1) console.log(3) 等待定时器到期将fn加入事件队列
// 只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
// 
复制代码

须要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等好久,因此并无办法保证,回调函数必定会在setTimeout()指定的时间执行。bash

callback queue遵循先进先出的逻辑,先被加入队列的回调会被先执行异步

宏任务与微任务

看一段代码socket

// 思考一下执行结果的顺序会是什么
Promise.resolve().then(() => {
    console.log(1);
})
console.log(2);
setTimeout(() => {
    console.log(3);
})
// 结果是 2 1 3
复制代码

惊不惊喜?难不成promise.then和setTimeout同样?那咱们换一个顺序函数

console.log(2);
setTimeout(() => {
    console.log(3);
})
Promise.resolve().then(() => {
    console.log(1);
})
// 结果依旧是 2 1 3
复制代码

这是宏任务与微任务的缘由
Promise.then是微任务,setTimeout是宏任务, 微任务在执行栈中代码走完后当即执行,在宏任务以前执行,全部微任务执行完再执行宏任务

宏任务

setTimeout setInterval (setImmediate)

微任务

Promise.then,浏览器把它的实现放到了微任务中,MutationObserve不兼容, MessageChannel微任务(vue中nextTick实现原理)

再看一段代码

console.log(1);
setTimeout(function(){
    console.log(2);
    Promise.resolve(1).then(function(){
        console.log('promise')
    })
})
setTimeout(function(){
    console.log(3);
})
复制代码

带浏览器中输入结果的顺序是 1 2 prmise 3
先走执行栈 console.log(1); 先走第一个setTimeout,将微任务放到队列中,执行微任务,微任务执行完再走宏任务 (浏览器过程)

可是在node里面执行就不是这么回事了
node里面执行结果是1 2 3 promise
node是将当前任务队列里面的全部回调走完再走微任务的回调队列

你没听错,微任务有一个本身的执行队列

Web Workers

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程能够执行任务而不干扰用户界面, 意思就是在执行Web Worker里面脚本时,页面不会假死。

简单说一下专用worker用法,一个专用worker仅仅能被生成它的脚本所使用

main.js(主线程文件)

if (window.worker) { // 处理错误兼容
    let myWorker = new Worker('worker.js'); // 指定一个脚本的URI
    
    // 获取两个input输入框
    let first = document.getElementById('first')
    let first = document.getElementById('second')
    
    // workers的方法经过postMessage()方法和onmessage事件处理函数生效
    first.onchange = function() {
        myWorker.postMessage([first.value,second.value]);
        console.log('Message posted to worker');
    }

    second.onchange = function() {
        myWorker.postMessage([first.value,second.value]);
        console.log('Message posted to worker');
    }
    
    myWorker.onmessage = function(e) { // 获取worker.js 的返回结果
      result.textContent = e.data;
      console.log('Message received from worker');
    }
}
复制代码
worker.js

onmessage = function(e) {
  // 传进来的参数都在e.data里面
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult); // 返回数据
}
复制代码

详细了解请点击

node

node 的 Event Loop

当Node.js启动时,它会初始化事件循环,这可能会调用异步API调用,定时器或调用 process.nextTick(),而后开始处理事件循环。

下图显示了事件循环的操做顺序的简化概述。

注意:每一个方框将被称为事件循环的“阶段”。

每一个阶段都有一个执行回调的FIFO(先入先出 first in,first out)队列。

阶段概述

  • 定时器: 此阶段执行由setTimeout()和setInterval()定义的回调。
  • I/O回调: 此阶段执行几乎全部的回调,除了关闭回调,定时器和setImmediate().
  • idle, prepare: 只在内部使用。
  • 轮询: 检索新的I / O事件。检查是否有定时器到期。
  • 检查: setImmediate()在这里调用回调。
  • 关闭回调: socket.on('close', ...)等。

详细阶段

计时器

计时器指定时间以后能够执行提供的回调,但不会当即执行,只是把回调放入timers阶段的队列中。
注意:技术上讲,轮询阶段控制什么时候执行定时器

I/O回调

此阶段为某些系统操做(读写文件等)执行回调。

轮询

该阶段有两个主要功能
1.执行以及到时间的定时器
2.处理轮询队列中的事件

当进入此阶段而且没有定时器时:

  • 若是轮询队列不为空,则事件循环将遍历其回调队列,同步执行它们,直到队列耗尽或达到系统相关硬限制。

  • 若是轮询队列为空,则会发生如下两件事之一:

    • 若是setImmediate()已经被调用,那就结束轮询阶段进入检查阶段
    • 若是没有setImmediate()调用,事件循环将等待回调被添加到队列中,而后当即执行它们

一旦轮询队列为空,事件循环将检查已达到时间的定时器。若是一个或多个定时器准备就绪,则事件循环将回退到定时器阶段以执行这些定时器的回调。

检查

执行setImmediate()的阶段

node里面的微任务

node 里面的微任务有 then 和 nextTick

他们不是事件循环里面的一部分微任务有本身的队列,不管事件循环在任何阶段,微任务队列都将在当前操做完成后处理。也就是当前阶段结束,下一个阶段开始以前清空微任务队列。

最后

有理解不对的地方还请多多指教。

相关文章
相关标签/搜索