呼呼。。。前不久參加了一個筆試,里面有一到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) { //你的代碼 } };
差點沒把我看暈...
好吧,一步一步來看看怎么回事。
①了解一下觀察者模式
觀察者模式:
這是一種創建松散耦合代碼的技術。它定義對象間 一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。由主體和觀察者組成,主體負責發布事件,同時觀察者通過訂閱這些事件來觀察該主體。主體並不知道觀察者的任何事情,觀察者知道主體並能注冊事件的回調函數。
例子:
基本模式:
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]); } } } };
通過這道題,感覺考得真的很巧妙而且很考基礎。好啦。。。我還是好好復習去了。。。
