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