熟悉一个老项目的过程中,许多业务功能是在前端完成的,其中有一个技术点就是如何完成主页面与子页面之间的通信,其中很多js看起来很模糊,后来问同事了解到我们这个项目用到了这么一个js文件
主要功能如下
/*
* 一个类似观察者设计模式 使用的WEB前端发布(publish) 订阅(subscribe) 的小组件 不需要第3方库的依赖
* * 支持像jquery一样的链式调用风格
* *使用ps.pub / ps.publish 发布事件通知
* *使用ps.sub /ps.subscribe 订阅通知
*/
!function(w){
var ps={},
subs={},
tokencache=1,
tokenPre="pubAndSub-";
/**
* 广播订阅者
*/
ps.publish=ps.pub=function(){
var args=getArgsAsArrays(arguments);
if(args.length<=0){
throw new Error('广播事件必须要指定事件类型');
}
var event=args[0];
var calls=subs[event];
if(!calls){//不处理没有用户订阅的事件
return;
}
var ctx = { event: event, args:args.splice(1)},
len=calls.length;
for(var i=0;i<len;i++){
ctx.token=calls[i].token;
calls[i].callback.apply(ctx,ctx.args);
}
return this;
}
/**
* 订阅
*/
ps.subscribe=ps.sub=function(event,call){
var a=subs[event]=subs[event]||[],
instanceToken=tokenPre+tokencache++;
a.push({
token:instanceToken,
callback:call
});
return instanceToken;
}
//util
/**
* 获取参数列表 转换为[]
*/
function getArgsAsArrays(arg){
return Array.prototype.slice.call(arg);
}
w.ps=ps;
}(window);
需要先subscribe,后publish,我们做一个小演示
var person = {
name: "ning",
sayHi: function() {
console.log("person sub......");
}
};
var msg = ps.sub(person,person.sayHi);
ps.pub(person);
我们看看调用sub的代码
ps.subscribe=ps.sub=function(event,call){
var a=subs[event]=subs[event]||[],
instanceToken=tokenPre+tokencache++;
a.push({
token:instanceToken,
callback:call
});
return instanceToken;
}
在整个JS中先定义了subs这个对象,当我们调用ps.sub的时候,为subs添加了一个event属性,值为[],一个空数组,并将这个空数组的引用给了a,instanceToken只是调用subs时返回的信息,暂且忽略,接着我们,为这个空数组传入了对象,subs这个对象就变成了:
{
Object: [{callback:ƒ (),token:"pubAndSub-1" }]
}
其中这个Object就是person对象,ƒ ()就是sayHi那个函数,
那么在调用pub的时候做了什么呢
ps.publish=ps.pub=function(){
var args=getArgsAsArrays(arguments);
if(args.length<=0){
throw new Error('广播事件必须要指定事件类型');
}
var event=args[0];
var calls=subs[event];
if(!calls){//不处理没有用户订阅的事件
return;
}
var ctx = { event: event, args:args.splice(1)},
len=calls.length;
for(var i=0;i<len;i++){
ctx.token=calls[i].token;
calls[i].callback.apply(ctx,ctx.args);
}
return this;
}
getArgsAsArrays是将arguments对象转成数组,在ps.pub(person)时,args就是一个数组,只包含person对象这一个值。
var event=args[0]; //这一行取出数组第一个元素,即person对象
var calls=subs[event]; //拿到这个对象的所有回调函数组成的数组
var ctx = { event: event, args:args.splice(1)} //其中这个对象中的event就是我们的person对象,args.splice(1)则删除了第一个元素,只留下了需要传的参数,在我们这次调用中,只留下一个空数组,因为不需传参。
在for循环中对所有回调函数,以ctx这个对象为作用域,传入args参数,执行函数。
于是在控制台中看到
person sub......
但是如果我们将person中的sayHi改为如下代码时
var person = {
name: "ning",
sayName: function() {
console.log(this.name);
}
};
var msg = ps.sub(person,person.sayName);
ps.pub(person); //undefined
person.sayName(); //ning
这就牵扯到作用域的问题,关键字为apply(),this
当person.sayName()的时候,this代表的是person这个作用域,所欲this.name即ning
但是当使用观察者模式时,调用sayName的代码变为了如下代码
calls[i].callback.apply(ctx,ctx.args);
apply函数的作用是改变函数的作用域,此时调用sayName()的作用域为ctx,ctx中没有name属性,自然就是undefined,如果想要输出名字,则需要将sayName中的代码改为
console.log(this.event.name);//ning
其中的this.event就是ctx对象中的event属性,本次调用中即为person对象