观察者模式

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 1
图片 2

1 赞 5 收藏
评论

关于作者:winty

图片 3

前端工程师,前端爱好者。博客:

个人主页 ·
我的文章 ·
1 ·
 

图片 4

谈谈 JavaScript 的观察者模式(自定义事件)

2016/08/25 · JavaScript
·
观察者模式,
设计模式

本文作者: 伯乐在线 –
winty
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

呼呼,前不久参加了一个笔试,里面有一到JS编程题,当时看着题目就蒙圈。后来研究了一下,原来就是所谓的观察者模式。就记下来
^_^

题目

JavaScript

[附加题] 请实现下面的自定义事件 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′<br>var Event = { //
通过on接口监听事件eventName //
如果事件eventName被触发,则执行callback回调函数 on: function (eventName,
callback) { //你的代码 }, // 触发事件 eventName emit: function
(eventName) { //你的代码 } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[附加题] 请实现下面的自定义事件 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′<br>var Event = {
    // 通过on接口监听事件eventName
    // 如果事件eventName被触发,则执行callback回调函数
    on: function (eventName, callback) {
        //你的代码
    },
    // 触发事件 eventName
    emit: function (eventName) {
        //你的代码
    }
};

差点没把我看晕…

好吧,一步一步来看看怎么回事。

①了解一下观察者模式

观察者模式

这是一种创建松散耦合代码的技术。它定义对象间
一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者的任何事情,观察者知道主体并能注册事件的回调函数。

例子:

假如我们正在开发一个商城网站,网站里有header头部、nav导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。这是很正常的,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登录后返回的信息。这个时候,我们就可以把这几个模块的渲染事件都放到一个数组里面,然后待登录成功之后再遍历这个数组并且调用每一个方法。

基本模式:

JavaScript

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);
} } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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:是相应的删除函数的方法。

好啦,回到题目,分析一下。

观察者模式。②题目中的测试一:

JavaScript

// 测试1 Event.on(‘test’, function (result) { console.log(result); });
Event.on(‘test’, function () { console.log(‘test’); });
Event.emit(‘test’, ‘hello world’); // 输出 ‘hello world’ 和 ‘test’

1
2
3
4
5
6
7
8
// 测试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方法。其中第一个参数就是事件类型,第二个参数就是要传进函数的参数。

是不是这个回事呢?很好,那么我们要写的代码就是:

JavaScript

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]); } } } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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]);
           }
       }
    }
};

这样测试,完美地通过了测试一。

③测试二:

JavaScript

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’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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之间是互相独立的。

直接测试,发现输出了

图片 5

这个好像是题目要求有点出入呢,或者这才是题目的坑吧!

解释一下,Object.assign(person1, Event);

这个是ES6的新对象方法,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

意思是将Event里面的可枚举的对象和方法放到person1里面。

图片 6

也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。由于进行测试一的时候调用了on方法,所以event里面已经有了handles这个可枚举的属性。然后再分别合并到两个person里面的话,两个person对象里面的handles都只是一个引用。所以就互相影响了。

如果assign方法要实现深克隆则要这样:

图片 7

问题是,题目已经固定了方式,我们不能修改这个方法。

所以,我们必须将handles这个属性定义为不可枚举的,然后在person调用on方法的时候再分别产生handles这个对象。

也就是说正确的做法应该是:

JavaScript

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]); } } } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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]);
           }
       }
    }
};

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

打赏支持我写出更多好文章,谢谢!

打赏作者

You may also like...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图