想必你们都了解,JavaScript语言最大的特色之一就是单线程,什么是单线程呢?vue
就是同一时间只能执行一件事情,这么设计的主要缘由是为了防止用户在操做时出现冲突,例如两个线程同时处理一个DOM那么以谁为准呢?node
在H5中新增了一个后台运行的线程Web Workers,可是这个线程是受主线程控制的而且不能操做DOM。web
图片转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》 ajax
存放在堆(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 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.js启动时,它会初始化事件循环,这可能会调用异步API调用,定时器或调用 process.nextTick(),而后开始处理事件循环。
下图显示了事件循环的操做顺序的简化概述。
每一个阶段都有一个执行回调的FIFO(先入先出 first in,first out)队列。
计时器
计时器指定时间以后能够执行提供的回调,但不会当即执行,只是把回调放入timers阶段的队列中。
注意:技术上讲,轮询阶段控制什么时候执行定时器
I/O回调
此阶段为某些系统操做(读写文件等)执行回调。
轮询
该阶段有两个主要功能
1.执行以及到时间的定时器
2.处理轮询队列中的事件
当进入此阶段而且没有定时器时:
若是轮询队列不为空,则事件循环将遍历其回调队列,同步执行它们,直到队列耗尽或达到系统相关硬限制。
若是轮询队列为空,则会发生如下两件事之一:
一旦轮询队列为空,事件循环将检查已达到时间的定时器。若是一个或多个定时器准备就绪,则事件循环将回退到定时器阶段以执行这些定时器的回调。
检查
执行setImmediate()的阶段
node 里面的微任务有 then 和 nextTick
他们不是事件循环里面的一部分微任务有本身的队列,不管事件循环在任何阶段,微任务队列都将在当前操做完成后处理。也就是当前阶段结束,下一个阶段开始以前清空微任务队列。
有理解不对的地方还请多多指教。