《鬼谷八荒攻略》捏臉各部位魅力值一覽
概述
在javascript中,函式是第一類物件,這意味著函式可以像物件一樣按照第一類管理被使用。既然函式實際上是物件:它們能被“儲存”在變數中,能作為函式引數被傳遞,能在函式中被建立,能從函式中返回。
因為函式是第一類物件,我們可以在javaScript使用回撥函式。在下面的文章中,我們將學到關於回撥函式的方方面面。回撥函式可能是在JavaScript中使用最多的函式式程式設計技巧,雖然在字面上看起來它們一直一小段JavaScript或者jquery程式碼,但是對於許多開發者來說它任然是一個謎。在閱讀本文之後你能瞭解怎樣使用回撥函式。
回撥函式是從一個叫函數語言程式設計的程式設計正規化中衍生出來的概念。簡單來說,函數語言程式設計就是使用函式作為變數。函數語言程式設計過去 - 甚至是現在,依舊沒有被廣泛使用 - 它過去常被看做是那些受過特許訓練的,大師級別的程式設計師的祕傳技巧。
幸運的是,函式是程式設計的技巧現在已經被充分闡明因此像我和你這樣的普通人也能去輕鬆使用它。函數語言程式設計中的一個主要技巧就是回撥函式。在後面內容中你會發現實現回撥函式其實就和普通函式傳參一樣簡單。這個技巧是如此的簡單以致於我常常感到很奇怪為什麼它經常被包含在講述JavaScript高階技巧的章節中。
什麼是回撥或者高階函式
一個回撥函式,也被稱為高階函式,是一個被作為引數傳遞給另一個函式(在這裡我們把另一個函式叫做otherFunction)的函式,回撥函式在otherFunction中被呼叫。一個回撥函式本質上是一種程式設計模式(為一個常見問題建立的解決方案),因http://www.cppcns.com
下面是一個在jQuery中使用回撥函式簡單普遍的例子:
//注意到click方法中是一個函式而不是一個變數 //它就是回撥函式 $("#btn_1").click(function() { alert("Btn 1 Clicked"); });
正如你在前面的例子中看到的,我們將一個函式作為引數傳遞給了click方法。click方法會呼叫(或者執行)我們傳遞給它的函式。這是JavaScript中回撥函式的典型用法,它在jQuery中廣泛被使用。
下面是另一個JavaScript中典型的回撥函式的例子:
var friends = ["Mike","Stacy","Andy","Rick"]; friends.forEach(function (eachName,index){ console.log(index + 1 + ". " + eachName); // 1. Mike,2. Stacy,3. Andy,4. Rick });
再一次,注意到我們講一個匿名函式(沒有名字的函式)作為引數傳遞給了forEach方法。
到目前為止,我們將匿名函式作為引數傳遞給了另一個函式或方法。在我們看更多的實際例子和編寫我們自己的回撥函式之前,先來理解回撥函式是怎樣運作的。
回撥函式是怎樣運作的?
因為函式在JavaScript中是第一類物件,我們像對待物件一樣對待函式,因此我們能像傳遞變數一樣傳遞函式,在函式中返回函式,在其他函式中使用函式。當我們將一個回撥函式作為引數傳遞給另一個函式是,我們僅僅傳遞了函式定義。我們並沒有在引數中執行函式。我們並不傳遞像我們平時執行函式一樣帶有一對執行小括號()的函式。
需要注意的很重要的一點是回撥函式並不會馬上被執行。它會在包含它的函式內的某個特定時間點被“回撥”(就像它的名字一樣)。因此,即使第一個jQuery的例子如下所示:
//匿名函式不會再引數中被執行 //這是一個回撥函式 $("#btn_1").click(function(){ alert("Btn 1 Clicked"); });
這個匿名函式稍後會在函式體內被呼叫。即使有名字,它依然在包含它的函式內通過arguments物件獲取。
回撥函式是閉包
都能夠將一個回撥函式作為變數傳遞給另一個www.cppcns.com函式時,這個回撥函式在包含它的函式內的某一點執行,就好像這個回撥函式是在包含它的函式中定義的一樣。這意味著回撥函式本質上是一個閉包。
正如我們所知,閉包能夠進入包含它的函式的作用域,因此回撥函式能獲取包含它的函式中的變數,以及全域性作用域中的變數。
實現回撥函式的基本原理
回撥函式並不複雜,但是在我們開始建立並使用回撥函式之前,我們應該熟悉幾個實現回撥函式的基本原理。
使用命名或匿名函式作為回撥
在前面的jQuery例子以及forEach的例子中,我們使用了在引數位置定義的匿名函式作為回撥函式。這是在回撥函式使用中的一種普遍的魔術。另一種常見的模式是定義一個命名函式並將函式名作為變數傳遞給函式。比如下面的例子:
//全域性變數 var allUserData = []; //普通的logStuff函式,將內容列印到控制檯 function logStuff (userData){ if ( typeof userData === "string"){ console.log(userData); } else if ( typeof userData === "object"){ for(var item in userData){ console.log(item + ": " + userData[item]); } } } //一個接收兩個引數的函式,後面一個是回撥函式 function getInput (options,callback){ allUserData.push(options); callback(options); } //當我們呼叫getInput函式時,我們將logStuff作為一個引數傳遞給它 //因此logStuff將會在getInput函式內被回撥(或者執行) getInput({name:"Rich",speciality:"Javascript"},logStuff); //name:Rich //speciality:Javascript
傳遞引數給回撥函式
既然回撥函式在執行時僅僅是一個普通函式,我們就能給它傳遞引數。我們能夠傳遞任何包含它的函式的屬性(或者全域性屬性)作為回撥函式的引數。在前面的例子中,我們將options作為一個引數傳遞給了回撥函式。現在我們傳遞一個全域性變數和一個本地變數:
//全域性變數
var generalLastName = "Cliton";
function getInput (options,callback){
allUserData.push (options);
//將全域性變數generalLastName傳遞給回撥函式
cawww.cppcns.comllback(generalLastName,options);
}
在執行之前確保回撥函式是一個函式
在呼叫之前檢查作為引數被傳遞的回撥函式確實是一個函式,這樣的做法是明智的。同時,這也是一個實現條件回撥函式的最佳時間。
我們來重構上面例子中的getInput函式來確保檢查是恰當的。
function getInput(options,callback){ allUserData.push(options); //確保callback是一個函式 if(typeof callback === "function"){ //呼叫它,既然我們已經確定了它是可呼叫的 callback(options); } }
如果沒有適當的檢查,如果getInput的引數中沒有一個回撥函式或者傳遞的回撥函式事實上並不是一個函式,我們的程式碼將會導致執行錯誤。
使用this物件的方法作為回撥函式時的問題
當回撥函式是一個this物件的方法時,我們必須改變執行回撥函式的方法來保證this物件的上下文。否則如果回撥函式被傳遞給一個全域性函式,this物件要麼指向全域性window物件(在瀏覽器中)。要麼指向包含方法的物件。
我們在下面的程式碼中說明:
//定義一個擁有一些屬性和一個方法的物件 //我們接著將會把方法作為回撥函式傳遞給另一個函式 var clientData = { id: 094545,fullName "Not Set",//setUsrName是一個在clientData物件中的方法 setUserName: fucntion (firstName,lastName){ //這指向了物件中的fullName屬性 this.fullName = firstName + " " + lastName; } } function getUserInput(firstName,lastName,callback){ //在這做些什麼來確認firstName/lastName //現在儲存names callback(firstName,lastName); }
在下面你的程式碼例子中,當clientData.setUsername被執行時,this.fullName並沒有設定clientData物件中的fullName屬性。相反,它將設定window物件中的fullName屬性,因為getUserInput是一個全域性函式。這是因為全域性函式中的this物件指向window物件。
getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData,fullName);www.cppcns.com //Not Set
//fullName屬性將在window物件中被初始化
console.log(window.fullName); //Barack Obama
使用Call和Apply函式來儲存this
我們可以使用Call或者Apply函式來修復上面你的問題。到目前為止,我們知道了每個JavaScript中的函式都有兩個方法:Call和Apply。這些方法被用來設定函式內部的this物件以及給此函式傳遞變數。
call接收的第一個引數為被用來在函式內部當做this的物件,傳遞給函式的引數被挨個傳遞(當然使用逗號分開)。Apply函式的第一個引數也是在函式內部作為this的物件,然而最後一個引數確是傳遞給函式的值的陣列。
聽起來很複雜,那麼我們來看看使用Apply和Call有多麼的簡單。為了修復前面例子的問題,我http://www.cppcns.com將在下面你的例子中使用Apply函式:
//注意到我們增加了新的引數作為回撥物件,叫做“callbackObj” function getUserInput(firstName,callback. callbackObj){ //在這裡做些什麼來確認名字 callback.apply(callbackObj,[firstName,lastName]); }
使用Apply函式正確設定了this物件,我們現在正確的執行了callback並在clientData物件中正確設定了fullName屬性:
//我們將clientData.setUserName方法和clientData物件作為引數,clientData物件會被Apply方法使用來設定this物件 getUserName("Barack",clientData.setUserName,clientData); //clientData中的fullName屬性被正確的設定 console.log(clientUser.fullName); //Barack Obama
我們也可以使用Call函式,但是在這個例子中我們使用Apply函式。
允許多重回調函式
我們可以將不止一個的回撥函式作為引數傳遞給一個函式,就像我們能夠傳遞不止一個變數一樣。這裡有一個關於jQuery中AJAX的例子:
function successCallback(){ //在傳送之前做點什麼 } function successCallback(){ //在資訊被成功接收之後做點什麼 } function completeCallback(){ //在完成之後做點什麼 } function errorCallback(){ //當錯誤發生時做點什麼 } $.ajax({ url:"http://fiddle.jshell.net/favicon.png",success:successCallback,complete:completeCallback,error:errorCallback });
“回撥地獄”問題以及解決方案
在執行非同步程式碼時,無論以什麼順序簡單的執行程式碼,經常情況會變成許多層級的回撥函式堆積以致程式碼變成下面的情形。這些雜亂無章的程式碼叫做回撥地獄因為回撥太多而使看懂程式碼變得非常困難。我從node-mongodb-native,一個適用於Node.js的MongoDB驅動中拿來了一個例子。這段位於下方的程式碼將會充分說明回撥地獄:
var p_client = new Db('integration_tests_20',new Server("127.0.0.1",27017,{}),{'pk':CustomPKFactory}); p_client.open(function(err,p_client) { p_client.dropDatabase(function(err,done) { p_client.createCollection('test_custom_key',function(err,collection) { collection.insert({'a':1},docs) { collection.find({'_id':new ObjectID("aaaaaaaaaaaa")},cursor) { cursor.toArray(function(err,items) { test.assertEquals(1,items.length); // Let's close the db p_client.close(); }); }); }); }); }); });
你應該不想在你的程式碼中遇到這樣的問題,當你當你遇到了
- 你將會時不時的遇到這種情況
- 這裡有關於這個問題的兩種解決方案。
- 給你的函式命名並傳遞它們的名字作為回撥函式,而不是主函式的引數中定義匿名函式。
- 模組化L將你的程式碼分隔到模組中,這樣你就可以到處一塊程式碼來完成特定的工作。然後你可以在你的巨型應用中匯入模組。
建立你自己的回撥函式
既然你已經完全理解了關於JavaScript中回撥函式的一切(我認為你已經理解了,如果沒有那麼快速的重讀以便),你看到了使用回撥函式是如此的簡單而強大,你應該檢視你的程式碼看看有沒有能使用回撥函式的地方。回撥函式將在以下幾個方面幫助你:
- 避免重複程式碼(DRY-不要重複你自己)
- 在你擁有更多多功能函式的地方實現更好的抽象(依然能保持所有功能)
- 讓程式碼具有更好的可維護性
- 使程式碼更容易閱讀
- 編寫更多特定功能的函式
建立你的回撥函式非常簡單。在下面的例子中,我將建立一個函式完成以下工作:讀取使用者資訊,用資料建立一首通用的詩,並且歡迎使用者。這本來是個非常複雜的函式因為它包含很多if/else語句並且,它將在呼叫那些使用者資料需要的功能方面有諸多限制和不相容性。
相反,我用回撥函式實現了新增功能,這樣一來獲取使用者資訊的主函式便可以通過簡單的將使用者全名和性別作為引數傳遞給回撥函式並執行來完成任何任務。
簡單來講,getUserInput函式是多功能的:它能執行具有無種功能的回撥函式。
//首先,建立通用詩的生成函式;它將作為下面的getUserInput函式的回撥函式 function genericPoemMaker(name,gender) { console.log(name + " is finer than fine wine."); console.log("Altruistic and noble for the modern time."); console.log("Always admirably adorned with the latest style."); console.log("A " + gender + " of unfortunate tragedies who still manages a perpetual smile"); } //callback,引數的最後一項,將會是我們在上面定義的genericPoemMaker函式 function getUserInput(firstName,gender,callback) { var fullName = firstName + " " + lastName; // Make sure the callback is a function if (typeof callback === "function") { // Execute the callback function and pass the parameters to it callback(fullName,gender); } }
呼叫getUserInput函式並將genericPoemMaker函式作為回撥函式:
getUserInput("Michael","Fassbender","Man",genericPoemMaker); // 輸出 /* Michael Fassbender is finer than fine wine. Altruistic and noble for the modern time. Always admirably adorned with the latest style. A Man of unfortunate tragedies who still manages a perpetual smile. */
因為getUserInput函式僅僅只負責提取資料,我們可以把任意回撥函式傳遞給它。例如,我們可以傳遞一個greetUser函式:
unction greetUser(customerName,sex) { var salutation = sex && sex === "Man" ? "Mr." : "Ms."; console.log("Hello," + salutation + " " + customerName); } // 將greetUser作為一個回撥函式 getUserInput("Bill","Gates",greetUser); // 這裡是輸出 Hello,Mr. Bill Gates
我們呼叫了完全相同的getUserInput函式,但是這次完成了一個完全不同的任務。
正如你所見,回撥函式很神奇。即使前面的例子相對簡單,想象一下能節省多少工作量,你的程式碼將會變得更加的抽象,這一切只需要你開始使用毀掉函式。大膽的去使用吧。
在JavaScript程式設計中回撥函式經常以幾種方式被使用,尤其是在現代Web應用開發以及庫和框架中:
- 非同步呼叫(例如讀取檔案,進行HTTP請求,等等)
- 時間監聽器/處理器
- setTimeout和setInterval方法
- 一般情況:精簡程式碼
結束語
JavaScript回撥函式非常美妙且功能強大,它們為你的Web應用和程式碼提供了諸多好處。你應該在有需求時使用它;或者為了程式碼的抽象性,可維護性以及可讀性而使用回撥函式來重構你的程式碼。
以上就是理解與使用JavaScript中的回撥函式的詳細內容,更多關於JavaScript的資料請關注我們其它相關文章!