javascript面向对象--观察者模式(为对象添加事件监听功能)
正文开始之前,有这么一个需求:
>背景:宁静的夜晚,一天晚上,狗蛋(主人)睡得正香,侠盗高飞(小偷)突然造访。
>事件:高飞正准备下手的时候,不料被旺财(狗)发现了,于是旺财叫了起来,狗蛋醒了,高飞逃走了。
分析需求后,可以发现有三个对象:
- 高飞(小偷)
- 旺财(狗)
- 狗蛋(主人)
//人的构造函数
function Person(name){
this.name=name;
//首次创建实例时,为Person的原型添加共有的方法
if(!Person.prototype.sleep){
//人共有的方法
Person.prototype.sleep=function(){
console.log(this.name+'睡着了');
}
Person.prototype.wake=function(){
console.log(this.name+'惊醒了');
}
}
}
//小偷的构造函数
function Thief(name){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
}
}
}
//狗的构造函数
function Dog(name){
this.name=name;
//首次创建实例时,为Dog的原型添加共有的方法
if(!Dog.prototype.wow){
Dog.prototype.wow=function(){
console.log(this.name+'汪!汪!汪!');
}
}
}进一步分析需求,可以发现每个对象的动作是通过某种事件驱动的,比如:高飞偷东西-->旺财叫了
自然地想到了:在Thief.prototype.steal()方法中传入回调函数
//创建一个变量将来存储回调函数的引用
var callback=null;
//小偷的构造函数
function Thief(name){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
//添加了回调函数
if(callback){
callback();
}
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
}
}
}
var gaofei=new Thief('高飞');
callback=function(){
console.log('旺财叫了');
}
gaofei.steal();
//控制台结果: 高飞在偷东西 旺财叫了上面的代码实现了:高飞偷东西 --触发--> 旺财叫了
上述方案存在的问题:
- 设置回调函数时似乎太暴力了,我们可以将这一过程封装成高飞的方法;
- callback暴露在全局作用域中对全局变量造成污染;
- callback被所有实例共享,而合理的做法是让每个实例应该拥有自己独有的callback变量;
function Thief(name){
var callback=null;
return (function(){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
//添加了回调函数
if(callback){
callback();
}
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
}
//添加事件监听的方法
Thief.prototype.eventListener=function(fn){
callback=fn;
}
}
}).call(this);
}
var gaofei=new Thief('高飞');
gaofei.eventListener(function(){
console.log('旺财叫了');
})
gaofei.steal();
var difei=new Thief('低飞');
difei.eventListener(function(){
console.log('大黄叫了');
})
difei.steal();
//控制台结果: 高飞在偷东西 旺财叫了
// 低飞在偷东西 大黄叫了
可以看到上面两个实例拥有了自己独有的callback变量
但是还有缺陷:
callback是一个变量,只能存储一个函数地址,所以最多只能绑定一个回调函数,正常的需求是能够绑定多个函数;
解决方案:
将callback定义为一个数组
function Thief(name){
var callback=[];
return (function(){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
//添加了回调函数
callback.forEach(function(fn,index){
fn();
})
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
}
//添加事件监听的方法
Thief.prototype.eventListener=function(fn){
callback.push(fn);
}
}
}).call(this);
}
var gaofei=new Thief('高飞');
gaofei.eventListener(function(){
console.log('旺财叫了');
});
gaofei.eventListener(function(){
console.log('大黄也叫了');
})
gaofei.steal();
//控制台结果:高飞在偷东西
// 旺财叫了
// 大黄也叫了上面的代码实现了事件触发多个回调函数的功能
但是事件监听方法eventListener只能监听gaifei.steal(),正常情况下我们可能还需要监听gaofei.escape()或者其他方法。
改进方案:
- 改造eventListener方法,使之能够通过参数指定需要监控事件;
- 将callback变量定义为一个对象,用不同的属性存储不同事件的回调函数。
function Thief(name){
var callback={};
return (function(){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
//如果callback.steal存在,则执行里面的回调函数
if(callback.steal){
callback.steal.forEach(function(fn,index){
fn();
})
}
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
//如果callback.escape存在,则执行里面的回调函数
if(callback.escape){
callback.escape.forEach(function(fn,index){
fn();
})
}
}
//添加事件监听的方法
Thief.prototype.eventListener=function(eventType,fn){
//如果对象callback中不存在eventType属性,则初始化一个eventType属性;
callback[eventType]=callback[eventType]?callback[eventType]:[];
//推入回调函数
callback[eventType].push(fn);
}
}
}).call(this);
}
var gaofei=new Thief('高飞');
gaofei.eventListener('steal',function(){
console.log('旺财叫了');
});
gaofei.eventListener('escape',function(){
console.log('要跑就要跑快点!');
})
gaofei.steal();// 高飞在偷东西-->旺财叫了
gaofei.escape();// 高飞逃跑了-->要跑就要跑快点! 这样,对象的事件监听功能基本上完善了,回到最初的需求,我们为 狗蛋 旺财 也增加事件监听方法://小偷的构造函数
function Thief(name){
var callback={};
return (function(){
this.name=name;
//首次创建实例时,为Thief的原型添加共有的方法
if(!Thief.prototype.steal){
Thief.prototype.steal=function(){
console.log(this.name+'在偷东西');
//如果callback.steal存在,则执行里面的回调函数
if(callback.steal){
callback.steal.forEach(function(fn,index){
fn();
})
}
}
Thief.prototype.escape=function(){
console.log(this.name+'逃跑了');
//如果callback.escape存在,则执行里面的回调函数
if(callback.escape){
callback.escape.forEach(function(fn,index){
fn();
})
}
}
//添加事件监听的方法
Thief.prototype.eventListener=function(eventType,fn){
//如果对象callback中不存在eventType属性,则初始化一个eventType属性;
callback[eventType]=callback[eventType]?callback[eventType]:[];
//推入回调函数
callback[eventType].push(fn);
}
}
}).call(this);
}
//狗的构造函数
function Dog(name){
var callback={};
return (function(){
this.name=name;
//首次创建实例时,为Dog的原型添加共有的方法
if(!Dog.prototype.wow){
Dog.prototype.wow=function(){
console.log(this.name+'汪!汪!汪!');
if(callback.wow){
callback.wow.forEach(function(fn,index){
fn();
})
}
}
Dog.prototype.eventListener=function(eventType,fn){
//如果对象callback中不存在eventType属性,则初始化一个eventType属性;
callback[eventType]=callback[eventType]?callback[eventType]:[];
//推入回调函数
callback[eventType].push(fn);
}
}
}).call(this);
}
//人的构造函数
function Person(name){
var callback={};
return (function(){
this.name=name;
//首次创建实例时,为Person的原型添加共有的方法
if(!Person.prototype.sleep){
//人共有的方法
Person.prototype.sleep=function(){
console.log(this.name+'睡着了');
if(callback.sleep){
callback.sleep.forEach(function(fn,index){
fn();
})
}
}
Person.prototype.wake=function(){
console.log(this.name+'惊醒了');
if(callback.wake){
callback.wake.forEach(function(fn,index){
fn();
})
}
}
Person.prototype.eventListener=function(eventType,fn){
//如果对象callback中不存在eventType属性,则初始化一个eventType属性;
callback[eventType]=callback[eventType]?callback[eventType]:[];
//推入回调函数
callback[eventType].push(fn);
}
}
}).call(this);
}
var gaofei = new Thief('高飞');
var goudan = new Person('狗蛋');
var wangcai = new Dog('旺财');
goudan.eventListener('sleep', gaofei.steal.bind(gaofei));//将gaofei.steal中的this强制绑定到gaofei上
gaofei.eventListener('steal', wangcai.wow.bind(wangcai));//同理
wangcai.eventListener('wow', goudan.wake.bind(goudan));//同理
wangcai.eventListener('wow', gaofei.escape.bind(gaofei));//同理
goudan.sleep();
//控制台结果:
/** 狗蛋睡着了-->
* 高飞在偷东西-->
* 旺财汪!汪!汪!-->
* 狗蛋惊醒了-->
* 高飞逃跑了-->
**/ 至此,需求已经实现了,上述为对象构建事件监听功能采用的模式就是面向对象中的 “观察者模式”。 相关推荐
心理学哲学批判性思维 2017-12-31