关于js异步回调

首先赋上看的比较好的链接:

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

js的异步和回调

首先要明白的是Js语言的执行环境是“单线程”的。而所谓的“单线程”指的是一次只能完成一个任务。如果有多个任务,则需要排队,只能等到前一个任务完成之后,再执行下一个任务,直到所有任务执行完毕。

但是这跟js异步回调有什么关系呢?

关系还挺大的。如果在执行任务的时候,其中某个任务耗时很长,又由于后面的任务只能排队等着,所以这势必会拖延整个程序的执行,导致整个页面卡在这个任务点上,其他任务无法执行。这就好比如,用户在网站页面打击了一个评论,而响应这个评论事件很耗时,导致页面卡在这个地方不动,要是你是用户,你能忍?婶都不能忍啊!

所以这就是js异步回调该起作用的时候了。

好,下面就先谈谈同步、异步、回调的基本概念了。这里我先引用一位博主的例子,例子如下:

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

console.log(i);

同步:js程序运行是同步的,也就是按照顺序执行书写的代码。在这个例子中,程序先执行完for循环后再执行外部的console.log(i)。

异步:与同步相对的一个概念,意思是在前一个任务没执行完就执行下一个任务。在这个例子中,本来是要写运行for循环里的setTimeout函数的,但是由于要执行setTimeout函数里面的代码要等到1秒后才行,所以这里的异步指的是未执行完setTimeout函数就先执行了外部的console.log(i)。

回调:顾名思义,意思是把一个函数作为参数传入到另一个函数中,那么这个作为参数的函数就叫做回调函数。在这个例子中,setTimeout就相当于回调函数。

那么,回调和异步到底有什么区别呢?它们之间的关系是什么?异步有什么好处?

其实回调只是实现异步的一种方式而已,而实现异步还有事件类方法和promise等。而且可以从上面的例子可以看出,js中的异步其实是伪异步。因为js是单线程的,永远只有一个通道执行代码,而不像其他语言可以多线程异步执行代码。

从异步的概念中可以发现,程序异步运行,可以提高程序运行的效率,不必等一个程序跑完,再跑下一个程序,特别当这两个程序是无关的时候。两个程序在一定时间内,可以是同时运行的。

所以最后这段代码输出为多少呢?我想大家都应该知道了吧,答案是5。

我觉得给出例子的博主的解释很好,下面是她的解释:

   我相信只要是做过前端笔试题的都见过这样的题目,那么输出的结果是什么呢?

          第一种可能的答案: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(let是ES6新特性之一,只在当前作用域有用)

[javascript]  view plain  copy
  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,如果想要每隔一秒输出这样的结果,可以将程序修改为如下:

[javascript]  view plain  copy
  1. for (let i = 0; i < 5; ++i) {  
  2.     setTimeout(function() {  
  3.         console.log(i);  
  4.     }, i*1000);  
  5. }  
2、闭包的方法(不同闭包的童鞋可以查阅链接对js闭包的理解
[javascript]  view plain  copy
  1. for (var i = 1; i <=20; i++){  
  2.     (function (i) {  
  3.         setTimeout(function timer() {  
  4.             console.log(i);  
  5.         }, i*1000)  
  6.     })(i);  
  7. }  
      结果如上,这里可以使用闭包的知识进行解释,也可以用作用域辅助理解。

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

[javascript]  view plain  copy
  1. (function(){  
  2.     var count = 0;  
  3.     setTimeout( function timer() {  
  4.         console.log(count);  
  5.     }, count * 1000 );  
  6. })()  
  7. (function(){  
  8.     var count = 1;  
  9.     setTimeout( function timer() {  
  10.         console.log(count);  
  11.     }, count * 1000 );  
  12. })()  
  13. (function(){  
  14.     var count = 2;  
  15.     setTimeout( function timer() {  
  16.         console.log(count);  
  17.     }, count * 1000 );  
  18. })()  
  19. (function(){  
  20.     var count = 3;  
  21.     setTimeout( function timer() {  
  22.         console.log(count);  
  23.     }, count * 1000 );  
  24. })()  
  25. (function(){  
  26.     var count = 4;  
  27.     setTimeout( function timer() {  
  28.         console.log(count);  
  29.     }, count * 1000 );  
  30. })()  
  31. (function(){  
  32.     var count = 5;  
  33.     setTimeout( function timer() {  
  34.         console.log(count);  
  35.     }, count * 1000 );  
  36. })()  

而且她为了帮助更好的理解,她还举了一个例子,如下:

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。

在这个例子中,她用到了promise,但是我相信还是有很多的童鞋是不懂的,而promise是可以实现异步编程的,实现异步编程也不只有promise一种方法,还有其他的方法,可以查阅链接:


纯属个人理解,不对之处请指正,部分属于转载,已标明出处。



猜你喜欢

转载自blog.csdn.net/charles_tian/article/details/79862571