談談JS的觀察者模式(自定義事件)


   呼呼。。。前不久參加了一個筆試,里面有一到JS編程題,當時看着題目就蒙圈。。。后來研究了一下,原來就是所謂的觀察者模式。就記下來。。。^_^

題目

[附加題] 請實現下面的自定義事件 Event 對象的接口,功能見注釋(測試1)
該 Event 對象的接口需要能被其他對象拓展復用(測試2)
// 測試1
Event.on('test', function (result) {
    console.log(result);
});
Event.on('test', function () {
    console.log('test');
});
Event.emit('test', 'hello world'); // 輸出 'hello world' 和 'test'
// 測試2
var person1 = {};
var person2 = {};
Object.assign(person1, Event);
Object.assign(person2, Event);
person1.on('call1', function () {
    console.log('person1');
});
person2.on('call2', function () {
    console.log('person2');
});
person1.emit('call1'); // 輸出 'person1'
person1.emit('call2'); // 沒有輸出
person2.emit('call1'); // 沒有輸出
person2.emit('call2'); // 輸出 'person2'
var Event = {
// 通過on接口監聽事件eventName // 如果事件eventName被觸發,則執行callback回調函數 on: function (eventName, callback) { //你的代碼 }, // 觸發事件 eventName emit: function (eventName) { //你的代碼 } };

差點沒把我看暈...

好吧,一步一步來看看怎么回事。

 

①了解一下觀察者模式

觀察者模式

這是一種創建松散耦合代碼的技術。它定義對象間 一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。由主體和觀察者組成,主體負責發布事件,同時觀察者通過訂閱這些事件來觀察該主體。主體並不知道觀察者的任何事情,觀察者知道主體並能注冊事件的回調函數。

例子:

  假如我們正在開發一個商城網站,網站里有header頭部、nav導航、消息列表、購物車等模塊。這幾個模塊的渲染有一個共同的前提條件,就是必須先用ajax異步請求獲取用戶的登錄信息。這是很正常的,比如用戶的名字和頭像要顯示在header模塊里,而這兩個字段都來自用戶登錄后返回的信息。這個時候,我們就可以把這幾個模塊的渲染事件都放到一個數組里面,然后待登錄成功之后再遍歷這個數組並且調用每一個方法。

基本模式:

function EventTarget(){     
    this.handlers = {}; 
} 
EventTarget.prototype = {     
    constructor: EventTarget,
    addHandler: function(type, handler){
         if (typeof this.handlers[type] == "undefined"){
this.handlers[type] = []; } this.handlers[type].push(handler); }, fire: function(event){ if (!event.target){ event.target = this; } if (this.handlers[event.type] instanceof Array){ var handlers = this.handlers[event.type]; for (var i=0, len=handlers.length; i < len; i++){
handlers[i](event); } } }, removeHandler:
function(type, handler){ if (this.handlers[type] instanceof Array){ var handlers = this.handlers[type]; for (var i=0, len=handlers.length; i < len; i++){ if (handlers[i] === handler){ break; } } handlers.splice(i, 1); } } };

大概意思就是,創建一個事件管理器。handles是一個存儲事件處理函數的對象。

addHandle:是添加事件的方法,該方法接收兩個參數,一個是要添加的事件的類型,一個是這個事件的回調函數名。調用的時候會首先遍歷handles這個對象,看看這個類型的方法是否已經存在,如果已經存在則添加到該數組,如果不存在則先創建一個數組然后添加。

fire方法:是執行handles這個對象里面的某個類型的每一個方法。

removeHandle:是相應的刪除函數的方法。

 

好啦,回到題目,分析一下。

②題目中的測試一:

// 測試1
Event.on('test', function (result) {
    console.log(result);
});
Event.on('test', function () {
    console.log('test');
});
Event.emit('test', 'hello world'); // 輸出 'hello world' 和 'test'

意思就是,定義一個叫'test'類型的事件集,並且注冊了兩個test事件。然后調用test事件集里面的全部方法。在這里on方法等價於addHandle方法,emit方法等價於fire方法。其中第一個參數就是事件類型,第二個參數就是要傳進函數的參數。

是不是這個回事呢?很好,那么我們要寫的代碼就是:

var Event = {
    // 通過on接口監聽事件eventName
    // 如果事件eventName被觸發,則執行callback回調函數
    on: function (eventName, callback) {
        //我的代碼
        if(!this.handles){
             this.handles={};    
        }      
       if(!this.handles[eventName]){
            this.handles[eventName]=[];
       }
       this.handles[eventName].push(callback);
    },
    // 觸發事件 eventName
    emit: function (eventName) {
        //你的代碼
       if(this.handles[arguments[0]]){
           for(var i=0;i<this.handles[arguments[0]].length;i++){
               this.handles[arguments[0]][i](arguments[1]);
           }
       }
    }
};

這樣測試,完美地通過了測試一。

③測試二:

var person1 = {};
var person2 = {};
Object.assign(person1, Event);
Object.assign(person2, Event);
person1.on('call1', function () {
    console.log('person1');
});
person2.on('call2', function () {
    console.log('person2');
});
person1.emit('call1'); // 輸出 'person1'
person1.emit('call2'); // 沒有輸出
person2.emit('call1'); // 沒有輸出
person2.emit('call2'); // 輸出 'person2'

大概意思就是為兩個不同person注冊自定義事件,並且兩個person之間是互相獨立的。

直接測試,發現輸出了

這個好像是題目要求有點出入呢,或者這才是題目的坑吧!

解釋一下,Object.assign(person1, Event);

這個是ES6的新對象方法,用於對象的合並,將源對象(source)的所有可枚舉屬性,復制到目標對象(target)。

意思是將Event里面的可枚舉的對象和方法放到person1里面。

也就是說,如果源對象某個屬性的值是對象,那么目標對象拷貝得到的是這個對象的引用。由於進行測試一的時候調用了on方法,所以event里面已經有了handles這個可枚舉的屬性。然后再分別合並到兩個person里面的話,兩個person對象里面的handles都只是一個引用。所以就互相影響了。

如果assign方法要實現深克隆則要這樣:

問題是,題目已經固定了方式,我們不能修改這個方法。

 所以,我們必須將handles這個屬性定義為不可枚舉的,然后在person調用on方法的時候再分別產生handles這個對象。

也就是說正確的做法應該是:

var Event = {
    // 通過on接口監聽事件eventName
    // 如果事件eventName被觸發,則執行callback回調函數
    on: function (eventName, callback) {
        //你的代碼
        if(!this.handles){
            //this.handles={};
            Object.defineProperty(this, "handles", {
                value: {},
                enumerable: false,
                configurable: true,
                writable: true
            })
        }
       
       if(!this.handles[eventName]){
            this.handles[eventName]=[];
       }
       this.handles[eventName].push(callback);
    },
    // 觸發事件 eventName
    emit: function (eventName) {
        //你的代碼
       if(this.handles[arguments[0]]){
           for(var i=0;i<this.handles[arguments[0]].length;i++){
               this.handles[arguments[0]][i](arguments[1]);
           }
       }
    }
};

 

通過這道題,感覺考得真的很巧妙而且很考基礎。好啦。。。我還是好好復習去了。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM