谈谈JS的观察者模式(自定义事件)
呼呼。。。前不久参加了一个笔试,里面有一到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]);
- }
- }
- }
- };
通过这道题,感觉考得真的很巧妙而且很考基础。好啦。。。我还是好好复习去了。。。
谈谈JS的观察者模式(自定义事件)的更多相关文章
- JS 中的自定义事件和模拟事件
在 JS 中模拟事件指的是模拟 JS 中定义的一些事件,例如点击事件,键盘事件等. 自定义事件指的是创建一个自定义的,JS 中之前没有的事件. 接下来分别说一下创建这两种事件的方法. 创建自定义事件 ...
- js:实现自定义事件对象接口
网易2017内推笔试题 要求: 请实现下面的自定义事件Event对象的接口,功能见注释(测试1) 该Event对象的接口需要能被其他对象拓展复用(测试2) //测试1 Event.on('test', ...
- js自定义事件CustomEvent、Event、TargetEvent
1.Event Event 对象代表事件的状态,比如事件在其中发生的元素.键盘按键的状态.鼠标的位置.鼠标按钮的状态. 事件通常与函数结合使用,函数不会在事件发生前被执行! Event的事件都是系统自 ...
- Node.js 教程 05 - EventEmitter(事件监听/发射器 )
目录: 前言 Node.js事件驱动介绍 Node.js事件 注册并发射自定义Node.js事件 EventEmitter介绍 EventEmitter常用的API error事件 继承EventEm ...
- 使用jQuery在javascript中自定义事件
js中的自定义事件有attachEvent,addEventListener等等好多种,往往受困于浏览器兼容,而且代码写起来也相当麻烦.jQuery为我们解决了这个问题,几行代码就可以很好的实现事件的 ...
- js自定义事件、DOM/伪DOM自定义事件
一.说明.引言 我JS还是比较薄弱的,本文的内容属于边学边想边折腾的碎碎念,可能没什么条理,可能有表述不准确的地方,可能内容比较拗口生僻.如果您时间紧迫,或者JS造诣已深,至此您就可以点击右侧广告(木 ...
- 漫谈js自定义事件、DOM/伪DOM自定义事件
一.说明.引言 我JS还是比较薄弱的,本文的内容属于边学边想边折腾的碎碎念,可能没什么条理,可能有表述不准确的地方,可能内容比较拗口生僻.如果您时间紧迫,或者JS造诣已深,至此您就可以点击右侧广告(木 ...
- javascript事件之:谈谈自定义事件(转)
http://www.cnblogs.com/pfzeng/p/4162951.html 对于JavaScript自定义事件,印象最深刻的是用jQuery在做图片懒加载的时候.给需要懒加载的图片定义一 ...
- js自定义事件
自定义事件的本质,创建一个对象,然后把事件的名字作为对象的一个属性,然后value是一个[],把此事件的所以回调都push进去. 写一个很基本的,没有把对象暴露出去的js的自定义事件. var eve ...
随机推荐
- jsp中出现onclick函数提示Cannot return from outside a function or method
在使用Myeclipse10部署完项目后,原先不出错的项目,会有红色的叉叉,JSP页面会提示onclick函数错误 Cannot return from outside a function or m ...
- Partition2:对表分区
在SQL Server中,普通表可以转化为分区表,而分区表不能转化为普通表,普通表转化成分区表的过程是不可逆的,将普通表转化为分区表的方法是: 在分区架构(Partition Scheme)上创建聚集 ...
- 06.LoT.UI 前后台通用框架分解系列之——浮夸的图片上传
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- Spring的数据库开发
Spring JDBC框架操作mysql数据库 Spring中的JDBC为我们省去连接和关闭数据库的代码,我们着重关注对数据库的操作.Sprin ...
- ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存
ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存 part 1:给我点时间,允许我感慨一下2016年 正好有时间,总结一下最近使用的一些技术,也算是为2016年画上一个完 ...
- notepad++设置默认打开txt文件失效的解决方法
1.系统环境 win10企业版,64位系统 2.初步设置 设置txt默认为notepad++打开,菜单:设置->首选项->文件关联 选择对应的文件扩展,点击"关闭"按钮 ...
- Create a Team in RHEL7
SOLUTION VERIFIED September 13 2016 KB2620131 Environment Red Hat Enterprise Linux 7 NetworkManager ...
- java springMVC SSM 操作日志 4级别联动 文件管理 头像编辑 shiro redis
A 调用摄像头拍照,自定义裁剪编辑头像 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,开发利器)+快速构建表单; 技术:313596790freemaker模版技术 ,0个代码不用写 ...
- Spring(三)__aop编程
aop( aspect oriented programming ) 面向切面编程,是对所有对象或者是一类对象编程 几个重要的概念: 1.切面(aspect):要实现的交叉功能,是系统模块化的一个切面 ...
- css样式之background详解
background用法详解: 1.background-color 属性设置元素的背景颜色 可能的值 color_name 规定颜色值为颜色名称的背景颜色(比如 red) he ...