從大廠面試題看觀察者模式

題目

請實現下面的自定義事件 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) {
        //你的代碼
    }
};
複製代碼

差點沒把我看暈...前端

好吧,一步一步來看看怎麼回事。git

①瞭解一下觀察者模式

觀察者模式:github

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

例子:數組

假如咱們正在開發一個商城網站,網站裏有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之間是互相獨立的。

直接測試,發現輸出了

GitHub

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

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

這個是ES6的新對象方法,用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。

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

GitHub

也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。因爲進行測試一的時候調用了on方法,因此event裏面已經有了handles這個可枚舉的屬性。而後再分別合併到兩個person裏面的話,兩個person對象裏面的handles都只是一個引用。因此就互相影響了。

若是assign方法要實現深克隆則要這樣:

GitHub

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

因此,咱們必須將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]);
           }
       }
    }
};
複製代碼

經過這道題,感受考得真的很巧妙並且很考基礎。

最後

  • 這是一篇以前寫的博客,這裏是遷移了過來~~
  • 瞭解更多內容,歡迎關注個人blog, 給我個star~
  • 以爲內容有幫助能夠關注下個人公衆號 「前端Q」,一塊兒學習成長~~
    GitHub
相關文章
相關標籤/搜索