美文网首页
JS三座大山之异步和单线程

JS三座大山之异步和单线程

作者: 我向你奔 | 来源:发表于2018-02-25 11:39 被阅读166次

    什么是异步?

    我们通过代码来感受一下异步是什么

            console.log("start");
            setTimeout(function () {
                console.log("setTimeout");
            }, 1000);
            console.log("end");
    

    在不了解异步的情况下,可能会认为打印的顺序就是start,setTimeout,end。但事实上呢?


    QQ图片20180223203539.png

    此时结果并不是我们想的那样,而是按照start,end,setTimeout的顺序打印,仔细想想会发现这种打印方式不会阻塞,如果按照start,setTimeout,end的顺序执行就会在打印完start后等待一秒钟再执行setTimeout,end,在此期间程序就会被阻塞。我们再看一段代码对比一下异步和同步的区别

            console.log("100");
            alert("200")
            console.log("300");
    
    QQ图片20180223205817.png
    QQ图片20180223205851.png

    现在的代码就成了你什么时候点击确定按钮,什么时候才会执行下面的代码。此时我们就可以看出同步和异步的区别了:
    同步会阻塞线程,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;异步是异步代码会被放入一个任务队列,等到所有其他代码执行后才进行,而不会阻塞线程。

    何时需要异步?

    在可能发生等待的情况,在等待时我们不能什么都不做,就像alert一样阻塞程序进行,因此,所有的“需要等待的情况”都需要异步。
    那什么时候需要等待?
    ① 定时任务:setTimeout,setInterval
    ② 网络请求:Ajax请求,动态<img>加载
    ③ 事件绑定
    我们先来看一个Ajax请求的例子

           console.log("start");
           $.ajax({
               type: "GET",
               url: 'data.json', 
               dataType: "json",
               success: function (data) {
                   console.log(data);
               }
           })
           console.log("end");
    
    QQ图片20180224004605.png

    <img>加载示例

    console.log("start");
    var img = document.createElement("img");
    img.onload = function () {
          console.log("loaded")
    }
    img.src = "01.jpg";
    console.log("end");
    

    事件绑定示例

    console.log("start");
    document.getElementById("btn1").addEventListener("click", function () {
          alert("clicked");
    })
    console.log("end");
    
    QQ图片20180224153345.png
    QQ图片20180224153403.png

    为什么会产生异步?

    因为js语言的特点是单线程,也就是说,同一个时间只能做一件事,得按顺序一个一个来。我们用之前例子再来看看

    console.log("100");
    setTimeout(function () {
          console.log("200");
    });
    console.log("300");
    

    分析一下执行步骤:① 执行第一行,打印100;② 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点:不能同时干两件事);③ 执行最后一行,打印300;④ 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行的;⑤ 发现暂存的setTimeout函数无需等待时间,于是立即执行。

    总结一下:
    同步和异步的区别:同步会阻塞代码,异步不会;例子就是alert和setTimeout,alert是同步,setTimeout是异步。
    前端使用异步的情景:① 定时任务:setTimeout,setInterval;② 网络请求:Ajax请求,动态<img>加载;③ 事件绑定

    为什么JS引擎是单线程

    JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    任务队列

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

    如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

    JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

    于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    "任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

    异步执行

    具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

    (1)所有同步任务都在主线程上执行,形成一个执行栈。
    (2)主线程之外,还存在一个"任务队列"(task queue),只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
    (4)主线程不断重复上面的第三步。

    只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
    主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

    相关文章

      网友评论

          本文标题:JS三座大山之异步和单线程

          本文链接:https://www.haomeiwen.com/subject/jpjztftx.html