JS同步和异步+事件循环

为什么JavaScript是单线程语言?

我们都知道,JavaScript作为浏览器的脚本语言,主要用来实现与用户的交互,利用JavaScript,我们可以实现对DOM的各种各样的操作,如果JavaScript是多线程的话,一个线程想修改一个DOM节点,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是修改还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的。

为什么会有同步和异步?

我们知道JavaScript是一门单线程的语言,因此同一个时间只能做一件事情,这意味着所有任务都需要排队,前一个任务执行完,才会执行下一个任务。但是,如果前一个任务的执行时间很长,那么后一个任务就需要一直等待,这样就会造成页面的渲染不连贯,严重影响用户体验。
因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程无需等待这些耗时任务执行完成(此时这些耗时任务正在执行中),先运行排在后面的任务,等到这些耗时任务有了结果后,再回过头执行他们,因此,所有任务可以分成两种,一种是同步任务,另一种是异步任务。(总结:因为是单线程,所以必须异步。)

同步任务和异步任务是什么?

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
异步任务指的是,不进入主线程、而进入"任务队列" 的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该异步任务才会进入主线程执行。

同步任务和异步任务的区别

他们俩的区别就是在这条主线程上执行顺序的不同。全部同步任务执行完成后,才会去执行异步任务,遇到同步任务,如果他不执行,程序会卡在那,后面的任务无法执行,遇到异步任务,程序不会卡住,而是继续执行后面的任务。

总结:如果同步任务没有执行完,异步任务是不会执行的。

同步任务举例

    console.log('A');
    alert('haha'); 
    console.log('B');

alert函数是同步任务,我只有点击了确认,才会继续打印B。 

异步任务举例

    console.log(1);
    setTimeout(function () {
        console.log(2);
    }, 0);
    console.log(3);

先执行同步任务console.log(1),遇到异步任务setTimeout,要挂起,执行同步任务console.log(3),全部的同步任务执行完毕后,再来执行异步任务console.log(2)

    console.log('A');
    setTimeout(function () {
        console.log('B');
    })
    while (1) {

    }

上方代码的打印结果仍然是A,不会打印B。因为while是同步任务,setTimeout是异步任务,所以还是那句话:如果同步任务没有执行完,队列里的异步任务是不会执行的。

单线程又是如何实现异步的呢?

异步的核心就是事件循环机制(Event Loop)。

在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
当 JavaScript 引擎空闲下来,也就是当执行栈已经清空时,JavaScript 引擎才会去查询任务队列中是否有需要执行的任务(或者说回调函数),这就是保证异步代码不会阻塞其他任务执行的关键。

JavaScript 执行机制,别名事件循环(EventLoop)

  1. 所有同步任务都在主线程上执行,行成一个执行栈 
  2. 主线程之外,还存在任务队列,只要异步任务有了结果,就会在任务队列中放置对应的任务(回调函数),等待执行
  3. 一旦执行栈中的所有同步任务执行完毕,js引擎会判断任务队列中是否有任务可以执行,如果有,则该任务结束等待状态,进入执行栈,开始执行 ;
  4. 主线程不断的重复上面的第三步;

总结:只要主线程空了,就会去读取”任务队列”,这个过程是循环不断的,这就是JavaScript的异步执行机制,也叫事件循环。

注意点:

  1. 异步任务和异步任务的回调函数不一样,任务队列存放的是异步任务的回调函数。
  2. 放入任务队列的时机并不是一遇到异步任务就放入,若遇到异步任务,则将其放置到宿主环境中。如果在宿主环境中的异步任务达到了执行的条件,则将所要执行的回调函数放入任务队列中,比如说遇到了一个1s的定时器setTimeout,那就是1s后才将回调函数放到任务队列中。
  3. 栈是一种先出后出的数据结构,压在栈底的最慢被执行,队列是一种先进先出的数据结构,先去到队列的任务,优先被执行。

异步任务有哪些

JS 的异步是通过回调函数实现的
异步任务分为宏任务和微任务,也就是任务队列分为 微任务队列 和 宏任务队列。
宏任务: 整体代码script,setTimeout,setInterval,ajax 等等
微任务: Promise,async,await,nextTick 等等

        console.log('同步任务1');
        setTimeout(() => {
            console.log('settimeout');
        })
        new Promise((resolve, reject) => {
            console.log('同步任务2');
            resolve()
        }).then(value => {
            console.log('promise');
        })
        async function fn(){
            console.log('同步任务3');   //await之前的代码也是同步任务
            await 123;
            console.log('async');
        }
        fn()

结果:同步任务1 => 同步任务2 => 同步任务3 => promise => async  => settimeout

注:微任务执行时机比宏任务早!

执行顺序:整体代码(宏任务)进入执行栈后,开始执行代码。等所有同步任务执行完成后,会查看微任务的【事件队列】,有微任务的话则微任务进入执行栈,开始执行,接着再去读取宏任务的事件队列,有宏任务的则宏任务就进入执行栈,开始执行。一直重复这些步骤。

易错题

    for (var i = 0; i < 3; i++) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    }

1s后直接输出3个3

分析:for 循环是同步任务,setTimeout是异步任务。for循环每次遍历的时候,遇到settimeout,就先暂留着,等同步任务全部执行完毕(此时,i已经等于3了),再执行异步任务。

    for (var i = 0; i < 3; i++) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    }
    console.log(i);

第 1 个 3 直接输出,1 秒之后,连续输出 3 个 3。

分析:循环执行过程中,几乎同时设置了 3 个定时器,这些定时器都会在 1 秒之后触发,而循环完的输出是立即执行的

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值