找回密码
 立即注册

JavaScript同步、异步、回调执行顺序分析

Mr.HOU 发表于 2018-2-6 21:13:36 | 显示全部楼层 |阅读模式
之所以会写这篇文章,是因为在做笔试题的时候,会遇到一题很经典的题目,关于setTimeout的输出结果,下面我们先来看一道题目:
  1. for (var i = 0; i < 5; i++) {

  2.     setTimeout(function() {

  3.         console.log(i);

  4.     }, 1000);

  5. }



  6. console.log(i);
复制代码

我相信只要是做过前端笔试题的都见过这样的题目,那么输出的结果是什么呢?
          第一种可能的答案:0 1 2 3 4 5 5
          第二种可能的答案:5 5 5 5 5 5 5(后面每个5隔一秒输出)
    显然第二种结果是正确的,接下来我们分析一下这个题目。首先看一下目前大家都在用的一个口令或者说方法:
         
同步优先、异步靠边、回调垫底
          用公式表达就是:同步 => 异步 => 回调
    现在根据这个口令我们来分析一下结果为什么是这个:
    1)for循环和循环体外部的console是同步的,所以先执行for循环,再执行外部的console.log。(同步优先)
    2)for循环里面有一个setTimeout回调,他是垫底的存在,只能最后执行。(回调垫底)
    那么,为什么我们最先输出的是5呢?
    这个也是非常好理解,for循环先执行,但是不会给setTimeout传参(回调垫底),等for循环执行完,就会给setTimeout传参,而外部的console打印出5是因为for循环执行完成了。
    那么我们要如何能够输出0 1 2 3 4 5呢?
    目前我所知道的方法有两种,第一种是利用let的方法,第二种是利用闭包的方法。
1、利用let

  1. for (let i = 0; i < 5; ++i) {

  2.     setTimeout(function() {

  3.         console.log(i);

  4.     }, 1000);

  5. }
复制代码

此时的输出结果是:

0
1
2
3
4

但是现在是隔一秒之后依次输出0 1 2 3 4 5,如果想要每隔一秒输出这样的结果,可以将程序修改为如下:

  1. for (let i = 0; i < 5; ++i) {

  2.     setTimeout(function() {

  3.         console.log(i);

  4.     }, i*1000);

  5. }
复制代码

2、闭包的方法
  1. for (var i = 1; i <=20; i++){    (function (i) {        setTimeout(function timer() {            console.log(i);        }, i*1000)    })(i);}
复制代码

结果如上,这里可以使用闭包的知识进行解释,也可以用作用域辅助理解。

    由于 var i = xxx 是函数级别作用域,这里通过一个立即函数将变量 i 传入其中,使其包含在这一函数的作用域中。而在每次循环中,此立即函数都会将传入的 i 值保存下来,因而其循环展开结果为:

  1. (function(){    var count = 0;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()(function(){    var count = 1;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()(function(){    var count = 2;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()(function(){    var count = 3;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()(function(){    var count = 4;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()(function(){    var count = 5;    setTimeout( function timer() {        console.log(count);    }, count * 1000 );})()
复制代码

上面主要讲了同步和回调执行顺序的问题,接着我就举一个包含同步、异步、回调的例子。
  1. let a = new Promise(  function(resolve, reject) {    console.log(1)    setTimeout(() => console.log(2), 0)    console.log(3)    console.log(4)    resolve(true)  })a.then(v => {  console.log(8)}) let b = new Promise(  function() {    console.log(5)    setTimeout(() => console.log(6), 0)  }) console.log(7)
复制代码

在看正确结果之前,我先进行分析题目(同步 => 异步 => 回调):
    1)看同步代码:a变量是一个Promise,一开始大家都会以为Promise不是异步吗?不不,其实Promise是异步的,是指他的then()和catch()方法,Promise本身还是同步的,所以这里先执行a变量内部的Promise同步代码。(同步优先)
    console.log(1)
    setTimeout(() => console.log(2), 0) //回调
    console.log(3)
    console.log(4)
    2)Promise内部有4个console,第二个是一个setTimeout回调(回调垫底,所以暂时不输出)。所以这里先输出1,3,4,回调的方法丢到消息队列中排队等着。
    3)接着执行resolve(true),进入then(),then是异步,下面还有同步没执行完呢,所以then也去消息队列排队等候。(异步靠边)
    4)b变量也是一个Promise,和a一样,同样是同步的,执行内部的同步代码,输出5,setTimeout是回调,去消息队列排队等候,这里输出5。
    5)最下面同步输出7。
    6)现在同步的代码执行完了,JavaScript就跑去消息队列呼叫异步的代码:异步,出来执行了。这里只有一个异步then,所以输出8。
   7)此时,异步也over,轮到回调函数:回调,出来执行了。这里有2个回调在排队,他们的时间都设置为0,所以不受时间影响,只跟排队先后顺序有关。则先输出a里面的回调2,最后输出b里面的回调6。
   8)最终输出结果就是:1、3、4、5、7、8、2、6。
到这里,解释结束。关于执行顺序还是多去实践和理解,JavaScript博大精深,不是一句话就能概括出来的。
u=4011340030,1659823413&amp;fm=26&amp;gp=0.jpg.png

THE END

除本站原创文章外其他内容由会员自行上传,我们不对其内容的准确性、真实性及合法性负责。

任何单位或个人认为作品内容可能涉嫌侵犯其合法权益的,可及时联系我们,我们将会尽快处理。

标注“原创”标识的内容版权归本站所有,转载时请注明出处。

本页网址: http://it0470.cn/thread-1-1-1.html

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

售前咨询

微信联系我,扫一扫

售后服务

鸿雁科技售后客服