呼呼。。。前不久参加了一个笔试,里面有一到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]);
}
}
}
};

通过这道题,感觉考得真的很巧妙而且很考基础。好啦。。。我还是好好复习去了。。。

谈谈JS的观察者模式(自定义事件)的更多相关文章

  1. JS 中的自定义事件和模拟事件

    在 JS 中模拟事件指的是模拟 JS 中定义的一些事件,例如点击事件,键盘事件等. 自定义事件指的是创建一个自定义的,JS 中之前没有的事件. 接下来分别说一下创建这两种事件的方法. 创建自定义事件 ...

  2. js:实现自定义事件对象接口

    网易2017内推笔试题 要求: 请实现下面的自定义事件Event对象的接口,功能见注释(测试1) 该Event对象的接口需要能被其他对象拓展复用(测试2) //测试1 Event.on('test', ...

  3. js自定义事件CustomEvent、Event、TargetEvent

    1.Event Event 对象代表事件的状态,比如事件在其中发生的元素.键盘按键的状态.鼠标的位置.鼠标按钮的状态. 事件通常与函数结合使用,函数不会在事件发生前被执行! Event的事件都是系统自 ...

  4. Node.js 教程 05 - EventEmitter(事件监听/发射器 )

    目录: 前言 Node.js事件驱动介绍 Node.js事件 注册并发射自定义Node.js事件 EventEmitter介绍 EventEmitter常用的API error事件 继承EventEm ...

  5. 使用jQuery在javascript中自定义事件

    js中的自定义事件有attachEvent,addEventListener等等好多种,往往受困于浏览器兼容,而且代码写起来也相当麻烦.jQuery为我们解决了这个问题,几行代码就可以很好的实现事件的 ...

  6. js自定义事件、DOM/伪DOM自定义事件

    一.说明.引言 我JS还是比较薄弱的,本文的内容属于边学边想边折腾的碎碎念,可能没什么条理,可能有表述不准确的地方,可能内容比较拗口生僻.如果您时间紧迫,或者JS造诣已深,至此您就可以点击右侧广告(木 ...

  7. 漫谈js自定义事件、DOM/伪DOM自定义事件

    一.说明.引言 我JS还是比较薄弱的,本文的内容属于边学边想边折腾的碎碎念,可能没什么条理,可能有表述不准确的地方,可能内容比较拗口生僻.如果您时间紧迫,或者JS造诣已深,至此您就可以点击右侧广告(木 ...

  8. javascript事件之:谈谈自定义事件(转)

    http://www.cnblogs.com/pfzeng/p/4162951.html 对于JavaScript自定义事件,印象最深刻的是用jQuery在做图片懒加载的时候.给需要懒加载的图片定义一 ...

  9. js自定义事件

    自定义事件的本质,创建一个对象,然后把事件的名字作为对象的一个属性,然后value是一个[],把此事件的所以回调都push进去. 写一个很基本的,没有把对象暴露出去的js的自定义事件. var eve ...

随机推荐

  1. 死磕内存篇 --- JAVA进程和linux内存间的大小关系

    运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...

  2. C++中的const

    一,C++中const的基本知识 1.C++中const的基本概念 1.const是定义常量的关键字,表示只读,不可以修改. 2.const在定义常量的时候必须要初始化,否则报错,因为常量无法修改,只 ...

  3. SignalR系列目录

    [置顶]用SignalR 2.0开发客服系统[系列1:实现群发通讯] [置顶]用SignalR 2.0开发客服系统[系列2:实现聊天室] [置顶]用SignalR 2.0开发客服系统[系列3:实现点对 ...

  4. Node.js学习笔记——Node.js开发Web后台服务

    一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...

  5. BPM配置故事之案例7-公式计算

    行政主管发来邮件.要求物资明细表增加"单价""总价"."单价"由其审批时填写,"总价"根据"单价"与 ...

  6. H3 BPM产品安装手册(.Net版本)

    1         安装说明 1.1    服务器安装必备软件 在使用该工作流软件之前,有以下一些软件是必须安装: l  IIS7.0以上版本(必须): l  .Net Framework 4.5(必 ...

  7. Toast显示图文界面——Android开发之路1

    Toast的多种使用方法 Toast其实是一个功能特别强大的组件,不仅仅可以吐司一个文本内容,还可以吐司图片以及图文混排的界面.具体用法如下: 第一种:简单的纯文本内容的吐司: Toast.makeT ...

  8. 萌新笔记——linux下查看内存的使用情况

    windows上有各种软件可以进行"一键加速"之类的操作,释放掉一些内存(虽然我暂时不知道是怎么办到的,有待后续学习).而任务管理器也可以很方便地查看各进程使用的内存情况,如下图: ...

  9. 【一起学OpenFoam】01 OpenFoam的优势

    CFD技术发展到今天,已经超过了大半个世纪了,已经涌现出非常多的CFD软件可供人们使用.通用商业CFD软件譬如Fluent.CFX.Star CCM+等在工业上得到了广泛的应用,另外一些专用的软件(如 ...

  10. 从零开始,DIY一个jQuery(3)

    在前两章,为了方便调试,我们写了一个非常简单的 jQuery.fn.init 方法: jQuery.fn.init = function (selector, context, root) { if ...