在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

設(shè)計(jì)模式之觀察者模式

介紹

觀察者模式又叫發(fā)布訂閱模式(Publish/Subscribe),它定義了一種一對(duì)多的關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生變化時(shí)就會(huì)通知所有的觀察者對(duì)象,使得它們能夠自動(dòng)更新自己。

使用觀察者模式的好處:

  1. 支持簡(jiǎn)單的廣播通信,自動(dòng)通知所有已經(jīng)訂閱過的對(duì)象。
  2. 頁面載入后目標(biāo)對(duì)象很容易與觀察者存在一種動(dòng)態(tài)關(guān)聯(lián),增加了靈活性。
  3. 目標(biāo)對(duì)象與觀察者之間的抽象耦合關(guān)系能夠單獨(dú)擴(kuò)展以及重用。

正文(版本一)

JS 里對(duì)觀察者模式的實(shí)現(xiàn)是通過回調(diào)來實(shí)現(xiàn)的,我們來先定義一個(gè) pubsub 對(duì)象,其內(nèi)部包含了 3 個(gè)方法:訂閱、退訂、發(fā)布。

var pubsub = {};
(function (q) {
    var topics = {}, // 回調(diào)函數(shù)存放的數(shù)組
        subUid = -1;
    // 發(fā)布方法
    q.publish = function (topic, args) {
        if (!topics[topic]) {
            return false;
        }
        setTimeout(function () {
            var subscribers = topics[topic],
                len = subscribers ? subscribers.length : 0;
            while (len--) {
                subscribers[len].func(topic, args);
            }
        }, 0);
        return true;
    };
    //訂閱方法
    q.subscribe = function (topic, func) {
        if (!topics[topic]) {
            topics[topic] = [];
        }
        var token = (++subUid).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };
    //退訂方法
    q.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 false;
    };
} (pubsub));

使用方式如下:

//來,訂閱一個(gè)
pubsub.subscribe('example1', function (topics, data) {
    console.log(topics + ": " + data);
});
//發(fā)布通知
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);

怎么樣?用起來是不是很爽?但是這種方式有個(gè)問題,就是沒辦法退訂訂閱,要退訂的話必須指定退訂的名稱,所以我們?cè)賮硪粋€(gè)版本:

//將訂閱賦值給一個(gè)變量,以便退訂
var testSubscription = pubsub.subscribe('example1', function (topics, data) {
    console.log(topics + ": " + data);
});
//發(fā)布通知
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);
//退訂
setTimeout(function () {
    pubsub.unsubscribe(testSubscription);
}, 0);
//再發(fā)布一次,驗(yàn)證一下是否還能夠輸出信息
pubsub.publish('example1', 'hello again! (this will fail)');

版本二

我們也可以利用原型的特性實(shí)現(xiàn)一個(gè)觀察者模式,代碼如下:

function Observer() {
    this.fns = [];
}
Observer.prototype = {
    subscribe: function (fn) {
        this.fns.push(fn);
    },
    unsubscribe: function (fn) {
        this.fns = this.fns.filter(
                        function (el) {
                            if (el !== fn) {
                                return el;
                            }
                        }
                    );
    },
    update: function (o, thisObj) {
        var scope = thisObj || window;
        this.fns.forEach(
                        function (el) {
                            el.call(scope, o);
                        }
                    );
    }
};
//測(cè)試
var o = new Observer;
var f1 = function (data) {
    console.log('Robbin: ' + data + ', 趕緊干活了!');
};
var f2 = function (data) {
    console.log('Randall: ' + data + ', 找他加點(diǎn)工資去!');
};
o.subscribe(f1);
o.subscribe(f2);
o.update("Tom回來了!")
//退訂f1
o.unsubscribe(f1);
//再來驗(yàn)證
o.update("Tom回來了!");

如果提示找不到 filter 或者 forEach 函數(shù),可能是因?yàn)槟愕臑g覽器還不夠新,暫時(shí)不支持新標(biāo)準(zhǔn)的函數(shù),你可以使用如下方式自己定義:

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (fn, thisObj) {
        var scope = thisObj || window;
        for (var i = 0, j = this.length; i < j; ++i) {
            fn.call(scope, this[i], i, this);
        }
    };
}
if (!Array.prototype.filter) {
    Array.prototype.filter = function (fn, thisObj) {
        var scope = thisObj || window;
        var a = [];
        for (var i = 0, j = this.length; i < j; ++i) {
            if (!fn.call(scope, this[i], i, this)) {
                continue;
            }
            a.push(this[i]);
        }
        return a;
    };
}

版本三

如果想讓多個(gè)對(duì)象都具有觀察者發(fā)布訂閱的功能,我們可以定義一個(gè)通用的函數(shù),然后將該函數(shù)的功能應(yīng)用到需要觀察者功能的對(duì)象上,代碼如下:

//通用代碼
var observer = {
    //訂閱
    addSubscriber: function (callback) {
        this.subscribers[this.subscribers.length] = callback;
    },
    //退訂
    removeSubscriber: function (callback) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (this.subscribers[i] === callback) {
                delete (this.subscribers[i]);
            }
        }
    },
    //發(fā)布
    publish: function (what) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (typeof this.subscribers[i] === 'function') {
                this.subscribers[i](what);
            }
        }
    },
    // 將對(duì)象o具有觀察者功能
    make: function (o) { 
        for (var i in this) {
            o[i] = this[i];
            o.subscribers = [];
        }
    }
};

然后訂閱 2 個(gè)對(duì)象 blogger 和 user,使用 observer.make 方法將這 2 個(gè)對(duì)象具有觀察者功能,代碼如下:

var blogger = {
    recommend: function (id) {
        var msg = 'dudu 推薦了的帖子:' + id;
        this.publish(msg);
    }
};
var user = {
    vote: function (id) {
        var msg = '有人投票了!ID=' + id;
        this.publish(msg);
    }
};
observer.make(blogger);
observer.make(user);

使用方法就比較簡(jiǎn)單了,訂閱不同的回調(diào)函數(shù),以便可以注冊(cè)到不同的觀察者對(duì)象里(也可以同時(shí)注冊(cè)到多個(gè)觀察者對(duì)象里):

var tom = {
    read: function (what) {
        console.log('Tom看到了如下信息:' + what)
    }
};
var mm = {
    show: function (what) {
        console.log('mm看到了如下信息:' + what)
    }
};
// 訂閱
blogger.addSubscriber(tom.read);
blogger.addSubscriber(mm.show);
blogger.recommend(123); //調(diào)用發(fā)布\
//退訂
blogger.removeSubscriber(mm.show);
blogger.recommend(456); //調(diào)用發(fā)布
//另外一個(gè)對(duì)象的訂閱
user.addSubscriber(mm.show);
user.vote(789); //調(diào)用發(fā)布

jQuery 版本

根據(jù) jQuery1.7 版新增的 on/off 功能,我們也可以定義 jQuery 版的觀察者:

(function ($) {
    var o = $({});
    $.subscribe = function () {
        o.on.apply(o, arguments);
    };
    $.unsubscribe = function () {
        o.off.apply(o, arguments);
    };
    $.publish = function () {
        o.trigger.apply(o, arguments);
    };
} (jQuery));

調(diào)用方法比上面 3 個(gè)版本都簡(jiǎn)單:

//回調(diào)函數(shù)
function handle(e, a, b, c) {
    // `e`是事件對(duì)象,不需要關(guān)注
    console.log(a + b + c);
};
//訂閱
$.subscribe("/some/topic", handle);
//發(fā)布
$.publish("/some/topic", ["a", "b", "c"]); // 輸出abc      
$.unsubscribe("/some/topic", handle); // 退訂
//訂閱
$.subscribe("/some/topic", function (e, a, b, c) {
    console.log(a + b + c);
});
$.publish("/some/topic", ["a", "b", "c"]); // 輸出abc
//退訂(退訂使用的是/some/topic名稱,而不是回調(diào)函數(shù)哦,和版本一的例子不一樣
$.unsubscribe("/some/topic"); 

可以看到,他的訂閱和退訂使用的是字符串名稱,而不是回調(diào)函數(shù)名稱,所以即便傳入的是匿名函數(shù),我們也是可以退訂的。

總結(jié)

觀察者的使用場(chǎng)合就是:當(dāng)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象,并且它不知道具體有多少對(duì)象需要改變的時(shí)候,就應(yīng)該考慮使用觀察者模式。

總的來說,觀察者模式所做的工作就是在解耦,讓耦合的雙方都依賴于抽象,而不是依賴于具體。從而使得各自的變化都不會(huì)影響到另一邊的變化。