回調函數是程序設計的一種方法。這種方法是指,在傳遞了可能會進行調用的函數或對象以後,在須要時再分別對其進行調用。因爲調用方與被調用方的依賴關係與一般相反,因此也成爲控制反轉(IoC,Inversion of Control)。
因爲歷史緣由,在JavaScript開發中咱們經常會用到回調函數這一方法,這是多種因素致使的。第一個緣由是在客戶端JavaScript中基本都是GUI程序設計。GUI程序設計是一種很適合使用所謂事件驅動的程序設計方式。事件驅動正是一種回調函數設計模式。客戶端JavaScript程序設計是一種基於DOM的事件驅動式程序設計。
第二個緣由是,源於客戶端沒法實現多線程程序設計(最近HTML5 Web Works支持多線程了)。而經過將回調函數與異步處理相結合,就可以實現並行處理。因爲不支持多線程,因此爲了實現並行處理,不得不使用回調函數,這逐漸成爲了一種慣例。最後一個緣由與JavaScript中的函數聲明表達式和閉包有關。設計模式
var emitter = { // 爲了可以註冊多個回調函數而經過數組管理 callbacks:[], // 回調函數的註冊方法 register:function (fn) { this.callbacks.push(fn); }, // 事件的觸發處理 onOpen:function () { for (var f in this.callbacks) { this.callbacks[f](); } } }; emitter.register(function () {alert("event handler1 is called");}) emitter.register(function () {alert("event handler2 is called");}) emitter.onOpen(); // "event handler1 is called" // "event handler2 is called"
定義的兩個匿名函數就是回調函數,它們的調用由emitter.onOpen()
完成。數組
對emitter來講,這僅僅是對註冊的函數進行了調用,不過根據回調函數的定義,更應該關注使用了emitter部分的狀況。從這個角度來看,註冊過的回調函數與之造成的是一種調用與被調用的關係。多線程
上面的回調函數只是單純的函數而不具備狀態。若是回調函數具備狀態,就能獲得更爲普遍的應用。下面咱們把回調方改成了對象,因而emitter變爲了接受方法傳遞的形式。閉包
function MyClass(msg) { this.msg = msg; this.show = function () {alert(this.msg+' is called');} } // 將方法註冊爲回調函數 var obj = new MyClass("listener1"); var obj2 = new MyClass("listener2"); emitter.register(obj.show); emitter.register(obj2.show); emitter.onOpen(); // undefined is called // undefined is called
咱們發現,調用回調函數沒法正確顯示this.msg,錯誤緣由在於JavaScript內的this引用。解決方法有兩種,一種是使用bind,一種是不使用方法而是用對象進行註冊。後者在JavaScript中並不經常使用。app
emitter.register(obj.show.bind(obj)); emitter.register(obj2.show.bind(obj2)); emitter.onOpen(); // "listener1 is called" // "listener2 is called"
bind是ES5新增的功能,是Function.prototype對象的方法。bind的做用和apply與call相同,都是用於明確指定出方法調用時的this引用。對於函數來講,調用了bind以後會返回一個新函數,新的函數會執行與原函數相同的內容,不過其this引用是被指定爲它的第一個參數的對象。在調用apply與call時將會當即調用目標函數,而在調用bind時則不會如此,而是會返回一個函數(閉包)。
若是使用了apply或call,就能對bind進行獨立的實現。事實上在ES5才推出以前,在prototype.js等知名的庫中就經過apply/call提供了bind本身的實現。異步
腦補的bind的內部實現?函數
Function.prototype.bind = null; Function.prototype.bind = function (obj) { var f = this; return function () { f.call(obj); } } var obj = { x:"這是 obj.x !!!", fn:function () { alert(this.x); } }; var obj2 = {x:"obj2.x 對啦!!!"}; var testfn = obj.fn.bind(obj2); testfn(); // "obj2.x 對啦!!!"
閉包與回調函數this
emitter.register( (function () { var msg = "closure1"; return function () {alert(msg+" is called;")}; }()) ); emitter.register( (function () { var msg = "closure2"; return function () {alert(msg+" is called;")}; }()) ) emitter.onOpen(); // "closure1 is called" // "closure2 is called"
藉助閉包,前面繁複的說明彷彿不在存在,能夠很輕鬆的實現回調函數,而且還能像對象同樣具備狀態。prototype