詳解JavaScript的回呼函數,javascript回呼函數

來源:互聯網
上載者:User

詳解JavaScript的回呼函數,javascript回呼函數

本文的目錄:

  • 什麼是回調或進階函數
  • 回呼函數是如何?的
  • 實現回呼函數的基本原則
  • 回調地獄的問題和解決方案
  • 實現自己的回呼函數

在JavaScrip中,function是內建的類對象,也就是說它是一種類型的對象,可以和其它String、Array、Number、Object類的對象一樣用於內建對象的管理。因為function實際上是一種對象,它可以“儲存在變數中,通過參數傳遞給(別一個)函數(function),在函數內部建立,從函數中返回結果值”。

因為function是內建對象,我們可以將它作為參數傳遞給另一個函數,延遲到函數中執行,甚至執行後將它返回。這是在JavaScript中使用回呼函數的精髓。本篇文章的剩餘部分將全面學習JavaScript的回呼函數。回呼函數也許是JavaScript中使用最廣泛的功能性編程技術,也許僅僅一小段JavaScript或jQuery的代碼都會給開發人員留下一種神秘感,閱讀這篇文章後,也許會幫你消除這種神秘感。
回呼函數來自一種著名的編程範式——函數式編程,在基本層面上,函數式編程指定的了函數的參數。函數式編程雖然現在的使用範圍變小了,但它一直被“專業的聰明的”程式員看作是一種難懂的技術,以前是這樣,未來也將是如此。

幸運的是,函數式編程已經被闡述的像你我這樣的一般人也能理解和使用。函數式編程最主要的技術之一就是回呼函數,你很快會閱讀到,實現回呼函數就像傳遞一般的參數變數一樣簡單。這項技術如此的簡單,以至於我都懷疑為什麼它經常被包含在JavaScript的進階話題中去。

一、什麼是回調或進階函數?

回呼函數被認為是一種進階函數,一種被作為參數傳遞給另一個函數(在這稱作"otherFunction")的進階函數,回呼函數會在otherFunction內被調用(或執行)。回呼函數的本質是一種模式(一種解決常見問題的模式),因此回呼函數也被稱為回調模式。

思考一下下面這種在jQuery中常用的回呼函數:

//Note that the item in the click method's parameter is a function, not a variable.//The item is a callback function$("#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方法,作為forEach的參數。

到目前為止,我們傳遞了一個匿名的函數作為參數給另一個函數或方法。在看其它更複雜的回呼函數之前,讓我們理解一下回調的工作原理並實現一個自己的回呼函數。

二、回呼函數是如何?的?

我們可以像使用變數一樣使用函數,作為另一個函數的參數,在另一個函數中作為返回結果,在另一個函數中調用它。當我們作為參數傳遞一個回呼函數給另一個函數時,我們只傳遞了這個函數的定義,並沒有在參數中執行它。

當包含(調用)函數擁有了在參數中定義的回呼函數後,它可以在任何時候調用(也就是回調)它。

這說明回呼函數並不是立即執行,而是在包含函數的函數體內指定的位置“回調”它(形如其名)。所以,即使第一個jQuery的例子看起來是這樣:

//The anonymous function is not being executed there in the parameter. //The item is a callback function$("#btn_1").click(function() { alert("Btn 1 Clicked");});

匿名函數將延遲在click函數的函數體內被調用,即使沒有名稱,也可以被包含函數通過 arguments對象訪問。

回呼函數是閉包的
當作為參數傳遞一個回呼函數給另一個函數時,回呼函數將在包含函數函數體內的某個位置被執行,就像回呼函數在包含函數的函數體內定義一樣。這意味著回呼函數是閉包的,想更多地瞭解閉包,請參考作者另一個貼子Understand JavaScript Closures With Ease。從所周知,閉包函數可以訪問包含函數的範圍,所以,回呼函數可以訪問包含函數的變數,甚至是全域變數。

三、實現回呼函數的基本原則

簡單地說,自己實現回呼函數的時候需要遵循幾條原則。

1、使用命名函數或匿名函數作為回調
在前面的jQuery和forEach的例子中,我們在包含函數的參數中定義匿名函數,這是使用回呼函數的通用形式之一,另一個經常被使用的形式是定義一個帶名稱的函數,並將函數名作為參數傳遞給另一個函數,例如:


// global variablevar allUserData = [];// generic logStuff function that prints to consolefunction logStuff (userData) {  if ( typeof userData === "string")  {    console.log(userData);  }  else if ( typeof userData === "object")  {    for (var item in userData) {      console.log(item + ": " + userData[item]);    }  }}// A function that takes two parameters, the last one a callback functionfunction getInput (options, callback) {  allUserData.push (options);  callback (options);}// When we call the getInput function, we pass logStuff as a parameter.// So logStuff will be the function that will called back (or executed) inside the getInput functiongetInput ({name:"Rich", speciality:"JavaScript"}, logStuff);// name: Rich// speciality: JavaScript

2、傳遞參數給回呼函數
因為回呼函數在執行的時候就和一般函數一樣,我們可以傳遞參數給它。可以將包含函數的任何屬性(或全域的屬性)作為參數傳遞迴調函數。在上一個例子中,我們將包含函數的options作為參數傳遞給回呼函數。下面的例子讓我們傳遞一個全域變數或本地變數給回呼函數:

//Global variablevar generalLastName = "Clinton";function getInput (options, callback) {  allUserData.push (options);// Pass the global variable generalLastName to the callback function  callback (generalLastName, options);}

3、在執行之前確保回調是一個函數
在調用之前,確保通過參數傳遞進來的回調是一個需要的函數通常是明智的。此外,讓回呼函數是可選的也是一個好的實踐。

讓我們重構一下上面例子中的getInput函數,確保回呼函數做了適當的檢查。

function getInput(options, callback) {  allUserData.push(options);  // Make sure the callback is a function  if (typeof callback === "function") {  // Call it, since we have confirmed it is callable    callback(options);  }}

如果getInput函數沒有做適當的檢查(檢查callback是否是函數,或是否通過參數傳遞進來了),我們的代碼將會導致執行階段錯誤。

4、使用含有this對象的回呼函數的問題
當回呼函數是一個含有this對象的方法時,我們必須修改執行回呼函數的方法以保護this對象的內容。否則this對象將會指向全域的window對象(如果回呼函數傳遞給了全域函數),或指向包含函數。讓我們看看下面的代碼:

// Define an object with some properties and a method// We will later pass the method as a callback function to another functionvar clientData = {  id: 094545,  fullName: "Not Set",  // setUserName is a method on the clientData object  setUserName: function (firstName, lastName) {    // this refers to the fullName property in this object   this.fullName = firstName + " " + lastName;  }}function getUserInput(firstName, lastName, callback) {  // Do other stuff to validate firstName/lastName here  // Now save the names  callback (firstName, lastName);}

在下面的範例程式碼中,當clientData.setUserName被執行時,this.fullName不會設定clientData 對象中的屬性fullName,而是設定window 對象中的fullName,因為getUserInput是一個全域函數。出現這種現象是因為在全域函數中this對象指向了window對象。

getUserInput ("Barack", "Obama", clientData.setUserName);console.log (clientData.fullName);// Not Set// The fullName property was initialized on the window objectconsole.log (window.fullName); // Barack Obama

5、使用Call或Apply函數保護this對象

我們可以通過使用 Call 或 Apply函數來解決前面樣本中的問題。到目前為止,我們知道JavaScript中的每一個函數都有兩個方法:Call和Apply。這些方法可以被用來在函數內部設定this對象的內容,並內容傳遞給函數參數指向的對象。

Call takes the value to be used as the this object inside the function as the first parameter, and the remaining arguments to be passed to the function are passed individually (separated by commas of course). The Apply function's first parameter is also the value to be used as the thisobject inside the function, while the last parameter is an array of values (or the arguments object) to pass to the function.  (該段翻譯起來太拗口了,放原文自己體會)

這聽起來很複雜,但讓我們看看Apply和Call的使用是多麼容易。為解決前面例子中出現的問題,我們使用Apply函數如下:

//Note that we have added an extra parameter for the callback object, called "callbackObj"function getUserInput(firstName, lastName, callback, callbackObj) {  // Do other stuff to validate name here  // The use of the Apply function below will set the this object to be callbackObj  callback.apply (callbackObj, [firstName, lastName]);}

通過Apply函數正確地設定this對象,現在我們可以正確地執行回呼函數並它正確地設定clientData對象中的fullName屬性。

// We pass the clientData.setUserName method and the clientData object as parameters. The clientData object will be used by the Apply function to set the this object
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);// the fullName property on the clientData was correctly setconsole.log (clientData.fullName); // Barack Obama

我們也可以使用Call 函數,但在本例中我們使用的Apply 函數。

6、多重回呼函數也是允許的
我們可以傳遞多個回呼函數給另一個函數,就像傳遞多個變數一樣。這是使用jQuery的AJAX函數的典型例子:

function successCallback() {  // Do stuff before send}function successCallback() {  // Do stuff if success message received}function completeCallback() {  // Do stuff upon completion}function errorCallback() {  // Do stuff if error received}$.ajax({  url:"http://fiddle.jshell.net/favicon.png",  success:successCallback,  complete:completeCallback,  error:errorCallback});

四、“回調地獄”的問題和解決方案

非同步代碼執行是一種簡單的以任意順序執行的方式,有時是很常見的有很多層級的回呼函數,你看起來像下面這樣的代碼。下面這種淩亂的代碼稱作“回調地獄”,因為它是一種包含非常多的回調的麻煩的代碼。我是在node-mongodb-native裡看到這個例子的,MongoDB驅動Node.js.範例程式碼就像這樣:

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();          });        });      });    });  });});

你不太可能在自己的代碼裡碰到這個的問題,但如果你碰到了(或以後偶然碰到了),那麼有以下兩種方式解決這個問題。

命名並定義你的函數,然後傳遞函數名作為回調,而不是在主函數的參數列表裡定義一個匿名函數。
模組化:把你的代碼劃分成一個個模組,這樣你可以空出一部分代碼塊做特殊的工作。然後你可以將這個模型引入到你的大型應用程式中。

五、實現自己的回呼函數

現在你已經完全理解(我相信你已經理解了,如果沒有請快速重新閱讀一遍)了JavaScript關於回調的所用特性並且看到回調的使用是如此簡單但功能卻很強大。你應該看看自己的代碼是否有機會使用回呼函數,有以下需求時你可以考慮使用回調:

  • 避免重複代碼 (DRY—Do Not Repeat Yourself)
  • 在你需要更多的通用功能的地方更好地實現抽象(可處理各種類型的函數)。
  • 增強代碼的可維護性
  • 增強代碼的可讀性
  • 有更多定製的功能

實現自己的回呼函數很簡單,在下面的例子中,我可以建立一個函數完成所用的工作:擷取使用者資料,使用使用者資料產生一首通用的詩,使用使用者資料來歡迎使用者,但這個函數將會是一個淩亂的函數,到處是if/else的判斷,甚至會有很多的限制並無法執行應用程式可能需要的處理使用者資料的其它函數。

替而代之的是我讓實現增加了回呼函數,這樣主函數擷取使用者資料後可以傳遞使用者全名和性別給回呼函數的參數並執行回呼函數以完成任何任務。

簡而言之,getUserInput函數是通用的,它可以執行多個擁有各種功能的回呼函數。

// First, setup the generic poem creator function; it will be the callback function in the getUserInput function below.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");}//The callback, which is the last item in the parameter, will be our genericPoemMaker function we defined above.function getUserInput(firstName, lastName, 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);// Output/* 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函數。

function greetUser(customerName, sex) {  var salutation = sex && sex === "Man" ? "Mr." : "Ms."; console.log("Hello, " + salutation + " " + customerName);}// Pass the greetUser function as a callback to getUserInputgetUserInput("Bill", "Gates", "Man", greetUser);// And this is the outputHello, Mr. Bill Gates

和上一個例子一樣,我們調用了同一個getUserInput 函數,但這次卻執行了完全不同的任務。

如你所見,回呼函數提供了廣泛的功能。儘管前面提到的例子非常簡單,在你開始使用回呼函數的時候思考一下你可以節省多少工作,如何更好地抽象你的代碼。加油吧!在早上起來時想一想,在晚上睡覺前想一想,在你休息時想一想……

我們在JavaScript中經常使用回呼函數時注意以下幾點,尤其是現在的web應用開發,在第三方庫和架構中

  • 非同步執行(例如讀檔案,發送HTTP請求)
  • 事件監聽和處理
  • 設定逾時和時間間隔的方法
  • 通用化:代碼簡潔

以上就是更加深入的學習了JavaScript的回呼函數,希望對大家的學習有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.