【JS执行机制——事件循环机制】


前言

  JS是一门单线程语言,那就意味着一次只能执行一个任务且按顺序执行。如果有耗时任务,也必须等着它执行完了才能执行下一个任务。问题来了,浏览网页的时候,某个高清图片需要加载很久,那网页岂不是卡着等图片加载完才能做别的操作?
  显然不是这样的,JS设计者设计了一种执行机制:事件循环机制(Event Loop),以实现单线程非阻塞的方法。

一、什么是事件循环机制?

  通过上文,了解到事件循环机制(Event Loop)是用来实现单线程非阻塞的一种机制。

  我们先了解下任务。任务可分为同步任务、异步任务,同步任务可以立即执行,一般会直接进入主线程中执行。异步任务指不进入主线程而进入任务队列的任务,一般是比较耗时的,如setTimeout、ajax请求等。异步任务再细分下,可分为宏任务、微任务:

  • 宏任务(macro-task):包括整体代码script、setTimeout、setInterval、ajax、DOM事件
  • 微任务(micro-task):Promise.then、Node 环境下的process.nextTick

  然后,事件循环机制是如何执行任务的呢?进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。举例:

setTimeout(function() {
    
    
    console.log('4');
})

new Promise(function(resolve) {
    
    
    console.log('1');
    resolve();
}).then(function() {
    
    
    console.log('3');
})

console.log('2');
// 输出 1 2 3 4
  • 这段代码作为宏任务,进入主线程。
  • 遇到setTimeout,那么其回到函数注册后分发到宏任务Even Queue。
  • 接下来是Promise,new Promise立即执行,then函数分发到微任务队列。
  • 遇到console.log(),同步代码,立即执行。
  • 此时第一个宏任务结束,看有哪些微任务?接下来执行微任务。
  • 执行完微任务,第一次事件循环结束。接下来第二轮循环,从宏任务开始,执行宏任务队列setTimeout函数。
  • 结束

流程图:
img

二、题目测验

console.log('1');

setTimeout(function() {
    
    
    console.log('2');
    process.nextTick(function() {
    
    
        console.log('3');
    })
    new Promise(function(resolve) {
    
    
        console.log('4');
        resolve();
    }).then(function() {
    
    
        console.log('5')
    })
})
process.nextTick(function() {
    
    
    console.log('6');
})
new Promise(function(resolve) {
    
    
    console.log('7');
    resolve();
}).then(function() {
    
    
    console.log('8')
})

setTimeout(function() {
    
    
    console.log('9');
    process.nextTick(function() {
    
    
        console.log('10');
    })
    new Promise(function(resolve) {
    
    
        console.log('11');
        resolve();
    }).then(function() {
    
    
        console.log('12')
    })
})

  整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。

三、await会阻塞代码吗

function timer(){
    
    
   return new Promise((resolve)=>{
    
    
        setTimeout(()=>{
    
    
            resolve('A')
        },0)
        console.log('B')
   })
}
async function foo(){
    
    
    let ans = await timer();
    console.log(ans)
    console.log('C');
}
foo()
console.log('D')

以上输出什么呢?
async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
首先是执行foo方法,foo方法里面的timer有await关键字修饰,timer需要等到promise执行完才能执行timer后面的代码。但是不会去等待timer里面的同步代码,所以先输出B。然后打印D。等到异步操作完成,再接着执行函数体内后面的语句,所以接着是A、C。

awaitgenerator的语法糖,详细请看博客:深入理解ES6——迭代器与生成器

总结

1、JS是单线程语言,不管是什么新框架语法糖实现的所谓异步,其实,都是用同步方法模拟的。
2、事件循环是JS实现异步的方法,也是JS的执行机制。
3、执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。

猜你喜欢

转载自blog.csdn.net/weixin_42936434/article/details/126456415