小昱个人博客
欢迎来到小昱的世界

勤学如春起之苗,不见其增,日有所长;辍学如磨刀之石,不见其损,日有所亏
深入理解异步与回调机制(JS实现)
  • 首页 > >
  • 作者:小昱
  • 2017年8月31日 14:19 星期四
  • 浏览:114
  • 字号:
  • 评论:0
  • 零、写在前面

    在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

    因此回调不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

     

    一、同步回调(阻塞)

    简单的使用一下

    var func1=function(callback){
        //do something.
        (callback && typeof(callback) === "function") && callback();
    }
    var func2=function(){}
    func1(func2);

     

    二、异步回调(非阻塞)

    异步

    先谈谈什么是异步操作,用一个示例说明一下

    先看看同步的加法:

    function Add(a, b){
      return a+b;
    }
    Add(1, 2) // => 3

    改为异步操作:

    function LazyAdd(a){
      return function(b){
        return a+b;
      }
    }
    var result = LazyAdd(1); // ①
    
    // 现在可以写一些别的代码了
    console.log('wait some time, doing some fun'); // ②
    
    
    result = result(2) // => 3 ③
    console.log(result)

    ① result等于一个匿名函数,实际是闭包。

    我们的目的是做一个加法,result中保存了加法的一部分,即第一个参数和之后的运算规则

    通过返回一个持有外层参数a的匿名函数构成的闭包保存至变量result中,这部是异步的关键。

    极端的情况var result = LazyAdd(1)(2);这种极端情况又不属于异步了,它和同步没有区别。

    ② 在这个地方做一些其他事情。

    ③ 这里继续完成前面未完成的事,由于存在闭包,前面的值会存在闭包当中。

     

    异步回调

    function Add(a, b){
        return a+b;
    }
    function LazyAdd(a, cb){
        return function(b){
            cb(a, b);
        }
    }
    var result = LazyAdd(1, Add)
    
    // 在这里做一些其他事情
    
    result = result(2); // => 3

    这里通过闭包保存了a的状态。在这中间可以做一些其他的事情。

     

    一些应用场景

    f2 = function () {
    	console.log('callback')
    }
    
    function f1(callback){
      setTimeout(function () {
        // f1的任务代码
        callback();
      }, 1000);
    }
    
    console.log('1')
    
    f1(f2)
    
    console.log('2')

    这里可以清楚地看到,控制台先打印出1,2,再打印回调函数中的内容。

    采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

    回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

     

    三、Javascript异步编程的4种方法

    ① 回调函数

    ② 事件监听

    var doc = $(document);
    function f2() {
      console.log("done");
    }
    function f1() {
      setTimeout(function () {
        doc.trigger("done")
      }, 1000);
    }
    doc.on("done", f2);
    f1();

    ③ 发布/订阅

    发布订阅:其实和事件监听一模一样。

    观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。

    var Observable = {
      callbacks: [],
      add: function(fn) {
        this.callbacks.push(fn);
      },
      fire: function() {
        this.callbacks.forEach(function(fn) {
          fn();
        })
      }
    }
    Observable.add(function() {
      alert(1)
    })
    
    Observable.add(function() {
      alert(2)
    })
    Observable.fire(); // 1, 2

    ④ Promise对象

    详见阮一峰所写的博文 http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

     

    四、总结

    回调什么时候执行

    回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。

    回调函数的使用场合

    • 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调等等。
    • DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。
    • setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
    • 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现
    • setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

     

     


    参考资料

    详解回调函数——以JS为例解读异步、回调和EventLoop

    javascript的回调函数 同步 异步

    Javascript异步编程的4种方法

      您阅读这篇文章共花了:  
     本文无需标签!
    二维码加载中...
    本文作者:小昱      文章标题: 深入理解异步与回调机制(JS实现)
    本文地址:http://www.xiaoyulive.top/?post=129
    版权声明:若无注明,本文皆为“小昱个人博客”原创,转载请保留文章出处。
    返回顶部| 首页| 碰碰手气| 捐赠支持| 手机版本|后花园

    Copyright © 2016-2017 小昱个人博客 滇ICP备16006294号