从零起步,真正理解Javascript回调函数

零、前言

总听到这么一个词语:回调函数。
对于它的了解,只知道在微信网页受权用到了回调,以及在Angular中能够用观察者模式进行.subscribe订阅,但对于它原理的理解,倒是一团浆糊。直到昨天开会时,忽然被问到回调函数的知识,我才意识到本身真的不理解。算法

1、基础知识:JavaScript标准写法

咱们先从最简单的写法入手,一步一步走向回调函数。
(若是熟悉语法,请跳到第二节)编程

如何测试JS代码

最简单的方法就是,在Chrome控制台直接输入。
图片.png
下文的Demo都在浏览器中演示。浏览器

JS定义函数

(说明:这里的"函数"至关于面向对象的"方法"微信

/*  定义名为test的函数
    传入的参数是a
    功能:输出传入的变量  */

var test = function(a){
    console.log(a);
};

//调用方法,输出HelloWorld

test('helloworld');

图片.png
成功输出告终果。函数

函数调用函数

很简单:测试

/*  定义函数sum
    传入两个参数a,b
    做用:求a,b的和   */
var sum = function(a,b)
{
    return a+b;
};

/*  定义函数test
    调用sum
    传入参数1,2 */
var test = function()
{
    result = sum(1,2);
    console.log(result);
}

//调用test,启动程序
test();

图片.png

2、数据和算法

咱们先看一个写死的函数:this

var test = function( ){
    console.log("HelloWorld");
};

这个函数事实上是没有意义的,由于它没有输入,不会变化,不管重复运行多少次,它的结果都是同样的。
一个函数里面,既有算法又有数据:"算法"指的是输出字符串的这个操做,"数据"这的是输出的内容'HelloWorld'。因此若是咱们想让这个函数发挥做用,就要让它能够变化spa

(备用)咱们能够借助最原始时代的编程思想来理解:在早期的计算机思想中,数据算法是分离的,算法被写成一段段代码,数据是用来被算法操做的。3d

程序等于数据加算法。借助这种想法,咱们认为,在一个函数中,只要数据算法其中一个是能够变化的,那么函数就是有意义的。code

改变数据——普通的函数

咱们先想到的确定是改变数据,把一个写死的函数加上参数,它就变“活了”。

//写死的函数
var test = function( ){
    console.log("HelloWorld");
};
test();

//加上参数
var test = function(string){
    console.log(string);
};
test('HelloWorld');

这样,把一段永远不会变化的代码,变成了能够在调用时根据不一样输入来得到不一样输出的程序。

改变算法——回调函数

初次揭开回调函数的面纱:
把一个写死的函数变活,能够传入数据,用相同的算法对不一样的数据进行处理;固然也能够传入一个算法,用不一样的算法对相同的数据进行处理,然后者,正是回调函数。

用一句话归纳:在直接调用函数A()时,把另外一个函数B()做为参数,传入函数A()里面,以此来经过函数A()间接调用函数B()。

比较下面两个函数的异同:

//函数1
var test = function(abc){
    console.log(abc);
};
//函数1的调用
test('HelloWorld');

//函数2
var test = function(abc){
    abc('Helloworld');
};
//函数2的调用
test( function(words) {console.log(words);}  );

这两个函数有着一样的参数,都是abc,只不过,函数1是普通函数,参数abc是做为数据传入的,而后用函数1的语句来操做abc;
而函数2是回调函数,参数是做为算法传入的,而后用传进来的这个函数abc来操做'HelloWorld'这个字符串。
图片.png
图片显示,这两种方式的结果同样。
图片.png

普通函数,参数是数据,在调用test()时,传入HelloWorld字符串,那么abc就是这个字符串,test函数对传入的字符串执行了输出操做;
而回调函数,参数是函数,在调用test()时,传入输出字符串的方法,那么abc就是输出字符串的方法,test函数将会用传进来的输出字符串的方法对一个固定字符串HelloWorld执行操做。与此同时,这个HelloWorld字符串,又成了传到test函数的这个function(words) {console.log(words);}函数的参数,若是传入的参数有名字的话,在调用test时,在函数内部,等价于发生了以下操做:

{
    //调用test传进来一个函数以后,abc就是那个函数
    abc = function(words) {console.log(words);};
    //用abc操做字符串,'HelloWorld'变成了传进来的函数的参数
    abc('HelloWorld');
}

三,深刻回调函数

咱们已经理解了初级阶段的回调,但目前,传入的函数还处于function(){}的形式,这种写法是原始写法,至关于定义了一个新函数而后传进去,不只脱离生产环境,并且有不少局限(好比this.做用域问题),下一步,要把这种形式改成剪头函数。

一个函数只用一次,而且不须要直到它的名字时,能够用匿名函数来简化。
在解决this.做用域时,又将匿名函数转化为箭头函数

//如下两种写法等价

function (words) {console.log(words);}

(words) => {console.log(words);}

能够看出,箭头函数省略了function标识,只留下了参数words和函数体console.log(),两者用箭头链接。
这种省略写法更贴近生产环境:

//函数2
var test = function(abc){
    abc('Helloworld');
};
//函数2的调用
test( (words) => {console.log(words);}  );

了解参数的对应关系

图片.png
咱们刚才已经知道,用传入的方法操做固定字符串,这个字符串就是传进来的函数的参数。

若是要用一个函数操做两个字符串呢?
——把传入的剪头函数定义两个参数。

图片.png
图片.png
上图中,传进去的剪头函数须要两个变量,那么回调的时候就得传进去两个变量,对应关系如上图。

若是要对同一个字符串执行两种不一样的操做呢?
——传入两个箭头函数

图片.png
图片.png

上图中,传进去两个函数,对同一字符串操做,就实现了用两种不一样方式操做同一字符串。

把上面两种结合一下:

//
var test = function(abc, def){
    abc('HelloWorld1', 'HelloWorld2');
    def('HelloWorld1', 'HelloWorld2');
};
//
test((words1,words2) => {
        console.log('我是箭头1,我输出'+words1);
        console.log('我是箭头1,我输出'+words2);
    },
    (words1,words2)=> {
        console.log('我是箭头2,我输出'+words1);
        console.log('我是箭头2,我输出'+words2)
    }
    );

图片.png
请结合以前的知识,自行理解上述代码。

4、回调函数嵌套

回调嵌套在实际生产中使用的不多,但这并不妨碍它做为咱们深入理解回调函数的一种方式。
比较下面三个函数:

//普通函数
var test = function(abc){
    console.log(abc);
}

//回调函数
var test = function(abc){
    abc('HelloWorld');
};

//回调函数嵌套
var test = function(abc){
    abc( (def) => {console.log(def);} );
}

练习题:问,以上三种状况下,若是分别调用三个函数,输出HelloWorld字符串?

第一种早就学会了,直接调用就能够:

//普通函数
test('HelloWorld');

第二种也已经会了,有了HelloWorld的数据,咱们须要传进去的是操做这个数据的方法,因此:

//回调函数
test( (words) => {console.log(words);});

主要说的是第三种,
回调函数是传入一个函数,用传入的函数abc去操做一个数据'HelloWorld'。这个被操做的数据,做为传进来的函数abc的参数。
而再看回调函数嵌套,它也是传进去一个函数abc,但不一样的是,它是用这个传进来的函数去操做另外一个函数。
此时,咱们传入的abc函数须要一种能够接收函数的能力,而再也不是接收变量的能力。
因此怎么办?——在传进去的这个函数abc中再使用一次回调,使得abc接收的参数是一个函数,而不是一个变量:

//回调函数嵌套
test( (aFunction) => {aFunction('HelloWorld')} );

//若是看不明白,把箭头函数复原,以下
test( function(aFunction) {aFunction('HelloWorld')});

图片.png
刚才说了,传进去的函数abc须要接收函数的能力,而再看接收的函数,正是{console.log()},也就是具有输出的功能,因此只须要在接收到函数aFunction以后,用这个aFunction函数处理'HelloWorld'字符串就能够了。
成功输出告终果:
图片.png

5、图解具体步骤

怕上面没说清楚,最后用多图流,再说一下回调嵌套的步骤。

这是原始代码,定义了一个test,要求经过调用test来输出HelloWorld:
图片.png

第一步,调用test(),传入函数,
传入以后,

图片.png

abc = function(aFunction) {aFunction('HelloWorld')}

第二步,用传进来的abc处理另外一个函数,须要把另外一个函数做为参数aFunction,传到abc中,此时:

图片.png

aFunction = Function(def){console.log(def);}

第三步,abc接收到aFunction后,用aFunction来操做'HelloWorld'字符串,把字符串传到aFunction中,此时:

图片.png

def = 'HelloWorld';

第四步,执行console.log(def),输出'HelloWorld'
图片.png

到此为止,若是弄懂了回调函数嵌套,咱们对回调函数的理解就差很少了。

6、生产环境中的观察者

在Angular中,有一个.subcribe订阅,这个就是回调,之前这知道这么用,但如今咱们能够解释一下它的原理了!

先上代码:

//向8080端口的helloWorld路径发起请求
httpClient.get('http://localhost:8080/helloWorld')
  .subscribe(
    function success(data) {
      console.log('请求成功');
      console.log(data);
    },
    function error(data) {
      console.log('请求失败');
      console.log(data);
    });

httpClient的做用是发起HTTP请求,而且记录请求状态。
.get设定请求地址。
.subscript是设定请求完成的操做。

咱们的目的是,输出请求以后的信息,以便让咱们知道请求是否成功。而且咱们已经知道httpClient会记录请求的状态,这个“状态”就是个变量。
既然变量就在那里放着不动,咱们只须要想办法,去操做这个状态的变量就能够了,——传入两个函数,success和error,在请求成功以后调用success,若是失败调用error。再次强调,它已经有数据了,咱们传进去的是函数,请求完毕后,就会用咱们传入的函数去操做这个请求状态。咱们传入的“输出”,他就“输出”这个状态!

//向8080端口的helloWorld路径发起请求
httpClient.get('http://localhost:8080/helloWorld')
  .subscribe(
    function success(data) {
      console.log('请求成功');
      console.log(data);
    },
    function error(data) {
      console.log('请求失败');
      console.log(data);
    });

//系统中的subscirbe函数
function subscribe(success,error)
{
    request_success;//请求是否成功
    request_data;//请求数据

    if (request_success == 1) {
        success(request_data);
    }
    else{
        error(request_data);
    }
}

为了便于理解,我写了一个假的subscribe函数:
图片.png

7、总结

咱们遇到什么回调函数,也不要怕,微笑着面对他,消除恐惧的最好办法,就是明白回调函数是在已经有数据的前提下,传入一个方法,而后用咱们传入的方法去操做那个已经存在的数据,坚持就是胜利,加油,奥利给!!!!!!!

相关文章
相关标签/搜索