资讯专栏INFORMATION COLUMN

javascript设计模式学习笔记之发布-订阅模式

klivitamJ / 1676人阅读

摘要:发布订阅模式定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都将得到通知简单实现定义发布者缓存列表存放订阅者的回调函数定义订阅者发布消息测试订阅者价格订阅者价格发布消息上面的实现方式导致了每个订阅者都会收到发布

发布-订阅模式

定义对象间的一种 一对多 的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都将得到通知

简单实现

    // 定义发布者
    var salesOffices = {};

    // 缓存列表, 存放订阅者的回调函数
    salesOffices.clientList = [];
    
    // 定义订阅者
    salesOffices.listen = function (fn) {
        this.clientList.push(fn);
    }

    // 发布消息
    salesOffices.trigger = function () {
        for (var i = 0, fn; fn = this.clientList[i++];) {
            fn.apply(this, arguments)
        }
    }
    
    /*** 测试 ***/
    
    // 订阅者1
    salesOffices.listen(function (price, squareMeter) {
        console.log("价格=" + price);
        console.log("squareMeter= " + squareMeter);
    });

    // 订阅者2
    salesOffices.listen(function (price, squareMeter) {
        console.log("价格=" + price);
        console.log("squareMeter= " + squareMeter);
    });

    // 发布消息
    salesOffices.trigger(2000, 80);
    salesOffices.trigger(3000, 100);

上面的实现方式, 导致了, 每个订阅者都会收到发布者发布的消息

    // 定义发布者
    var salesOffices = {};

    // 缓存列表, 存放订阅者的回调函数
    salesOffices.clientList = {};

    // 定义订阅者 (增加缓存, 增加标示 key )
    salesOffices.listen = function (key, fn) {
        if (!this.clientList[key]) {
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
    }

    // 发布消息
    salesOffices.trigger = function () {

        var key = Array.prototype.shift.call(arguments),
            fns = this.clientList[key];

        if (!fns || fns.length === 0) {
            return false;
        }

        for (var i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
提炼发布-订阅模式
    // 核心事件
    var event = {
        clientList: {},
        listen: function (key, fn) {
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn);
        },
        trigger: function () {
            var key = Array.prototype.shift.call(arguments),
                fns = this.clientList[key];

            if (!fns || fns.length === 0) {
                return false
            }

            for (var i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments);
            }
        }
    }
    
    // 取消订阅消息
    event.remove = function (key, fn) {
        var fns = this.clientList[key];

        if (!fns) {
            return false;
        }

        if (!fn) {
            // 没有传入fn 取消key对应的所有的订阅
            fns && (fns.length = 0);
        } else {
            // 传入fn 删除对应的fn
            for (var l = fns.length - 1; l >= 0; l--) {
                var _fn = fns[l];
                if (_fn === fn) {
                    fns.splice(l, 1)
                }
            }
        }
    }
    
    // 给任何对象动态增加发布-订阅功能
    var installEvent = function (obj) {
        for (var key in event) {
            if (event.hasOwnProperty(key)) {
                obj[key] = event[key];
            }
        }
    }
    
    /*** 测试 ***/
    var salesOffices = {};

    installEvent(salesOffices);

    // 订阅者1
    salesOffices.listen("squareMeter80", function (price) {
        console.log("价格=" + price);
    });

    // 订阅者2
    salesOffices.listen("squareMeter100", function (price) {
        console.log("价格=" + price);
    });

    // 发布消息
    salesOffices.trigger("squareMeter80", 20000);
    salesOffices.trigger("squareMeter100", 30000);

登录案例

    // 登录成功, 发布成功消息
    $.ajax("http://xxx.com/login", function (data) {
        login.trigger("loginSuccess", data);
    });

    // 这种写法也很好
    var header = (function () {
        // 订阅消息
        login.listen("loginSuccess", function (data) {
            header.setAvatar(data);
        })
        return {
            setAvatar: function (data) {
                console.log("header 模块拿到用户信息")
            }
        }
    })();

以上写法会有三个问题

需要一个类似"中介者"的角色, 把发布者和订阅者联系起来(通过 全局的 Event 来解决)

以上必须先订阅, 才能发布

全局命名的冲突

实现类似 Event 的发布-订阅模式

优点1: 时间上的解耦,

优点2: 对象之间的解耦

缺点1: 创建订阅者本生要消耗内存和时间

缺点2: 弱化了对象之间的联系之后, 对象之间的必要联系也被埋没

    var Event = (function() {
        var global = this,
            Event,
            _default = "default";

        Event = function () {
            var _listen,
                _trigger,
                _remove,
                _slice = Array.prototype.slice,
                _shift = Array.prototype.shift,
                _unshift = Array.prototype.unshift,
                namespaceCache = {},
                _create,
                find,
                each = function(arr, fn) {
                    var ret;
                    for (var i = 0, l = arr.length; i < l; i++) {
                        var n = arr[i];
                        ret = fn.call(n, i, n);
                    }
                    return ret;
                };

            _listen = function (key, fn, cache) {
                if (!cache[key]) {
                    cache[key] = [];
                }
                cache[key].push(fn);
            };

            _remove = function (key, cache, fn) {
                if (cache[eky]) {
                    if (fn) {
                        for (var i = cache[key].length - 1; i >= 0; i--) {
                            if (cache[key][i] === fn) {
                                cache[key].splice(i, 1);
                            }
                        }
                    } else {
                        cache[key] = [];
                    }
                }
            }

            _trigger = function () {
                var cache = _shift.call(arguments),
                    key = _shift.call(arguments),
                    args = arguments,
                    _self = this,
                    ret,
                    stack = cache[key];

                if (!stack || !stack.length) {
                    return;
                }

                return each(stack, function () {
                    return this.apply(_self, args);
                })
            };

            _create = function (namespace) {
                namespace = namespace || _default;
                var cache = {},
                    offlineStack = [],
                    ret = {
                        listen: function (key, fn, last) {
                            _listen(key, fn, cache);

                            if (offlineStack === null) {
                                return;
                            }

                            if (last === "last") {
                                offlineStack.length && offlineStack.pop()();
                            } else {
                                each(offlineStack, function () {
                                    this();
                                })
                            }

                            offlineStack = null;
                        },

                        one: function (key, fn, last) {
                            _remove(key, fn, cache);
                            this.listen(key, fn, last);
                        },

                        remove: function (key, fn) {
                            _remove(key, cache, fn);
                        },

                        trigger: function () {
                            var fn,
                                args,
                                _self = this;

                            _unshift.call(arguments, cache);

                            args = arguments;

                            fn = function () {
                                return _trigger.apply(_self, args);
                            }

                            if (offlineStack) {
                                return offlineStack.push(fn);
                            }

                            return fn();
                        }
                    };

                return  namespace
                            ? (namespaceCache[namespace]
                                ? namespaceCache[namespace]
                                : namespaceCache[namespace] = ret)
                            : ret
            }

            return {
                create: _create,
                one: function (key, fn, last) {
                    var event = this.create();
                    event.one(key, fn, last);
                },
                remove: function (key, fn) {
                    var event = this.create();
                    event.remove(key, fn);
                },
                listen: function (key, fn, last) {
                    var event = this.create();
                    event.listen(key, fn, last);
                },
                trigger: function () {
                    var event = this.create();
                    event.trigger.apply(this, arguments);
                }
            }
        }()

        return Event;
    })();
观察者(observer) 模式 和 发布/订阅模式 之间的区别

本质上的区别及时在调度的地方不同

分清楚谁是发布者, 谁是观察者, 谁是订阅者

观察者模式

    // subject(发布) 中的目标发生变化. Observer(观察) 能接受到变化
    function ObserverList() {
        this.observerList = [];
    }

    ObserverList.prototype.add = function (obj) {
        return this.observerList.push(obj);
    }
    
    ObserverList.prototype.get = function (index) {
        if (index > -1 && this.observerList.length) {
            return this.observerList[index];
        }
    }

    ObserverList.prototype.indexOf = function (obj, startIndex) {
        var i = startIndex;

        while(i < this.observerList.length) {
            if (this.observerList[i] === obj) {
                return i;
            }
            i++;
        }

        return -1;
    }
    
    ObserverList.prototype.removeAt = function (index) {
        this.observerList.splice(index, 1);
    }
    
    // 发布者
    function Subject() {
        this.observers = new ObserverList();
    }

    Subject.prototype.addObserver = function (observer) {
        this.observers.add(observer);
    }

    Subject.prototype.removeObserver = function (observer) {
        this.observers.removeAt(this.observers.indexOf(observer, 0));
    }

    Subject.prototype.notify = function (context) {
        var observerCount = this.observers.count();
        for (var i = 0; i < observerCount; i++) {
            this.observers.get(i).update(context);
        }
    }

    // 观察者
    function Observer() {
        this.update = function () {
            // ...
        }
    }

发布(Publish)/订阅(Subscribe)模式

    var pubsub = {};
    (function (myObject) {
        var topics = {};

        var subUid = -1;

        // 发布
        myObject.publish = function (topic, args) {
            if (!topics[topic]) {
                return false;
            }

            var subscribers = topics[topic],
                len = subscribers ? subscribers.length : 0;

            while (len--) {
                subscribers[len].func(topic, args);
            }

            return this;
        }

        // 订阅者
        myObject.subscribe = function (topic, func) {
            if (!topics[topic]) {
                topics[topic] = [];
            }

            var token = (++subUid).toString();

            topics[topic].push({
                token: token,
                func: func
            })
        }

        // 取消订阅
        myObject.unsubscribe = function (token) {
            for (var m in topics) {
                if (topics[m]) {
                    for (var i = 0, j = topics[m].length; i < j; i++) {
                        if (topics[m][i].token === token) {
                            topics[m].splice(i, 1);
                            return token;
                        }
                    }
                }
            }
            return this;
        }
    })(pubsub)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104533.html

相关文章

  • 前端学习笔记观察者模式

    摘要:观察者模式也称发布订阅模式它的作用就是当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,自动刷新对象状态举个生活比较常见常见的例子比如你去面试之后,面试官看你表现不错,最后会跟你要联系方式,以便之后可以联系你。 观察者模式也称发布-订阅模式,它的作用就是当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,自动刷新对象状态 举个生活比较常见常见的例子,比如你去面试之后,...

    tommego 评论0 收藏0
  • JavaScript设计模式发布-订阅模式(观察者模式)-Part1

    摘要:设计模式与开发实践读书笔记。发布订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系。附设计模式之发布订阅模式观察者模式数据结构和算法系列栈队列优先队列循环队列设计模式系列设计模式之策略模式 《JavaScript设计模式与开发实践》读书笔记。 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。 例...

    muzhuyu 评论0 收藏0
  • vue 双向数据绑定的实现学习(一)

    摘要:双向数据绑定简言之数据动页面动,页面动,数据动典型的应用就是在做表单时候,输入框的内容改动后,跟该输入框的的值改动。看官网上的这个的演示案例双向数据绑定的好处要说出这个好处的时候,也只有在实际场景中才能对应的显示出来。 前言:本系列学习笔记从以下几个点展开 什么是双向数据绑定 双向数据绑定的好处 怎么实现双向数据绑定 实现双向数据数据绑定需要哪些知识点 数据劫持 发布订阅模式 ...

    anonymoussf 评论0 收藏0
  • JavaScript设计模式发布-订阅模式(观察者模式)-Part2

    摘要:设计模式与开发实践读书笔记。看此文章前,建议先看设计模式之发布订阅模式观察者模式在中,已经介绍了什么是发布订阅模式,同时,也实现了发布订阅模式。 《JavaScript设计模式与开发实践》读书笔记。 看此文章前,建议先看JavaScript设计模式之发布-订阅模式(观察者模式)-Part1 在Part1中,已经介绍了什么是发布-订阅模式,同时,也实现了发布-订阅模式。但是,就Part1...

    Charlie_Jade 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<