事件循环机制
-
单线程与多线程
- 单线程
- 多线程
-
任务队列与事件循环机制
单线程与多线程
线程分为单线程和多线程
-
单线程
-
只能同时进行一个任务,后面的任务想要进行就必需等前面的任务完成了
-
可以保证代码执行顺序,但是容易导致阻塞
-
-
多线程
-
同时可以执行多个任务,谁也不挡谁
-
可以解决阻塞问题,但是会改变代码执行顺序。改变顺序后可能让程序变得稍微难理解了一些
-
js是单线程
解决单线程和多线程的问题
同步模式:代码按顺序执行,前面没有执行完后后面的代码不会执行
异步模式:代码可以同时执行,前面没执行完的代码不会影响后面的代码
在js中有哪些是异步模式?
1、回调函数
2、事件
3、定时器
4、AJAX
5、发布/订阅(设计模式)
6、Promise(ES6)
7、Generator函数(ES6)
8、async函数(ES7)
同步模式的代码如下,只有当2000000个span创建完后才会输出 ’ 终于走到我这里了’
for(var i = 0;i<2000000;i++){
var span = document.createElement('span');
span.innerHTML = i;
document.body.appendChild(span);
}
console.log('终于走到我这里了');
异步模式的代码如下
setTimeout(function(){
console.log('老沙最慢')
},100);
setTimeout(function(){
console.log('八戒其次')
},0);
console.log('猴哥最快')
代码会依次输出 —猴哥最快、八戒其次、老沙最慢
任务队列与事件循环机制
js引擎给我们提供了任务队列
任务队列的读取方式:先进先出,后进后出
任务队列(消息队列)分类:
- macrotask(宏任务队列)
1、DOM操作
2、用户交互(事件)
3、网络任务(ajax)
4、history traversal任务(h5当中的历史操作)
5、定时器 - microtask(微任务队列)
1、Promise.then
2、process.nextTick(nodejs中的一个异步操作)
3、MutationObserver(H5里面增加的,用来监听DOM节点变化的)
如下代码运行结果是猴哥最快、八戒其次、老沙最慢
setTimeout(function () {
console.log('老沙最慢');
}, 100);
setTimeout(function () {
console.log('八戒第二');
}, 0);
console.log('猴哥最快');
代码运行过程详解如下
1、代码走到script标签时,会创建一个全局环境globalEC,然后将globalEC放入上下文执行栈(call stack)中,
2、接着走到第一个setTimeout的时候,因为执行时间为100ms,所以,先不放入宏任务队列,
3、接着走到第二个setTimeout的时候,执行时间为0ms,放入宏任务队列,100ms时间到了之后,才放入第一个setTimeout里面的函数
4、走到console.log(‘猴哥最快’)的时候,因为不是异步,所以放入globalEC里面,然后执行console里面的,执行完后就被删除
5、stack为空的时候,接下来就找微任务队列,如果微任务没有任务,就找宏任务队列
再来一个,下面的代码有宏任务也有微任务,
浏览器依次输出 ‘全局的’ 、 ‘Promise的’ 、 ‘then的’ 、 ‘定时器的’,
Promise.then才会被放入微任务队列中,看下图
console.log('全局的');
setTimeout(function(){
console.log('定时器的');
},0);
new Promise(function(resolve){
console.log('Promise的');
resolve();
}).then(function(){
console.log('then的');
});
再来看另一个代码
<input type="text">
<button>点击</button>
<input type="text">
var inputs = document.querySelectorAll('input');
var btn = document.querySelector('button');
btn.onclick = function () {
//inputs[0].value+=' button';
setTimeout(function () {
inputs[0].value += ' button'; // body button
}, 0);
};
document.body.onclick = function () {
inputs[0].value += ' body';
};
在js代码中第四行被注释的代码,在不用setTimeout的时候,点击button的时候会输出‘button body’,这里存在着事件冒泡。但是加上了setTimeout的时候,会输出‘body button’,这也和事件循环机制有关
那么下面这段代码为啥要加setTimeout呢,并且时间还是0,加了有啥意义呢?
inputs[1].onkeydown = function () {
var This = this;
setTimeout(function () {
console.log(This.value);
}, 0);
};
你们可以自己试一下,不加setTimeout的时候,在输入框中输入数据的时候看控制台打印的什么,在输入框为空的时候,我们输入一个a,控制台上会打印一个空白,在输入一个s的时候,打印a。这是为什么呢?
因为事件发生的非常快,浏览器接受的内容没有事件快,浏览器需要渲染,但是事件不需要渲染,所以取不到内容
这时候我们可以通过定时器解决这个问题----由于setTimeout里面的console现在是一个异步,
所以会把console内容放入宏任务队列--console其实是在第二次event loop发生的时候才执行,因为第二次的时候浏览器已经渲染完了
下面总结一下
事件循环 Event Loop
1、它是HTML规范中定义的或者是DOM中定义的,并不是ECMAScript定义的
1、https://www.w3.org/TR/html5/webappapis.html#event-loops
2、https://html.spec.whatwg.org/#event-loops
2、它包括两种,一种在浏览器上下文里,一种在web workers里(HTML5)
3、它是一种分配模式,把任务队列里的任务分配给全局执行上下文进行执行
4、执行顺序
1、先把微任务队列里的任务拿出来交给全局执行上下文执行
2、再把宏任务队列里的任务拿出来交给全局执行上下文执行
3、不断的循环
注意:
1、任务队列(异步代码)里的代码最终还是要放到全局执行上下文(同步的代码)里去执行
2、当全局执行上下文里的代码都执行完了才去执行任务队列里的代码
3、宏任务与微任务的区别是:宏任务会再下一次的Event Loop里执行,微任务会在本次Event Loop里执行