爲了提升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
這個變量了。
這就是對回調函數的簡單講解,萌新程序員,歡迎糾錯- ̗̀(๑ᵔ⌔ᵔ๑)