2011-09-30 31 views
10

所以,我正在編寫一個Web應用程序。幾乎所有事情都是在客戶端完成的,服務器只是一個RESTful接口。我使用jQuery作爲我的選擇框架,並在Revealing Module Pattern中實現我的代碼。以編程方式實現與JS/jQuery的回調

我的代碼基本上線框看起來像這樣:

(function($){ 
    $.fn.myplugin = function(method) 
    { 
     if (mp[method]) 
     { 
      return mp[method].apply(this, Array.prototype.slice.call(arguments, 1)); 
     } 
     else if (typeof method === 'object' || ! method) 
     { 
      return mp.init.apply(this, arguments); 
     } 
     else 
     { 
      $.error('Method ' + method + ' does not exist on $.myplugin'); 
     } 
    }; 

    var mp = 
    { 
     init : function(options) 
     { 
      return this.each(function() 
      { 
       // stuff 
      } 
     }, 
     callbacks : {}, 
     addCallback : function(hook_name, cb_func, priority) 
     { 
      // some sanity checking, then push cb_func onto a stack in mp.callbacks[hook_name] 
     }, 
     doCallbacks : function(hook_name) 
     { 
      if (!hook_name) { hook_name = arguments.callee.caller.name; } 
      // check if any callbacks have been registered for hook_name, if so, execute one after the other 
     } 
    }; 
})(jQuery); 

非常簡單的,對不對?

現在,我們能夠從內部以及從應用程序範圍之外註冊(多層次)回調。

什麼是竊聽我:爲了使整個事情的擴展可能,我不得不求助於這些方針的東西:

foo : function() { 
    mp.doCallbacks('foo_before'); 
    // do actual stuff, maybe some hookpoints in between 
    mp.doCallbacks('foo_after');   
} 

我的應用程序內的每個單一的功能將不得不開始和結束像那樣。這看起來不正確。

那麼,JS的巫師呢 - 幹什麼?

+0

幾乎所有的插件架構我見過的實施已採取之前和掛機後。據我所知,大多數時候你只需要實現人們所要求的內容。沒有我知道的魔術解決方案。 –

+0

這似乎是一個過度工程的例子。你真的需要庫中的每個_internal_函數來支持回調之前和之後嗎? Rails就像一個巨大的回調三明治,即使如此,Rails中只有1%的方法支持回調。而且,在他們支持回調的地方,它們完全按照您在示例_foo_函數中所述的方式執行。 –

回答

10

您可以編寫一個函數,將另一個函數作爲參數,並返回一個新的函數,該函數會圍繞該參數調用您的鉤子。例如:

function withCallbacks(name, func) 
{ 
    return function() { 
     mp.doCallbacks(name + "_before"); 
     func(); 
     mp.doCallbacks(name + "_after"); 
    }; 
} 

然後,你可以寫類似:

foo: withCallbacks("foo", function() { 
    // Do actual stuff, maybe some hookpoints in between. 
}) 
+0

+1在你編輯你的文章之前,我幾乎說了一些東西,不過很好的解決方案! – Chad

+0

@Chad,謝謝,它花了我一些時間來接受我無法從函數中推導出鉤子名稱:) –

+0

在我的'doCallbacks()'實現中,如果沒有給出'hook_name',我使用'arguments。 callee.caller.name'從函數中推導出鉤子名稱......如果通過'withCallbacks()';)調用,這顯然會被破壞 – vzwick

2

你基本上實現jQueryUI Widget factory的精簡版本。我建議使用該功能來避免必須自行滾動。

小部件工廠將自動映射神奇字符串的方法調用,使得:

$("#foo").myPlugin("myMethod", "someParam") 

將調用myMethod上的插件實例與'someParam'作爲參數。此外,如果您啓動自定義事件,則用戶可以通過向與事件名稱匹配的選項添加屬性來添加回調。

例如,tabs小部件有一個select事件,您可以通過初始化過程中添加select屬性選項進軍:

$("#container").tabs({ 
    select: function() { 
     // This gets called when the `select` event fires 
    } 
}); 
當然

,你需要前後加將事件掛鉤以便能夠借用此功能,但這通常會導致維護更容易。

希望有幫助。乾杯!

+0

感謝指針,意識到這一點,但無論如何;) – vzwick

4

我可能還沒有理解錯的問題,因爲我不明白爲什麼你不添加代碼,直接在爲myplugin代碼調用回調函數:

$.fn.myplugin = function(method) 
{ 
    if (mp[method]) 
    { 
     var params = Array.prototype.slice.call(arguments, 1), ret; 
     // you might want the callbacks to receive all the parameters 
     mp['doCallbacks'].apply(this, method + '_before', params); 
     ret = mp[method].apply(this, params); 
     mp['doCallbacks'].apply(this, method + '_after', params); 
     return ret; 
    } 
    // ... 
} 

編輯:

好的,在閱讀你的評論後,我認爲另一種解決方案將是(當然)另一種間接方式。也就是說,有一個invoke函數正在使用從構造函數以及其他公共方法之間進行調用。我想指出,它只適用於公共方法,因爲附加私有方法的鉤子會破壞封裝。

簡單的版本是這樣的:

function invoke(method) { 
    var params = Array.prototype.slice.call(arguments, 1), ret; 
    // you might want the callbacks to receive all the parameters 
    mp['doCallbacks'].apply(this, method + '_before', params); 
    ret = mp[method].apply(this, params); 
    mp['doCallbacks'].apply(this, method + '_after', params); 
} 

$.fn.myplugin = function() { 
    // ... 
    invoke('init'); 
    // ... 
}; 

但是,我已經實際寫入多一點的代碼,這將降低插件之間的重複,以及。 這是如何創建一個插件會看看到底

(function() { 

function getResource() { 
    return {lang: "JS"}; 
} 

var mp = NS.plugin.interface({ 
    foo: function() { 
     getResource(); // calls "private" method 
    }, 
    bar: function() { 
     this.invoke('foo'); // calls "fellow" method 
    }, 
    init: function() { 
     // construct 
    } 
}); 

$.fn.myplugin = NS.plugin.create(mp); 

})(); 

而這部分實現的樣子:

NS = {}; 
NS.plugin = {}; 

NS.plugin.create = function(ctx) { 
    return function(method) { 
     if (typeof method == "string") { 
      arguments = Array.prototype.slice.call(arguments, 1); 
     } else { 
      method = 'init'; // also gives hooks for init 
     } 

     return ctx.invoke.apply(ctx, method, arguments); 
    }; 
}; 

// interface is a reserved keyword in strict, but it's descriptive for the use case 
NS.plugin.interface = function(o) { 
    return merge({ 
     invoke:  NS.plugin.invoke, 
     callbacks: {}, 
     addCallback: function(hook_name, fn, priority) {}, 
     doCallbacks: function() {} 
    }, o); 
}; 

NS.plugin.invoke = function(method_name) { 
    if (method_name == 'invoke') { 
     return; 
    } 

    // bonus (if this helps you somehow) 
    if (! this[method]) { 
     if (! this['method_missing') { 
      throw "Method " + method + " does not exist."; 
     } else { 
      method = 'method_missing'; 
     } 
    } 

    arguments = Array.prototype.slice.call(arguments, 1); 

    if (method_name in ["addCallbacks", "doCallbacks"]) { 
     return this[method_name].apply(this, arguments); 
    } 

    this.doCallbacks.apply(this, method_name + '_before', arguments); 
    var ret = this[method_name].apply(this, arguments); 
    this.doCallbacks.apply(this, method_name + '_after', arguments); 

    return ret; 
}; 

當然,這是完全未經測試:)

+0

這對於外部調用/「公共」方法是可行的。我需要能夠在回調仍在執行的情況下從「內部」'mp'執行this.someFunction()。 – vzwick

+0

我已在編輯中解決了您的評論。對於你的用例可能太複雜了,但寫起來很有趣。 – deviousdodo

+0

整潔的一段代碼! – vzwick

1

基本上我寧願避免回調並使用事件。原因是simle。我可以添加多個函數來偵聽給定的事件,我不必混淆回調參數,也不必檢查是否定義了回調。至於所有的方法都是通過$.fn.myplugin調用的,在方法調用之前和之後很容易觸發事件。

下面是一個例子代碼:

(function($){ 
    $.fn.myplugin = function(method) 
    { 
     if (mp[method]) 
     { 
      $(this).trigger("before_"+method); 
      var res = mp[method].apply(this, Array.prototype.slice.call(arguments, 1)); 
      $(this).trigger("after_"+method); 
      return res; 
     } 
     else if (typeof method === 'object' || ! method) 
     { 
      return mp.init.apply(this, arguments); 
     } 
     else 
     { 
      $.error('Method ' + method + ' does not exist on $.myplugin'); 
     } 
    }; 

    var mp = 
    { 
     init : function(options) 
     { 
      $(this).bind(options.bind); 
      return this.each(function() 
      { 
       // stuff 
      }); 
     }, 

     foo: function() { 
      console.log("foo called"); 
     } 
    }; 
})(jQuery); 


$("#foo").myplugin({ 
    bind: { 
     before_foo: function() { 
      console.log("before foo"); 
     }, 

     after_foo: function() { 
      console.log("after foo"); 
     } 
    } 
}); 
$("#foo").myplugin("foo");