2011-09-30 40 views
10

Vì vậy, tôi đang viết một ứng dụng web. Khá nhiều thứ được thực hiện phía máy khách, máy chủ là giao diện RESTful. Tôi đang sử dụng jQuery làm khung lựa chọn của mình và triển khai mã của tôi trong một số Revealing Module Pattern.Thực hiện các cuộc gọi lại theo lập trình bằng JS/jQuery

Các wireframe của mã của tôi về cơ bản trông như thế này:

(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); 

Khá đơn giản, phải không?

Bây giờ, chúng tôi có thể đăng ký (nhiều, phân cấp) cuộc gọi lại từ bên trong cũng như từ bên ngoài phạm vi ứng dụng.

gì được bugging tôi: Để làm cho toàn bộ điều như mở rộng càng tốt, tôi sẽ phải dùng đến một cái gì đó dọc theo những dòng:

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

Mỗi chức năng duy nhất bên trong ứng dụng của tôi sẽ phải bắt đầu và kết thúc như thế. Điều này dường như không đúng.

Vì vậy, thuật sĩ JS của SO - làm gì?

+0

Hầu hết các kiến ​​trúc plugin tôi đã nhìn thấy được thực hiện đã viện đến trước và sau khi móc. Hầu hết thời gian bạn chỉ thực hiện những gì mọi người yêu cầu khi họ yêu cầu, theo như tôi có thể nói. Không có giải pháp ma thuật mà tôi biết. –

+0

Điều này có vẻ giống như một trường hợp quá kỹ thuật. Bạn có thực sự cần mọi chức năng _internal_ trong thư viện của mình để hỗ trợ trước và sau khi gọi lại không? Rails giống như một chiếc bánh sandwich gọi lại khổng lồ, và thậm chí như vậy, chỉ có 1% các phương thức trong Rails hỗ trợ gọi lại. Và, nơi họ hỗ trợ gọi lại, họ thực hiện chính xác như bạn đã nêu trong hàm _foo_ ví dụ của bạn. –

Trả lời

10

Bạn có thể viết hàm nhận hàm khác làm đối số và trả về hàm mới gọi móc của bạn xung quanh đối số đó. Ví dụ:

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

Sau đó, bạn có thể viết một cái gì đó như:

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

+1 Tôi gần như đã nói điều gì đó trước khi bạn chỉnh sửa bài đăng của mình, giải pháp tốt mặc dù! – Chad

+0

@Chad, cảm ơn, nó đã cho tôi một thời gian để chấp nhận rằng tôi không thể suy ra các tên móc từ chức năng :) –

+0

Trong thực hiện 'doCallbacks()' của tôi, nếu không có 'hook_name' được đưa ra, tôi sử dụng' đối số. callee.caller.name' để suy ra tên móc từ hàm ... Rõ ràng là sẽ bị hỏng nếu được gọi qua 'withCallbacks()';) – vzwick

2

bạn là chủ yếu thực hiện một phiên bản rút gọn của các jQueryUI Widget factory. tôi khuyên bạn nên sử dụng chức năng đó để tránh phải tự mình cuộn.

nhà máy phụ tùng sẽ tự động một cách kỳ diệu đồ chuỗi các cuộc gọi phương pháp, ví dụ rằng:

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

sẽ gọi myMethod trên dụ plugin với 'someParam' như một cuộc tranh cãi. Ngoài ra, nếu bạn kích hoạt sự kiện tùy chỉnh, người dùng có thể thêm gọi lại bằng cách thêm thuộc tính vào các tùy chọn khớp với tên sự kiện.

Ví dụ, widget tabs có một sự kiện select mà bạn có thể khai thác vào bằng cách thêm một tài sản để các tùy chọn trong quá trình khởi select:

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

tất nhiên, bạn sẽ cần phải thêm trước và sau móc như các sự kiện để có thể mượn chức năng này, nhưng điều đó thường dẫn đến việc bảo trì dễ dàng hơn.

hy vọng sẽ hữu ích. chúc mừng!

+0

Cảm ơn con trỏ, đã nhận thức được điều đó, nhưng dù sao đi nữa;) – vzwick

4

tôi có thể chưa hiểu câu hỏi một cách chính xác, bởi vì tôi không thấy lý do tại sao bạn không thêm mã để gọi callbacks trực tiếp trong mã 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; 
    } 
    // ... 
} 

EDIT:

Ok, sau khi đọc bình luận của bạn, tôi nghĩ rằng một giải pháp khác sẽ là (dĩ nhiên) một sự khác biệt. Tức là, có một hàm gọi được sử dụng từ hàm tạo cũng như các phương thức công khai khác cho các cuộc gọi giữa chúng.Tôi muốn chỉ ra rằng nó sẽ chỉ làm việc cho các phương pháp công cộng, như gắn liền với phương pháp riêng 'móc phá vỡ đóng gói anyway.

Phiên bản đơn giản sẽ là một cái gì đó như thế này:

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'); 
    // ... 
}; 

Nhưng, tôi đã thực sự viết một mã hơn chút, điều đó sẽ làm giảm sự trùng lặp giữa các plugin là tốt. Đây là cách tạo một plugin sẽ trông vào cuối

(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); 

})(); 

Và đây là cách thực hiện một phần trông giống như:

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; 
}; 

Tất nhiên, điều này là hoàn toàn chưa được kiểm tra :)

+0

Điều đó sẽ khả thi đối với các phương thức "công khai" bên ngoài. Tôi cần để có thể làm 'this.someFunction()' từ "bên trong" 'mp' với callbacks vẫn thực hiện. – vzwick

+0

Tôi đã giải quyết nhận xét của bạn trong chỉnh sửa của tôi. Có thể quá phức tạp đối với trường hợp sử dụng của bạn, nhưng thật thú vị khi viết. – deviousdodo

+0

Đoạn mã gọn gàng! – vzwick

1

Về cơ bản Tôi thích tránh gọi lại và sử dụng các sự kiện để thay thế. Lý do là simle. Tôi có thể thêm nhiều hơn một chức năng để nghe sự kiện đã cho, tôi không phải gây rối với các tham số gọi lại và tôi không phải kiểm tra xem một cuộc gọi lại đã được xác định hay chưa. Theo như tất cả các phương pháp của bạn được gọi thông qua $.fn.myplugin thật dễ dàng để kích hoạt các sự kiện trước và sau khi gọi phương thức.

Dưới đây là một ví dụ mã:

(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"); 
Các vấn đề liên quan