从一道执行题,了解浏览器中JS执行机制

执行题

setTimeout(function(){
    console.log('定时器开始啦')
});
new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        if(i == 99) resolve();
    }
}).then(function(){
    console.log('执行then函数啦');
});
console.log('代码执行结束');
// 马上执行for循环啦
// 代码执行结束
// 执行then函数啦
// 定时器开始啦
// 如果没有执行对,请往下看
复制代码

JavaScript本身是一门单线程语言

为什么 JS 是单线程的?作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM 。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

同步执行和异步执行

单线程就意味着,所有任务都需要排队,前一个任务结束,才能执行后一个任务。如果前一个任务耗时很长,那么后一个任务就不得不一直等待 于是乎,JS 设计者们把所有任务分成两类,同步和异步

  1. 同步:只有前一个任务执行完毕,才能执行后一个任务
  2. 异步:当同步任务执行到某个 WebAPI 时,就会触发异步操作,此时浏览器会单独开线程去处理这些异步任务。

任务队列、回调队列、事件循环

WebAPI 是啥?浏览器事件、定时器、ajax,这些操作不会阻塞 JS 的执行,JS 会跳过当前代码,执行后续代码

  1. 任务队列( Task Queue ):主线程执行完毕后所触发的异步任务( WebAPIs ),叫任务队列
  2. 回调队列( Callback Queue ):这些异步 WebAPI 执行完成后得到的结果,会添加到 callback queue
  3. 事件循环( Event Loop ):只要主线程的同步任务执行完毕,就会不断的读取 "回调队列" 中的回调函数,到主线程中执行,这个过程不断循环往复

如何知道主线程执行执行完毕?JS引擎存在 monitoring process 进程,会持续不断的检查主线程执行为空,一旦为空,就会去 callback queue 中检查是否有等待被调用的函数。

说了一堆概念,来一起看看这段代码

console.log('1');
setTimeout(function() {
    console.log('2');
}, 0);
console.log('3');
复制代码

执行结果如下:

  1. 打印1
  2. 遇到 WebAPI( setTimeout ) ,浏览器新开定时器线程处理,执行完成后把回调函数存放到回调队列中。专业一点的说发: JS 引擎遇到异步任务后不会一直等待其返回结果,而是将这个任务挂起交給其他浏览器线程处理,自己继续执行主线程中的其他任务。这个异步任务执行完毕后,把结果返回给回调队列。被放入的代码不会被立即执行。而是当主线程所有同步任务执行完毕, monitoring process 进程就会把 "回调队列" 中的第一个回调代码放入主线程。然后主线程执行代码。如此反复
  3. 打印3 异步 setTimeout 不会阻塞同步代码,因此会首先打印3
  4. 主线程执行完毕后,执行 Callback Queue 打印2

macro task 与 micro task

异步任务的执行优先级并不相同,它们被分为两类:微任务( micro task ) 和 宏任务( macro task ) 根据异步事件的类型,这些事件实际上会被派发对应的宏任务和微任务中,在当前主线程执行完毕后,

  1. 会先查看微任务中是否有事件存在,如果不存在,则再去找宏任务
  2. 如果存在,则会依次执行队列中的参数,直到微任务列表为空,让后去宏任务中一次读取事件到主线程中执行,如此反复 当前主线程执行完毕后,会首先处理微任务队列中的事件,让后再去读取宏任务队列的事件。在同一次事件循环中,微任务永远在宏任务之前执行。
  1. 宏任务( macro-task ):整体 scriptsetTimeoutsetIntervalUI交互事件I/O
  2. 微任务( micro-task ):process.nextTickPromiseMutaionObserver

整体script本身就是一次宏任务

上代码

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function (resolve, reject) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()
1. setTimeout:宏任务:存入宏任务队列
2. Promise:函数本身是同步执行的( **Promise** 只有一个参数,默认new的时候就会同步执行), `.then` 是异步,因此依次打印12  `.then` 存入微任务中
3. 打印3( 第一次主线程执行完毕 )
4. 执行微任务中的回调函数:5, 让后执行宏任务中的 `setTimeout` 4
// 最终结果1,2,3,5,4
复制代码

来点稍微高难度的

console.log(1)

setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
})

new Promise(resolve => {
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

setTimeout(() => {
    console.log(9)
    new Promise(resolve => {
        console.log(11)
        resolve()
    }).then(() => {
        console.log(12)
    })
})
第一次同步执行:1,7,8
    宏任务:setTimeout
2,4,5
9,11,12
复制代码

应用场景

console.log('先执行这里');
setTimeout(() => {
    console.log('再执行啦');
}, 0);
复制代码
  1. 在工作中,经常会遇到上述的代码,含义:只要主线程执行完成,就立马执行 setTimeout 中的回调代码
  2. micro-task 优先于 macro-task 执行,在浏览器中高优先级的代码可以在 promisesetTimeout(()=>{}, 0) 中执行
  3. 认知尚浅,只能想到这些应用场景

总结

这些概念是中级以上前端开发必知必会内容,其实实际的落地场景很少

  1. 为什么 new Promise 第一个参数是同步执行的 ?学习Promise && 简易实现Promise
  2. node 中的 JS 执行机制是什么样子的?从一道执行题,了解Node中JS执行机制

附:这篇博客 也许 想表达 浏览器环境中JS同步和异步的执行过程 (⊙﹏⊙)b

转载于:https://juejin.im/post/5b0e84d0f265da08c86fa580

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值