從零起步,真正理解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、總結

咱們遇到什麼回調函數,也不要怕,微笑着面對他,消除恐懼的最好辦法,就是明白回調函數是在已經有數據的前提下,傳入一個方法,而後用咱們傳入的方法去操做那個已經存在的數據,堅持就是勝利,加油,奧利給!!!!!!!

相關文章
相關標籤/搜索