So, a colleague introduced me to the publish/subscribe pattern (in JS/jQuery), but I'm having a hard time getting to grips with why one would use this pattern over 'normal' JavaScript/jQuery.

For example, previously I had the following code...

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    var orders = $(this).parents('form:first').find('div.order');
    if (orders.length > 2) {
        orders.last().remove();
    }
});

And I could see the merit of doing this instead, for example...

removeOrder = function(orders) {
    if (orders.length > 2) {
        orders.last().remove();
    }
}

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    removeOrder($(this).parents('form:first').find('div.order'));
});

Because it introduces the ability to re-use the removeOrder functionality for different events etc.

But why would you decide to implement the publish/subscribe pattern and go to the following lengths, if it does the same thing? (FYI, I used jQuery tiny pub/sub)

removeOrder = function(e, orders) {
    if (orders.length > 2) {
        orders.last().remove();
    }
}

$.subscribe('iquery/action/remove-order', removeOrder);

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    $.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order'));
});

我确实读过关于这个模式的书,但我无法想象为什么有必要这样做.我看到的教程解释了如何实现这个模式,这些教程只包含了和我自己一样的基本示例.

我想pub/sub的用处会在更复杂的应用程序中显现出来,但我无法想象这样的应用程序.恐怕我完全没有抓住要点,但如果有的话,我想知道要点是什么!

Could you explain succinctly why and in what situations this pattern is advantageous? Is it worth using the pub/sub pattern for code snippets like my examples above?

推荐答案

It’s all about loose coupling and single responsibility, which goes hand to hand with MV* (MVC/MVP/MVVM) patterns in JavaScript which are very modern in the last few years.

Loose coupling是一个面向对象的原则,在这个原则中,系统的每个组件都知道自己的职责,而不关心其他组件(或者至少尽量不关心它们).松耦合是一件好事,因为您可以轻松地重用不同的模块.您没有与其他模块的接口耦合.使用publish/subscribe,您只需要使用publish/subscribe接口,这并不是什么大问题——只有两种方法.所以,如果你决定在另一个项目中重用一个模块,你可以复制并粘贴它,它很可能会工作,或者至少你不需要太多的努力就能让它工作.

当谈到松耦合时,我们应该提到separation of concerns.如果您使用MV*架构模式构建应用程序,那么您总是有一个模型和一个视图.模型是应用程序的业务部分.您可以在不同的应用程序中重用它,因此将它与要显示它的单个应用程序的视图结合使用不是一个好主意,因为通常在不同的应用程序中,您有不同的视图.因此,最好使用发布/订阅进行模型视图通信.当模型发生更改时,它会发布一个事件,视图会捕捉该事件并进行self 更新.发布/订阅没有任何开销,这有助于实现解耦.以同样的方式,您可以将应用程序逻辑保留在控制器中(例如MVVM、MVP,它并不完全是一个控制器),并使视图尽可能简单.当您的视图发生变化(例如,用户单击某个对象)时,它只会发布一个新事件,控制器会捕捉到它并决定要做什么.如果您熟悉MVC模式或Microsoft technologies(WPF/Silverlight)中的MVVM模式,您可以像Observer pattern一样考虑发布/订阅.这种方法在主干网等框架中使用.js,击倒.js(MVVM).

下面是一个示例:

//Model
function Book(name, isbn) {
    this.name = name;
    this.isbn = isbn;
}

function BookCollection(books) {
    this.books = books;
}

BookCollection.prototype.addBook = function (book) {
    this.books.push(book);
    $.publish('book-added', book);
    return book;
}

BookCollection.prototype.removeBook = function (book) {
   var removed;
   if (typeof book === 'number') {
       removed = this.books.splice(book, 1);
   }
   for (var i = 0; i < this.books.length; i += 1) {
      if (this.books[i] === book) {
          removed = this.books.splice(i, 1);
      }
   }
   $.publish('book-removed', removed);
   return removed;
}

//View
var BookListView = (function () {

   function removeBook(book) {
      $('#' + book.isbn).remove();
   }

   function addBook(book) {
      $('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>');
   }

   return {
      init: function () {
         $.subscribe('book-removed', removeBook);
         $.subscribe('book-aded', addBook);
      }
   }
}());

另一个例子.如果你不喜欢MV*方法,你可以使用一些不同的方法(我将在下面描述的方法和最后提到的方法之间有一个交叉点).只需在不同的模块中构建应用程序.例如,看看Twitter.

Twitter Modules

如果你看一下界面,你只会看到不同的框.你可以把每个盒子看作一个不同的模块.例如,你可以发布一条推文.此操作需要更新几个模块.首先,它必须更新你的个人资料数据(左上框),但它也必须更新你的时间线.当然,您可以保留对这两个模块的引用,并使用它们的公共界面分别更新它们,但只发布一个事件更容易(也更好).这将使应用程序的修改更容易,因为耦合更松散.如果你开发了依赖于新推文的新模块,你可以订阅"发布推文"事件并处理它.这种方法非常有用,可以使应用程序非常解耦.你可以很容易地重用你的模块.

Here is a basic example of the last approach (this is not original twitter code it’s just a sample by me):

var Twitter.Timeline = (function () {
   var tweets = [];
   function publishTweet(tweet) {
      tweets.push(tweet);
      //publishing the tweet
   };
   return {
      init: function () {
         $.subscribe('tweet-posted', function (data) {
             publishTweet(data);
         });
      }
   };
}());


var Twitter.TweetPoster = (function () {
   return {
       init: function () {
           $('#postTweet').bind('click', function () {
               var tweet = $('#tweetInput').val();
               $.publish('tweet-posted', tweet);
           });
       }
   };
}());

对于这种方法,有一场精彩的Nicholas Zakas周年演讲.对于MV*方法,我所知道的最好的文章和书籍都是由Addy Osmani出版的.

缺点:您必须小心过度使用发布/订阅.如果你有成百上千个事件,那么管理所有事件可能会变得非常混乱.如果未使用名称空间(或未正确使用名称空间),也可能会发生冲突.一个看起来很像发布/订阅的中介的高级实现可以在这里找到.它具有名称空间和事件"冒泡"等功能,当然,这些功能可以被中断.发布/订阅的另一个缺点是难以进行单元测试,分离模块中的不同功能并独立测试它们可能会变得困难.

Jquery相关问答推荐

通过 jQuery 提取 application/json 数据

使用 jQuery $.ajax() 时如何使用 GET 在请求正文中发送数据

jQuery 绑定点击 *ANYTHING* 但 *ELEMENT*

如何知道所有ajax调用何时完成

JQuery手风琴自动高度问题

JQUERY UI Accordion 开始折叠

使用 jQuery 从 JavaScript 对象中添加/删除项目

javascript 正则表达式用于包含至少 8 个字符、1 个数字、1 个大写和 1 个小写的密码

将返回的 JSON 对象属性转换为(较低的第一个)camelCase

JQuery Ajax Post 导致 500 内部服务器错误

一组元素中具有最大高度的元素

jquery: $(window).scrollTop() 但没有 $(window).scrollBottom()

Jquery:如何判断元素是否具有某些 css 类/样式

CSS3 相当于 jQuery slideUp 和 slideDown?

通过 :not 在 jQuery Select 器中隐藏除 $(this) 之外的所有内容

5秒后jQuery自动隐藏元素

在 javascript/浏览器中缓存 jquery ajax 响应

如何判断值是否为 JSON 对象?

在 Javascript/jQuery 中,(e) 是什么意思?

jQuery:通过 .attr() 添加两个属性;方法