用实例和知识点描述带您清晰的了解浏览器事件环的每一步;javascript
在计算机内存中存取数据, 基本的数据结构分为栈和队列php
栈(Stack)是一种后进先出的数据结构; 栈的特色是 操做只在一端进行, 通常来讲, 栈的操做只有两种: 进栈和出栈; 第一个进栈的数据老是最后一个才出来html
队列(Queue)和栈相似, 可是它是先进先出的数据结构,它的特色是 操做在队列两端进行, 从一端进入再从另外一端出来; 先进入(从A端)的老是先出来(从B端)前端
名称 | 进出特色 | 端的数量 |
---|---|---|
栈 | 后进先出 | 进出都在同一端 |
队列 | 先进先出 | 进出是在不一样端 |
// 队列执行时按照放置的顺序依次执行
setTimeout(function(){
console.log(1)
});
setTimeout(function(){
console.log(2)
});
setTimeout(function(){
console.log(3)
});
// => 1 2 3
复制代码
// 在JavaScript中函数的执行就是一个典型的入栈与出栈的过程
function a(){
console.log('a')
function b(){
console.log('b');
function c(){
console.log('c');
}
c();
}
b();
}
a();
// => a b c
// 函数调用顺序是a b c, 而做用域销毁的过程依次是c b a
复制代码
JavaScript是单线程的, 这里所谓的单线程指的是主线程是单线程;java
为何不是多线程呢? JavaScript最初设计是运行在浏览器中的, 假定是多线程, 有多个线程同时操做DOM, 岂不很混乱! 那会以哪一个为准呢?面试
JavaScript为单线程, 在一个线程中代码会一行一行往下走,直到程序执行完毕; 若执行期间遇到较为费时的操做, 那只能等待了;promise
单线程的设计使得语言的执行效率变差, 为了利用多核CPU的性能,javascript语言支持异步代码; 当有较为费时的操做时, 可将任务写为异步; 主线程在执行过程当中遇到异步代码, 会先将该异步任务挂起, 继续执行后面的同步代码, 待同步执行完毕再回过头来, 检查是否有异步任务, 若是有异步任务就执行它;浏览器
PS: Java君加班有点累, 他想烧水冲一杯咖啡, 若是采用同步执行方式,那他就傻傻地等待,等水开了再冲咖啡;bash
PS: Java君加班有点累, 他想烧水冲一杯咖啡, 若是采用异步执行方式,那么他在等待水烧开以前,他能够听听歌,刷刷抖音啥的,等水开了再冲咖啡;数据结构
(-很明显异步的方式效率会高一些);
JavaScript代码是在栈里执行的, 不管是同步仍是异步; 代码分为同步代码和异步代码, 异步代码又分为: {宏任务} 和 [微任务]
JavaScript是解释型语言,它的执行过程是这样的:
从以上步骤能够看出,不论同步仍是异步, 都是在栈里执行的, 栈里的任务执行完成后一遍又一遍地回头检查队列,这种方式就是所谓的"事件环"
// 先看个demo吧
console.log('start');
setTimeout(()=>{
console.log('hello');
}, 1000);
console.log('end');
// start end hello 上面代码执行后, 输出'start' 'end', 大约1s以后输出'hello'
// ? 为何'hello'不在end以前输出呢
复制代码
console.log('start');
setTimeout(() => {
console.log('hello');
},0);
console.log('end');
// start end hello
// 将上例微微调整,发现输出结果仍是同样的
// 由于setTimeout的回调函数只是会被添加到(事件)队列中,而不会当即执行。 再回头
复制代码
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve('Sucess');
});
promise.then((data)=>{
console.log(data);
});
console.log('Hello World!');
// 'Promise' 'Hello World!' 'Sucess'
复制代码
以前说到,异步任务又分为: 宏任务和微任务, 那他们是怎样执行的呢?
// 查看setTimeout和Promise.then的不一样
console.log(1);
setTimeout(()=>{
console.log(2);
Promise.resolve().then(()=>{
console.log(6);
});
}, 0);
Promise.resolve(3).then((data)=>{
console.log(data); // 3
return data + 1;
}).then((data)=>{
console.log(data) // 4
setTimeout(()=>{
console.log(data+1) // 5
return data + 1;
}, 1000)
}).then((data)=>{
console.log(data); // 上一个then没有任何返回值, 因此为undefined
});
// 1 3 4 undefined 2 6 5
复制代码
console.log(1);
,将其执行, 输出 1setTimeout(()=>{ console.log(2); }, 0)
, 将其放入宏任务队列,此时宏任务队列:[s1]setTimeout(()=>{ console.log(data+1); return data + 1; })
,将其放入宏任务队列(先标记,1s后异步执行完成后再将异步函数的回调放入队列), 此时宏任务队列:[s1,s2]setTimeout(()=>{
console.log(1);
Promise.resolve().then(data => {
console.log(2);
});
}, 0);
Promise.resolve().then(data=>{
console.log(3);
setTimeout(()=>{
console.log(4)
}, 0);
});
console.log('start');
// start -> 3 1 2 4
// 给方法分类: 宏任务 微任务
// 宏任务: setTimeout
// 微任务: then
/*
// 执行顺序: 微任务会先执行
// 默认先执行主栈中的代码,执行后完清空微任务;
// 以后微任务执行完毕,取出第一个宏任务到主栈中执行
// 第一个宏任务执行完后,若是有微任务会再次去清空微任务,以后再去取宏任务,这样就造成事件环;
*/
复制代码
解析:
setTimeout(function () {
console.log(1);
Promise.resolve().then(function () {
console.log(2);
}); // p2
}); // s1
setTimeout(function () {
console.log(3);
}); // s2
Promise.resolve().then(function () {
console.log(4);
}); // p1
console.log(5); // 5 4 1 2 3
复制代码
解析
setTimeout(()=>{
console.log('A');
},0);
var obj={
func:function () {
setTimeout(function () {
console.log('B')
},0);
return new Promise(function (resolve) {
console.log('C');
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
// C E D A B
复制代码
解析:
setTimeout(()=>{ console.log('A'); },0)
被加入到宏任务事件队列中,此时宏任务中有[s1(输出A)];setTimeout(()=>{console.log('B'); },0)
被加入到宏任务事件队列中,此时宏任务中有[s1,s2(输出B)];console.log('E')
,执行后输出 'E'磕磕绊绊终因而理解了这一块的知识点, 之前只是在不断的搬砖, 却从未停下来思考、认真学习, GET到以后感受解开了很多疑惑;
在写文档时候发现本身的语言描述能力竟然如此的不堪, 啰里啰嗦写了不少; 这大抵是成长的必经之路吧;
参考了一些朋友的文章, 从中学习到很多, 有知识点的学习也有大佬对知识点巧妙的描述技巧; 向大佬致敬!
参考文章: