前言
callback,大家都知道是回調函數的意思。如果讓你舉些callback的例子,我相信你可以舉出一堆。但callback的概念你知道嗎?你自己在實際應用中能不能合理利用回調實現功能? 我們在平時的學習中容易犯不去深究的病,功能實現了也就不再去追其原由,對一些概念模模糊糊。如果對callback沒有一個清楚的理解,估計你在學習Node.js后會崩潰,因為callback是Node.js三大核心之一。
一 .回調函數
回調函數的概念
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
以上是Google的解釋,非常清晰簡明,小編令人窒息的四級英語水平都能看懂。
下面給一個回調的例子
function doSomething(msg, callback){
alert(msg);
if(typeof callback == "function")
callback();
}
doSomething("回調函數", function(){
alert("匿名函數實現回調!");
});
我們再來看幾個經典的回調函數代碼,我保證你一定用過他們:
從上面的例子,我們可以看出回調與同步、異步并沒有直接的聯系,回調只是一種實現方式,既可以有同步回調,也可以有異步回調,還可以有事件處理回調和延遲函數回調,這些在我們工作中有很多的使用場景。
二.把使用this對象的函數作為回調函數(陷阱)
當回調函數是一個使用this對象的函數時,我們必須改變執行回調函數的調用對象來保證this對象的上下文。在講這個問題之前,我們必須先了解JS于this的指向。我們這里不詳細談這個問題,我直接說下自己學習過程總結的判斷this指向的兩條經驗(既然是自己的經驗,那就不一定對,希望大家指正):
(1)this的指向是在函數執行的時候確定的,在函數定義的時候是確定不了,實際上this的最終指向的是那個調用它的對象
(2)調用執行函數時,“.”前面是什么,this就是什么。前面沒有對象,就是window了。(好粗暴)
回到使用this對象的函數作為回調函數這個問題,我們首先來看看下面這段代碼:
//定義一個擁有一些屬性和一個方法的對象 //我們接著將會把方法作為回調函數傳遞給另一個函數
var clientData = {
id: 096545,
fullName: "Not Set",
//setUsrName是一個在clientData對象中的方法
setUserName: function (firstName, lastName){
this.fullName = firstName + " " + lastName;
}
}
function getUserInput(firstName, lastName, callback){
//code .....
//調用回調函數存儲
callback(firstName, lastName);
}
getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData.fullName); //Not Set
console.log(window.fullName); //Barack Obama
在上面的代碼中,當clientData.setUsername被執行時,this.fullName并沒有設置clientData對象中的fullName屬性。相反,它將設置window對象中的fullName屬性,這是因為callback中的this指向window的緣故。
使用Call和Apply函數來改變this指向
我們可以使用Call或者Apply函數來解決上面你的問題。到目前為止,我們知道了每個Javascript中的函數都有兩個方法:Call 和 Apply。這些方法被用來設置函數內部的this對象以及給此函數傳遞變量。
這里我們演示Apply函數實現,Call函數類似。(call接收的第一個參數為被用來在函數內部當做this的對象,傳遞給函數的參數被挨個傳遞。Apply函數的第一個參數也是在函數內部作為this的對象,然而最后一個參數確是傳遞給函數的值的數組。)
Apply函數:
//注意到我們增加了新的參數作為回調對象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback ,callbackObj){
//code .....
callback.apply(callbackObj, [firstName, lastName]);
}
getUserInput("Barack", "Obama", clientData.setUserName, clientData);
console.log(clientData.fullName); //Barack Obama
使用Apply函數正確設置了this對象,我們現在正確的執行了callback并在clientData對象中正確設置了fullName屬性
三.回調函數是實現異步編程的利器
在程序運行中,當某些請求過程漫長,我們有時沒必要選擇等待請求完成繼續處理下一個任務,這時使用回調函數進行異步處理可以大大提高程序執行效率。例如:AJAX請求。若是使用回調函數進行處理,代碼就可以繼續進行其他任務,而無需空等。實際開發中,經常在javascript中使用異步調用!
下面有個使用AJAX加載XML文件的示例,并且使用了call()函數,在請求對象(requested object)上下文中調用回調函數。
function fn(url, callback){
var httpRequest; //創建XHR
httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : //針對IE進行功能性檢測
window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
httpRequest.onreadystatechange = function(){
if(httpRequest.readystate === 4 && httpRequest.status === 200){ //狀態判斷
callback.call(httpRequest.responseXML);
}
};
httpRequest.open("GET", url);
httpRequest.send();
}
fn("text.xml", function(){ //調用函數
console.log(this); / /此語句后輸出
});
console.log("this will run before the above callback."); //此語句先輸出
我們請求異步處理,意味著我們開始請求時,就告訴它們完成之時調用我們的函數。在實際情況中,onreadystatechange事件處理程序還得考慮請求失敗的情況,這里我們是假設xml文件存在并且能被瀏覽器成功加載。這個例子中,異步函數分配給了onreadystatechange事件,因此不會立刻執行。
最終,第二個console.log語句先執行,因為回調函數直到請求完成才執行。
在Javascript編程中回調函數經常以幾種方式被使用,尤其是在現代web應用開發以及庫和框架中:
- 異步調用(例如讀取文件,進行HTTP請求,動態加載js文件,加載iframe資源后,圖片加載完成執行回調等等)
- 事件監聽器/處理器
- setTimeout和setInterval方法
- 一般情況:精簡代碼
4.“回調地獄”問題以及解決方案
這么多回調嵌套,我還沒遇到過。下面內容是直接從網上copy過來,大家看下,還是很好理解的。
在執行異步代碼時,無論以什么順序簡單的執行代碼,經常情況會變成許多層級的回調函數堆積以致代碼變成下面的情形。這些雜亂無章的代碼叫做回調地獄因為回調太多而使看懂代碼變得非常困難。我從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}, function(err, docs) {
collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
cursor.toArray(function(err, items) {
test.assertEquals(1, items.length);
// Let's close the db
p_client.close();
});
});
});
});
});
});
你應該不想在你的代碼中遇到這樣的問題,當你當你遇到了-你將會是不是的遇到這種情況-這里有關于這個問題的兩種解決方案。
給你的函數命名并傳遞它們的名字作為回調函數,而不是主函數的參數中定義匿名函數。
模塊化L將你的代碼分隔到模塊中,這樣你就可以到處一塊代碼來完成特定的工作。然后你可以在你的巨型應用中導入模塊。