十四、深入核心,详解事件循环机制【下】

简介: JavaScript的学习零散而庞杂,很多时候我们学到了一些东西,但是却没办法感受到进步!甚至过了不久,就把学到的东西给忘了。为了解决自己的这个困扰,在学习的过程中,我一直在试图寻找一条核心的线索,只要顺着这条线索,我就能够一点一点的进步。前端基础进阶正是围绕这条线索慢慢展开,而事件循环机制(Event Loop),则是这条线索的最关键的知识点

十四、深入核心,详解事件循环机制【下】


// demo02
console.log('golb1');
setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})
setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})
process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})
setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})
process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})
setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})


这个例子看上去有点复杂,乱七八糟的代码一大堆,不过不用担心,我们一步一步来分析一下。


第一步:宏任务script首先执行。全局入栈。glob1输出。


微信图片_20220510235215.jpg


第二步,执行过程遇到setTimeout。setTimeout作为任务分发器,将任务分发到对应的宏任务队列中。


setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})


微信图片_20220510235219.jpg


第三步:执行过程遇到setImmediate。setImmediate也是一个宏任务分发器,将任务分发到对应的任务队列中。setImmediate的任务队列会在setTimeout队列的后面执行。


setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})


微信图片_20220510235222.jpg


第四步:执行遇到nextTick,process.nextTick是一个微任务分发器,它会将任务分发到对应的微任务队列中去。


process.nextTick(function() {
    console.log('glob1_nextTick');
})

微信图片_20220510235228.jpg


第五步:执行遇到Promise。Promise的then方法会将任务分发到对应的微任务队列中,但是它构造函数中的方法会直接执行。因此,glob1_promise会第二个输出。


new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})


微信图片_20220510235232.jpg

微信图片_20220510235235.jpg


第六步:执行遇到第二个setTimeout。


setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})


微信图片_20220510235239.jpg


第七步:先后遇到nextTick与Promise


process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})


微信图片_20220510235242.jpg

第八步:再次遇到setImmediate。


setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})


微信图片_20220510235245.jpg


这个时候,script中的代码就执行完毕了,执行过程中,遇到不同的任务分发器,就将任务分发到各自对应的队列中去。接下来,将会执行所有的微任务队列中的任务。


其中,nextTick队列会比Promie先执行。nextTick中的可执行任务执行完毕之后,才会开始执行Promise队列中的任务。


当所有可执行的微任务执行完毕之后,这一轮循环就表示结束了。下一轮循环继续从宏任务队列开始执行。


这个时候,script已经执行完毕,所以就从setTimeout队列开始执行。


微信图片_20220510235248.jpg


setTimeout任务的执行,也依然是借助函数调用栈来完成,并且遇到任务分发器的时候也会将任务分发到对应的队列中去。


只有当setTimeout中所有的任务执行完毕之后,才会再次开始执行微任务队列。并且清空所有的可执行微任务。


setTiemout队列产生的微任务执行完毕之后,循环则回过头来开始执行setImmediate队列。仍然是先将setImmediate队列中的任务执行完毕,再执行所产生的微任务。


当setImmediate队列执行产生的微任务全部执行之后,第二轮循环也就结束了。


大家需要注意这里的循环结束的时间节点。

当我们在执行setTimeout任务中遇到setTimeout时,它仍然会将对应的任务分发到setTimeout队列中去,但是该任务就得等到下一轮事件循环执行了。例子中没有涉及到这么复杂的嵌套,大家可以动手添加或者修改他们的位置来感受一下循环的变化。


OK,到这里,事件循环我想我已经表述得很清楚了,能不能理解就看读者老爷们有没有耐心了。我估计很多人会理解不了循环结束的节点。


当然,这些顺序都是v8的一些实现。我们也可以根据上面的规则,来尝试实现一下事件循环的机制。


// 用数组模拟一个队列
var tasks = [];
// 模拟一个事件分发器
var addFn1 = function(task) {
    tasks.push(task);
}
// 执行所有的任务
var flush = function() {
    tasks.map(function(task) {
        task();
    })
}
// 最后利用setTimeout/或者其他你认为合适的方式丢入事件循环中
setTimeout(function() {
    flush();
})
// 当然,也可以不用丢进事件循环,而是我们自己手动在适当的时机去执行对应的某一个方法
var dispatch = function(name) {
    tasks.map(function(item) {
        if(item.name == name) {
            item.handler();
        }
    })
}
// 当然,我们把任务丢进去的时候,多保存一个name即可。
// 这时候,task的格式就如下
demoTask =  {
    name: 'demo',
    handler: function() {}
}
// 于是,一个订阅-通知的设计模式就这样轻松的被实现了


这样,我们就模拟了一个任务队列。我们还可以定义另外一个队列,利用上面的各种方式来规定他们的优先级。


需要注意的是,这里的执行顺序,或者执行的优先级在不同的场景里由于实现的不同会导致不同的结果,包括node的不同版本,不同浏览器等都有不同的结果。

相关文章
|
7月前
|
安全 数据处理 C++
【Qt 底层之事件驱动系统】深入理解 Qt 事件机制:主事件循环与工作线程的交互探究,包括 QML 的视角
【Qt 底层之事件驱动系统】深入理解 Qt 事件机制:主事件循环与工作线程的交互探究,包括 QML 的视角
1436 3
|
4月前
|
存储 前端开发 JavaScript
事件循环机制是如何工作的
【8月更文挑战第3天】事件循环机制是如何工作的
38 1
|
4月前
|
前端开发 JavaScript
前端搞懂事件循环机制
【8月更文挑战第3天】前端搞懂事件循环机制
53 1
|
7月前
|
Java
Java线程通信的精髓:解析通知等待机制的工作原理
Java线程通信的精髓:解析通知等待机制的工作原理
81 3
Java线程通信的精髓:解析通知等待机制的工作原理
|
7月前
|
存储 关系型数据库 MySQL
纯c协程框架NtyCo实现与原理
纯c协程框架NtyCo实现与原理
155 1
|
7月前
|
存储 前端开发 rax
|
存储 前端开发 rax
2.7 协程设计原理
2.7 协程设计原理
73 0
带你读《深入浅出Dart》十六、事件循环和协程机制(2)
带你读《深入浅出Dart》十六、事件循环和协程机制(2)
带你读《深入浅出Dart》十六、事件循环和协程机制(3)
带你读《深入浅出Dart》十六、事件循环和协程机制(3)
|
Dart JavaScript 前端开发
带你读《深入浅出Dart》十六、事件循环和协程机制(1)
带你读《深入浅出Dart》十六、事件循环和协程机制(1)
116 0