呼呼。。。前不久参加了一个笔试,里面有一到 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]);
- }
- }
- }
- };
通过这道题,感觉考得真的很巧妙而且很考基础。好啦。。。我还是好好复习去了。。。
来源: http://www.cnblogs.com/LuckyWinty/p/5796190.html