<sup id="sjju3"><noscript id="sjju3"></noscript></sup>
    <big id="sjju3"></big>

  • <blockquote id="sjju3"></blockquote>
    <blockquote id="sjju3"></blockquote>

      <td id="sjju3"></td>

      <big id="sjju3"></big>
        <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
      1. 如何通过setTimeout理解JS运行机制详解

         更新时间:2019年03月23日 11:15:37   作者:番茄沙司   我要评论

        这篇文章主要给大家介绍了关于如何通过setTimeout理解JS运行机制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用js具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧


        setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行。它返回一个整数,表示定时器timer的编号,可以用来取消该定时器。

        例子

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

        问:最后的打印顺序是什么?(如果不了解js的运行机制就会答错)

        正确答案:1 3 2

        解析?#20309;?#35770;setTimeout的执行时间是0还是1000,结果都是先输出3后输出2,这就是面试官常常考查的js运行机制的问题,接下来我们要引入一个概念,JavaScript 是单线程的。

        二、 JavaScript 单线程

        JavasScript引擎是基于事件驱动和单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加?#28304;?#29702;,浏览器无论什?#35789;?#20505;都只有一个JS线程在运行程序,即主线程。

        通俗的说:JS在同一时间内只能做一件事,这也常被称为 “阻塞式执行”。

        任务队列

        那么单线程的JavasScript是怎?#35789;?#29616;“非阻塞执行”呢?

        答:异步容?#36164;?#29616;非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步?#32479;?#20102;必然的选择。
        诸如事件点击触发回调函数、ajax通信、计时器这种异步处理是如何实现的呢?

        答:任务队列

        所有任务可以分成两种,一?#36136;?#21516;步任务(synchronous),另一?#36136;?#24322;步任务(asynchronous)。

        任务队列:一个先进先出的队列,它里面存放着各?#36136;?#20214;和任务。

        同步任务

        同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

        • 输出
        • 如:console.log()
        • 变量的声明
        • 同步函数:如果在函数返回的时候,调用者就能够拿到预期的返回值或者看到预期的效果,那么这个函数就是同步的。

        异步任务

        • setTimeout和setInterval
        • DOM事件
        • Promise
        • process.nextTick
        • fs.readFile
        • http.get
        • 异步函数:如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。

        除此之外,任务队列又分为macro-task(宏任务)与micro-task(微任务),在ES5标准中,它们被?#30452;?#31216;为task与job。

        宏任务

        1. I/O
        2. setTimeout
        3. setInterval
        4. setImmdiate
        5. requestAnimationFrame

        微任务

        1. process.nextTick
        2. Promise
        3. Promise.then
        4. MutationObserver

        宏任务和微任务的执行顺序

        一?#38382;?#20214;循环中,先执行宏任务队列里的一个任务,再把微任务队列里的所有任务执行完毕,再去宏任务队列取下一个宏任务执行。

        注:在当前的微任务没有执行完成时,是不会执行下一个宏任务的。

        三、setTimeout运行机制

        setTimeout 和 setInterval的运行机制是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。

        这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。

        优先关系:异步任务要挂起,先执行同步任务,同步任务执行完毕才会响应异步任务。

        四、进阶

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

        大家再猜一下这段程序输出的结果会是什么?

        答:A

        注:建议先注释掉while循环代码块的代码,执行后强制删除进程,不然会造成“假死”。

        同步队列输出A之后,陷入while(true){}的死循环中,异步任务不会被执行。

        类似的,有时addEventListener()方法监听点击事件click,用户点了某个按钮会卡死,就是因为当前JS正在处理同步队列,无法将click触发事件放入执行栈,不会执行,出现“假死”。

        五、定时获取接口更新数据

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

        输出结果为,隔1s后一起输出:4 4 4 4

        for循环是一个同步任务,为什么连续输出四个4?

        答:因为有队列插入的时间,即使执行时间从1000?#26576;?,还是输出四个4。

        那么这个问题是如何产生和解决的呢?请接着阅读

        异步队列执行的时间

        执行到异步任务的时候,会直接放到异步队列中吗?

        答案是不一定的。

        因为浏览器有个定时器(timer)模块,定时器到了执行时间才会把异步任务放到异步队列。
        for循环体执行的过程中并没有把setTimeout放到异步队列中,只是交给定时器模块了。4个循环体执行速度非常快(不到1毫秒)。定时器到了设置的时间才会把setTimeout语句放到异步队列中。

        即使setTimeout设置的执行时间为0毫秒,也按4毫秒算。

        这就解释了上题为什么会连续输出四个4的原因。

        HTML5 标准规定了setTimeout()的第二个参数的最小值,即最短间隔,不得低于4毫秒。如果低于这个值,就会自动增?#21360;?#22312;此之前,?#20064;?#26412;的浏览器都将最短间隔设为10毫秒。

        利用闭包实现 setTimeout 间歇调用

        for (let i = 0; i < 4; i++) {
         (function (j) {
         setTimeout(function () {
          console.log(j);
         }, 1000 * i)
         })(i);
        }

        执行后,会隔1s输出一个值,?#30452;?#26159;:0 1 2 3

        • 此方法巧妙利用IIFE声明即执行的函数表达式来解决闭包造成的问题。
        • 将var改为let,使用了ES6语法。

        这里也可以用setInterval()方法来实现间歇调用。

        详见:setTimeout和setInterval的区别

        利用JS中基本类型的参数传递是按值传递的特征实现

        var output = function (i) {
         setTimeout(function () {
         console.log(i);
        
         }, 1000 * i)
        }
        for (let i = 0; i < 4; i++) {
         output(i);
        }

        执行后,会隔1s输出一个值,?#30452;?#26159;:0 1 2 3

        实现原理:传过去的i值被复制了。

        基于Promise的解决方案

        const tasks = [];
        
        const output = (i) => new Promise((resolve) => {
         setTimeout(() => {
         console.log(i);
         resolve();
         }, 1000 * i);
        
        });
        
        //生成全部的异步操作
        for (var i = 0; i < 5; i++) {
         tasks.push(output(i));
        }
        //同步操作完成后,输出最后的i
        Promise.all(tasks).then(() => {
         setTimeout(() => {
         console.log(i);
         }, 1000)
        })

        执行后,会隔1s输出一个值,?#30452;?#26159;:0 1 2 3 4 5

        优点:提高了代码的可读性。

        注意:如果没有处理Promise的reject,会导致错误被丢进黑?#30784;?br />

        使用ES7中的async await特性的解决方案(推荐)

        const sleep = (timeountMS) => new Promise((resolve) => {
         setTimeout(resolve, timeountMS);
        });
        
        (async () => { //声明即执行的async
         for (var i = 0; i < 5; i++) {
         await sleep(1000);
         console.log(i);
         }
        
         await sleep(1000);
         console.log(i);
        
        })();

        执行后,会隔1s输出一个值,?#30452;?#26159;:0 1 2 3 4 5

        六、事件循环 Event Loop


        主线程从任务队列中读取事件,这个过程?#33540;?#29615;不断的,所以整个的这种运行机制又称为Event Loop。

        有时候 setTimeout明明写的延时3秒,?#23548;?#21364;5,6秒才执行函数,这?#36136;?#22240;为什么?

        答:setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是?#23548;?#36824;是空?#23567;?br />

        浏览器的JS引擎遇到setTimeout,拿走之后不会立即放入异步队列,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。js引擎发现同步队列中没有要执行的东西了,即运行栈空了就从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。

        这时setTimeout函数体就变成了运行栈中的执行任务,运行栈空了,再监听异步队列中有没有要执行的任务,如果有就继续执行,如此循环,就叫Event Loop。

        七、总结

        JavaScript通过事件循环和浏览器各线程协调共同实现异步。同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性。

        知识点梳理:

        • 理解JS的单线程的概念:一段时间内做一件事
        • 理解任务队列?#21644;?#27493;任务、异步任务
        • 理解 Event Loop
        • 理解哪些语句会放入异步任务队列
        • 理解语句放入异步任务队列的时机

        最后,希望大家阅后有所?#26632;瘛?#129312;

        好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

        相关文章

        最新评论

        2018白小姐一肖中特马
        <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
        <big id="sjju3"></big>

      2. <blockquote id="sjju3"></blockquote>
        <blockquote id="sjju3"></blockquote>

          <td id="sjju3"></td>

          <big id="sjju3"></big>
            <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
          1. <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
            <big id="sjju3"></big>

          2. <blockquote id="sjju3"></blockquote>
            <blockquote id="sjju3"></blockquote>

              <td id="sjju3"></td>

              <big id="sjju3"></big>
                <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>