總聽到這麼一個詞語:回調函數。
對於它的瞭解,只知道在微信的網頁受權用到了回調,以及在Angular中能夠用觀察者模式進行.subscribe訂閱,但對於它原理的理解,倒是一團漿糊。直到昨天開會時,忽然被問到回調函數的知識,我才意識到本身真的不理解。算法
咱們先從最簡單的寫法入手,一步一步走向回調函數。
(若是熟悉語法,請跳到第二節)編程
最簡單的方法就是,在Chrome控制檯直接輸入。
下文的Demo都在瀏覽器中演示。瀏覽器
(說明:這裏的"函數"至關於面向對象的"方法")微信
/* 定義名爲test的函數 傳入的參數是a 功能:輸出傳入的變量 */ var test = function(a){ console.log(a); }; //調用方法,輸出HelloWorld test('helloworld');
成功輸出告終果。函數
很簡單:測試
/* 定義函數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();
咱們先看一個寫死的函數: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'這個字符串。
圖片顯示,這兩種方式的結果同樣。
普通函數,參數是數據,在調用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);} );
咱們剛纔已經知道,用傳入的方法操做固定字符串,這個字符串就是傳進來的函數的參數。
若是要用一個函數操做兩個字符串呢?
——把傳入的剪頭函數定義兩個參數。
上圖中,傳進去的剪頭函數須要兩個變量,那麼回調的時候就得傳進去兩個變量,對應關係如上圖。
若是要對同一個字符串執行兩種不一樣的操做呢?
——傳入兩個箭頭函數
上圖中,傳進去兩個函數,對同一字符串操做,就實現了用兩種不一樣方式操做同一字符串。
把上面兩種結合一下:
// 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) } );
請結合以前的知識,自行理解上述代碼。
回調嵌套在實際生產中使用的不多,但這並不妨礙它做爲咱們深入理解回調函數的一種方式。
比較下面三個函數:
//普通函數 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')});
剛纔說了,傳進去的函數abc須要接收函數的能力,而再看接收的函數,正是{console.log()},也就是具有輸出的功能,因此只須要在接收到函數aFunction以後,用這個aFunction函數處理'HelloWorld'字符串就能夠了。
成功輸出告終果:
怕上面沒說清楚,最後用多圖流,再說一下回調嵌套的步驟。
這是原始代碼,定義了一個test,要求經過調用test來輸出HelloWorld:
第一步,調用test(),傳入函數,
傳入以後,
abc = function(aFunction) {aFunction('HelloWorld')}
第二步,用傳進來的abc處理另外一個函數,須要把另外一個函數做爲參數aFunction,傳到abc中,此時:
aFunction = Function(def){console.log(def);}
第三步,abc接收到aFunction後,用aFunction來操做'HelloWorld'字符串,把字符串傳到aFunction中,此時:
def = 'HelloWorld';
第四步,執行console.log(def),輸出'HelloWorld'
到此爲止,若是弄懂了回調函數嵌套,咱們對回調函數的理解就差很少了。
在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函數:
咱們遇到什麼回調函數,也不要怕,微笑着面對他,消除恐懼的最好辦法,就是明白回調函數是在已經有數據的前提下,傳入一個方法,而後用咱們傳入的方法去操做那個已經存在的數據,堅持就是勝利,加油,奧利給!!!!!!!