《JS編程全解》—— 回調函數

回調函數模式

回調函數與控制反轉

回調函數是程序設計的一種方法。這種方法是指,在傳遞了可能會進行調用的函數或對象以後,在須要時再分別對其進行調用。因爲調用方與被調用方的依賴關係與一般相反,因此也成爲控制反轉(IoC,Inversion of Control)。
因爲歷史緣由,在JavaScript開發中咱們經常會用到回調函數這一方法,這是多種因素致使的。第一個緣由是在客戶端JavaScript中基本都是GUI程序設計。GUI程序設計是一種很適合使用所謂事件驅動的程序設計方式。事件驅動正是一種回調函數設計模式。客戶端JavaScript程序設計是一種基於DOM的事件驅動式程序設計。
第二個緣由是,源於客戶端沒法實現多線程程序設計(最近HTML5 Web Works支持多線程了)。而經過將回調函數與異步處理相結合,就可以實現並行處理。因爲不支持多線程,因此爲了實現並行處理,不得不使用回調函數,這逐漸成爲了一種慣例。最後一個緣由與JavaScript中的函數聲明表達式和閉包有關。設計模式

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

相關文章
相關標籤/搜索