初步講解JS中的callback回調原理

JS的異步執行機制

什麼是異步執行

爲了提升Javascript代碼的運行效率,JS對於部分函數方法採用了異步調用機制(如Ajax的操做)。異步執行的函數方法的執行並不是爲一個隊列挨個執行的,而是相互獨立,同時調用執行的,從而避免代碼運行阻塞,減小沒必要要的等待時間。node

異步執行機制

咱們來舉一個栗子

大部分新手編程時,都會按照一種線性思惟的方法去設計代碼,這就跟JS中的異步執行機制相沖突。程序員

如:咱們在node中,但願在一個讀取文檔流的操做後,將讀取到的文件中的字符串賦值給變量,str 以後再用 console.log() 方法輸出讀取到的文件內容,這時若是按照咱們的線性思惟去設計代碼,會寫出以下的操做:web

// 需求:封裝一個方法,傳入一個路徑,能夠讀取相對應的文件
const fs = require('fs');
const path = require('path');

// 給定文件路徑,返回讀取到的內容
function getFileByPath(fpath) {
  fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    if (err)
      throw err;
    else {
      var str = dataStr;
    }
  })
}

// 調用讀取文件方法
getFileByPath(path.join(__dirname, './files/1.txt'));

console.log(str);

控制檯輸出的結果爲編程

ReferenceError: str is not defined

這就是因爲咱們是按照線性思惟去考慮問題,理所固然的認爲變量 str 的定義和賦值操做在 console.log() 操做以前,然而真實的狀況是,JS在被解析以後,能夠瞬間執行的操做,如 console.log()for循環 等基礎操做,都是按照隊列執行的,如:異步

var test = function () {
  console.log('1');
}
test();

for (var i = 2; i < 5; i++) {
  console.log(i);
}

console.log('6');

控制檯輸出的結果爲函數

1
2
3
4
5
6

然而讀取文件操做是一個會致使代碼阻塞的操做,因此JS會將其放置在異步隊列中,執行後方代碼,因此栗子中執行代碼的正確順序應該是先執行console.log() 再執行 getFileByPath()ui

回調函數

那假若說咱們就是須要有一步操做,放在讀取文件以後再執行,而不是跳過讀取文件操做直接執行,那該怎麼辦呢?spa

這就須要用「回調函數」的思想來拯救咱們。大部分人都知道回調函數在 jQuery 中被髮揮的淋漓盡致,然而新手每每不多知道回調函數原理,因此接下來咱們仍以這個栗子爲表明探討回調函數。設計

咱們先拋開回調函數,用最原始的方法讓一些操做在讀取文件操做後執行該怎麼辦呢?那就是直接改寫整個 getFileByPath() 方法:code

// 需求:封裝一個方法,傳入一個路徑,能夠讀取相對應的文件
const fs = require('fs');
const path = require('path');

// 給定文件路徑,返回讀取到的內容
function getFileByPath(fpath) {
  fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    if (err)
      throw err;
    else {
      var str = dataStr;
+        console.log(str);
    }
  })
}

// 調用讀取文件方法
getFileByPath(path.join(__dirname, './files/1.txt'));

這樣咱們就能夠在直行完 getFileByPath 方法以後在控制檯輸出讀取的文件內容。可是這樣的操做並不能很好的解決咱們的問題,假若方法被封裝拿給別人使用,其餘人須要更改源碼才能夠實現功能方法,很顯然這樣並不靈活,甚至還會更改該方法原有的功能。

因此咱們就須要設置一個回調函數,在異步操做完成以後,再進行咱們須要的下一步的操做。

爲了理解回調函數的原理,咱們先將變動後的這一部分代碼分理出來:

if (err)
      throw err;
    else {
      var str = dataStr;
+     console.log(str);
    }

能夠看出,讀取完文件以後,會直行else下的操做,若是咱們把 var str = dataStr; console.log(str)封裝成一個方法命名爲 clg,那咱們在 else 以後執行 clg() 方法就能夠實現一樣的操做:

function getFileByPath(fpath) {
  fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    if (err)
      throw err;
    else {
      clg(dataStr);
    }
  })
}

function clg(dataStr){
    var str = dataStr;
    console.log(str);
}

getFileByPath(path.join(__dirname, './files/1.txt'));

這樣咱們就能夠將讀取文件操做後執行的操做放在 clg 方法中就能夠執行,這個 clg() 實際上就能夠稱之爲一個回調函數,可是這樣仍是會讓代碼變得繁雜。

咱們來看一下jQuery的回調函數:

$('#demo').animate({"opacity":"1"}, 1000, fucntion(){... 回調函數 ..});

jQuery將回調函數做爲一個參數傳入到方法中,因此咱們只要在 getFileByPath() 方法中追加一個參數,這個參數是一個函數,咱們就能夠在源碼的 else 後執行傳入的這個函數,這個函數就稱之爲 "回調函數" 。

改寫後的 getFileByPath() 方法

function getFileByPath(fpath, callback) {
  fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    if (err)
      throw err;
    else {
      callback(dataStr);
    }
  })
}

以後咱們再在調用的時候,在參數位寫入一個方法函數,這個方法就會被傳入getFileByPath()方法內部,等文件讀取操做完成以後再直行。

getFileByPath(path.join(__dirname, './files/1.txt'), function(dataStr){
    var str = dataStr;
    console.log(str);
});

值得注意的是,咱們在源碼中設置傳入的參數位時,對回調函數設置了一個參數

callback(dataStr);

這個dataStr就是文件讀取操做讀取的文件內容,咱們將個變量傳入在callback()方法中,在調用getFileByPath()時寫入的回調函數中就能夠調用dataStr這個變量了。

這就是對回調函數的簡單講解,萌新程序員,歡迎糾錯- ̗̀(๑ᵔ⌔ᵔ๑)

相關文章
相關標籤/搜索