javascript面向对象--观察者模式(为对象添加事件监听功能)

正文开始之前,有这么一个需求:

>背景:宁静的夜晚,一天晚上,狗蛋(主人)睡得正香,侠盗高飞(小偷)突然造访。

>事件:高飞正准备下手的时候,不料被旺财(狗)发现了,于是旺财叫了起来,狗蛋醒了,高飞逃走了。

分析需求后,可以发现有三个对象:

  1. 高飞(小偷)
  2. 旺财(狗)
  3. 狗蛋(主人)
创建对象的构造函数以及原型方法
//人的构造函数
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();
//控制台结果:  高飞在偷东西  旺财叫了

上面的代码实现了:高飞偷东西 --触发--> 旺财叫了

上述方案存在的问题:

  1. 设置回调函数时似乎太暴力了,我们可以将这一过程封装成高飞的方法;
  2. callback暴露在全局作用域中对全局变量造成污染;
  3. 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()或者其他方法。

改进方案:

  1. 改造eventListener方法,使之能够通过参数指定需要监控事件;
  2. 将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();
//控制台结果:
/**     狗蛋睡着了-->
*       高飞在偷东西-->
*       旺财汪!汪!汪!-->
*       狗蛋惊醒了-->
*       高飞逃跑了-->
**/
至此,需求已经实现了,上述为对象构建事件监听功能采用的模式就是面向对象中的 “观察者模式”。