曾经,我非常羡慕那些人见人爱的人,我也想要变成那样,可是后来我才明白人见人爱也是需要天赋的,后来我开始默默努力,我想,就算我不能让每个人都喜欢我,至少因为我做的努力能得到别人的尊重。

详解回调函数

 

  什么是回调函数? 下面这个是吗

        function func(para, cb) {
            console.log(para);
            var num = Math.random();
            cb(num);
        }

        var callback = function (num) {
            console.log(num);
        }

        func(1, callback);

  这里,我定义了一个函数,函数接收一个函数作为参数,然后再函数内部调用这个接收的函数。 另外,我定义了传入的函数为callBack,这就是回调了吗?  no,no,no!

  下面是我们经常容易混淆的地方:

  • 定义了一个匿名函数callBack,然后它就是回调函数了?  否
  • func的第二个参数cb,即callback的简称,他就是回调了? 否
  • 只要callback被作为参数,传递到函数中被调用,它就是回调了? 否

  上面的callback函数的调用,实际上就是普通的函数调用,跟回调没有一点关系!

  在《JavaScript设计模式》还是《你不知道的JavaScript》中对闭包的讲解时设计到过这些问题

  

 

  本篇文章参考文章

  什么是回调函数? 知乎

  同步、异步与阻塞、非阻塞怎么理解? 知乎

 

  个人理解:

  同步、异步: 是否有主动通知的功能? 如果会主动通知,即为异步;否则为同步。

  阻塞、非阻塞:  是否在一直等待? 如果是,即为阻塞; 否则为非阻塞。

  回调:英文名为callback,即打电话过来之意。你去一个商店买东西,暂时没有货; 你留下电话, 并委托一件事情---有货了给我打电话。 有货了它就打来电话,然后你去取货。

     在这个过程中,你的电话号码就是回调函数。你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。具体这么多也不重要,重要的是callback才是回调函数,即不是你主动的去调用函数,而是系统主动的调用你写的函数

  

  然后我们再来看之前写的那个函数中的这两条语句:

   var num = Math.random();
   cb(num);

  可以发现,这里我们已经得到了一个随机数,所以cb(num);的时候就直接调用了这个函数,所以也就不存在是系统来调用你的函数了。故你还是主动方,是你调用了函数。除非它给你打了电话(callback),否则是不能称为回调函数的。

    

  以nodejs中的readFile这个api进一步说明:

  fs.readFile(filename, [options], callback) 

  有两个必填的参数 filename 和 callback
  callback是实际程序员要写代码的地方,写它的时候假设文件已经读取到了,该怎么写还怎么写,是API历史上的一次大进步。

  这里的callback才是回调,为什么呢?  

  这是因为我们不知道什么时候filename才能被读取结束,但是我们告诉了它等到你读取结束了就要给我说一声,帮我执行callback。代码如下:

//读取文件'etc/passwd',读取完成后将返回值,传入function(err, data) 这个回调函数。
fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

  这段代码对于人们的疑惑常常是,我怎么知道callback要接收几个参数,参数的类型是什么?
  答:是API提供者事先设计好的,它需要在文档中说明callback接收什么参数。

 

  那么EventLoop是什么呢?我们看下面的例子

function Add(a, b){
    return a+b;
}

function LazyAdd(a, cb){
    return function(b){
        cb(a, b);
    }
}

var result = LazyAdd(1, Add)
// 假设有一个变量button为false,我们继续调用result的条件是,当button为true的时候。
var button = false;

// 常用的办法是观察者模式,派一个人不断的看button的值,
//只要变了就开始执行result(2), 当然得有别人去改变button的值,
//这里假设有人有这个能力,比如起了另外一个线程去做。
while(true){
    if(button){
        result = result(2);
        break;
    }
}

result = result(2); // => 3

  这个实际上就是异步了,cpu开启一个线程来看着什么时候满足条件,一旦满足条件就告诉我们然后执行,而在主线程上还是可以继续做其他的事情。

  所以说回调的好处就是可以实现异步,但是这里明显太麻烦了,如果每次一个异步都要这样,一定效率会有下降。

  于是,这时EventLoop诞生了,派一个人来轮询所有的,其他人都可以把观察条件和回调函数注册在EventLoop上,它进行统一的轮询,注册的人越多,轮询一圈的时间越长。但是简化了编程,不用每个人都写轮询了,提供API变得方便,就像fs.readFile一样简单明白,fs.readFile读取文件’/etc/passwd’,将其注册到EventLoop上,当文件读取完毕的时候,EventLoop通过轮询感知到它,并调用readFile注册时带的回调函数,这里是funtion(err, data)。

  换一个说法再说一遍:在特定条件下,单台机器上用空间换计算。原本的时候callback执行了就不等了,存在一个地方,其他依赖它的,用观察着模式一直盯着它,各自轮询各自的。现在有人出来替大家统一轮询。那么显然这样就可以提高效率。

  总之,整个过程就是异步->回调->EventLoop

  但是callback也是会有问题的,比如下面的回调地狱:

fs.readFile('/etc/password', function(err, data){
    // do something
    fs.readFile('xxxx', function(err, data){
        //do something
            fs.readFile('xxxxx', function(err, data){
            // do something
        })
    })
})

  即嵌套十分严重,在es6中给出了generator, 后续会讲到。

 

posted @ 2017-03-16 19:07  Wayne-Zhu  阅读(1151)  评论(0编辑  收藏  举报

一分耕耘,一分收获。