題目
請實現下面的自定義事件 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]);
}
}
}
};
複製程式碼
通過這道題,感覺考得真的很巧妙而且很考基礎。
最後
- 這是一篇之前寫的部落格,這裡是遷移了過來~~
- 瞭解更多內容,歡迎關注我的blog, 給我個star~
- 覺得內容有幫助可以關注下我的公眾號 「前端Q」,一起學習成長~~