原文网址:http://www.cnblogs.com/LuckyWinty/p/5796190.html
[附加题] 请实现下面的自定义事件 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) { //你的代码 } };
上面的这道题就是所谓的js的观察者模式
所谓观察者模式就是一种创建耦合代码的技术。它定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时候,所有依赖于它的对象都将得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者任何事情,观察者知道主体并能注册事件的回调函数。
例子:
假如我们正在开发一个商城网站,网站里有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.lenth;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); } } };
现在回到题目中的测试一:
利用的是观察者模式:
var Event={
handles:{},
on:function(eventtype,callback){
if(!this.handles[eventtype]){this.handles[eventtype]=[];
}
this.handles[eventtype].push(callback);
},
emit:function(eventtype){
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]); } } } };