js是单线程的
- 因为是单线程,所以所有任务都需要排队,前一个任务结束,后一个任务才能执行,如果前一个任务花费时间较长,后一个任务等待时间也随之变长。
- js可以做到先把等待中的任务先放一边晾着,去处理后面的任务。于是所有任务可以分为两种,一种是同步任务,另一种是异步任务:
- 同步任务很简单,前面的任务完成后面才能执行,一个接一个的执行任务。
- 异步任务不占用主线程,直接进入“任务队列”中,等任务队列通知主线程,某个任务可以执行了,才会进入主线程执行。
异步执行的运行机制
1 所有同步任务都在主线程上执行,形成一个执行栈
2 主线程之外,还存在一个“任务队列”。只要异步任务有了执行结果,就在“任务队列”中放置一个事件
3 一旦执行栈中的所有同步任务都执行完毕,就会去“任务队列”中读取新的任务放到执行栈中,再依次执行任务
4 只要主线程空了,就会读取任务队列,这就是js的运行机制。这个过程会不断的重复
再说说事件和回调函数
- 任务队列其实存放的是事件的队列,主程序读取任务队列,其实就是在读有哪些事件罢了
- 只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取
- 异步任务必须指定回调函数,当主线程执行异步任务时,其实就是在执行对应的回调函数
- 任务队列是一个先进先出的数据结构,排在前面的先执行。当调用栈中的任务空了后,主线程会自动调用任务队列里的任务执行
来看看Event Loop
- 主线程从任务队列中读取任务,这个过程是不断重复的,所以被称为Event Loop(事件循环),从字面意思就清楚了
-
再来看一张图
上图中,主线程产生了heap(堆)和stack(栈),栈中的代码调用各种api,然后在任务队列中加入click,load,done等事件,当栈中的任务都执行完后就去调用任务队列中的事件并依次执行
function one() {
var a = 1;
two();
function two() {
var b = 2;
three();
function three() {
console.log(b);
}
}
}
one();
// 栈:是先进后出 函数调用就是最常见的形式
// one -> two -> three 依次执行
// 销毁过程是three -> two -> one
任务队列中的定时器
console.log(1);
setTimeout(function () {
console.log('定时器');
},0); // 如果不写时间,默认是4ms
let p = new Promise(function (resolve, reject) {
console.log(3);
resolve(100);
}).then(function (data) {
console.log(data);
});
console.log(2);
// 1 3 2 100 '定时器'
Node
先来看看node是如何工作的
NodeJs的运行机制:
1 V8引擎解析js代码
2 代码中可能会调用node API,node会交给LIBUV库处理
3 LIBUV通过阻塞I/O和多线程实现了异步I\O
4 将任务的执行结果返回给V8引擎,V8引擎再将结果返回给用户
Node中的Event Loop
在LIBUV内部有这样一个事件环机制,在node启动时会初始化事件环
这里每一个阶段都对应一个事件队列,当Event Loop执行到某一阶段的时候会将该阶段对应的事件依次执行。
当队列执行完毕or执行的数量超过上限的时候,会自动转入下一阶段。
这里我们重点关注一下poll阶段
poll阶段
总结一下:以上内容就是关于事件环的整体流程,也可以理解一下同步和异步的区别