随笔-16  评论-7  文章-0  trackbacks-0

   在写node.js的时候,经常会遇到要去数据库的多个地方取得多个数据,然后才能进行下一步的操作的情况。如果是线性执行的语言,通常的做法是一条一条去取,全部取到之后再进行下一步操作。然而在node里面,因为是基于事件的,所以只能够一层一层的在回调函数里面嵌套进去。取到第一个数据之后,执行回调函数去取第二条数据,然后再执行回调函数。
   
对于node来说,这样是效率低下的,因为很多数据并不是需要先取完A再去取B的,而是可以同时取AB的,然而通过回调函数嵌套的做法,只能够顺序去取数据(和线性执行的语言一样),同时会导致代码嵌套过多难以阅读。
   
解决这个问题的一个方法是通过事件的方式。遇到要取多个数据的时候,就让它们都去取数据,取到数据之后,抛出一个取到数据的事件,同时开一个监听函数,监听这些事件,当所有的数据都取到了,监听函数接收到了所有数据的时候,开始执行后面的步骤。这样可以尽可能的让数据获取能够同步进行(如果有限制关系:如先取到文章才能取到文章的评论,同样可以用事件的方式解决,不过依旧要按照顺序去获取数据),同时代码也会简洁易懂,没有多层的嵌套。

   
然而想法是美好的,node.js提供的eventemit却并不支持监听多个事件。eventProxy模块就是基于事件的对上述问题的一个解决方案。
   https://github.com/JacksonTian/eventproxy @朴灵

1.应用场景:
   1)最前面举例的时候说到,在写node的时候可以并行处理数据请求,解除多重回调嵌套。
   2)在前端页面渲染的时候,可以通过指定当多个事件触发之后(获取模板/获取数据)再开始渲染。
   ...

2.使用方法

通过assign方法监听多个事件:(只监听一次,触发后就被解除(once))
var EventProxy = require('EventProxy.js').EventProxy;
var event = new EventProxy();
event.assign("1""2""3""4"function(){
    
for (var i=0; i!=4++i){
        console.log(arguments[i]);
    }
});

console.log(
"first");
event.emit(
"1"1);
event.emit(
"2"2);
event.emit(
"3"3);
event.emit(
"3"3.5);
event.emit(
"4"4);
console.log(
"second");
event.emit(
"1"1);
event.emit(
"2"2);
event.emit(
"3"3);
event.emit(
"4"4);
/**
 * 结果输出:
 * first
 * 1
 * 2
 * 3
 * 4
 * second
 *
*/
    如果将上面的代码中的assign方法替换成asignAll方法监听多个事件:(持续监听(bind))

event.assignAll("1""2""3""4"function(){
    
for (var i=0; i!=4++i){
        console.log(arguments[i]);
    }
});

/**
 * 结果输出:
 *first
 *1
 *2
 *3
 *4
 *second
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *1
 *2
 *3
 *4
 *
*/

3.源码实现

     eventProxy对于事件的实现方法:

var EventProxy = function () {
    
this._callbacks = {};
    
this._fired = {};
};

 

   _callbacks用来存放所有的回调函数,对于一个事件en,每一次绑定都将在_callbacks中的_callbacks[en]这个数组中插入这个回调函数。(_callbacksobject来作为一个集合容器,而_callbacks[ev]用数组方式,在unbind的时候,需要遍历数组去找到要解除绑定的函数)

EventProxy.prototype.bind = EventProxy.prototype.on = EventProxy.prototype.addListener = function(ev, callback) {
    
var list  = this._callbacks[ev] || (this._callbacks[ev] = []);
    list.push(callback);
    
return this;
};

 

   同样,也可以将事件和回调函数解除绑定。

EventProxy.prototype.unbind = EventProxy.prototype.removeListener = function(ev, callback) {
    
var calls;
    
if (!ev) {//如果没有输入参数,则清除全部回调函数
        this._callbacks = {};
    } 
else if (calls = this._callbacks) {
        
if (!callback) {//如果输入了event,没输入callback,则删除这个event的所有callbacks
            calls[ev] = [];
        } 
else {
        
var list = calls[ev];
        
if (!list) return this;
        
for (var i = 0, l = list.length; i < l; i++) {
            
if (callback === list[i]) {
                list[i] 
= null//最后,将找到的这个回调函数指为null(为什么先指为null而到后面等到trigger的时候再删除?)
                break;
            }
        }
        }
    }
    
return this;
};


这个函数可以看出,js可以很灵活的通过参数传递的不同来实现不同的效果,比之C++的实现方式要容易。


evnetProxy还提供了once函数,注册的回调函数在执行一次之后就会被unbind

EventProxy.prototype.once = function (ev, callback) {
    
var self = this;
    
this.bind(ev, function () {
        callback.apply(self, arguments);
        self.unbind(ev, arguments.callee);
//callee是当前函数的引用
    });
    
return this;
};


同样和eventemit一样,eventProxy也有事件触发函数emit/fire/trigger,触发eventName事件。

EventProxy.prototype.emit = EventProxy.prototype.fire = EventProxy.prototype.trigger = function(eventName, data, data2) {
    
var list, calls, ev, callback, args, i, l;
    
var both = 2;    //其实每次都会触发两个事件
    if (!(calls = this._callbacks)) return this;
    
while (both--) {    
        ev 
= both ? eventName : 'all';    //第一次是触发eventName事件,第二次触发'all'事件,这个事件在后面提及的assign监听多个事件的时候会起到关键作用。
        if (list = calls[ev]) {
            
for (i = 0, l = list.length; i < l; i++) {
                
if (!(callback = list[i])) {
                    list.splice(i, 
1); i--; l--;    //触发一次这个事件的时候,遍历它,同时将unbind时设置为null的事件从数组删除。
                } else {
                    args 
= both ? Array.prototype.slice.call(arguments, 1) : arguments;
                    callback.apply(
this, args);//如果是eventName事件,则将data参数传给callback,如果是'all'事件,则把所有参数传递进去。
                }
            }
        }
    }
    
return this;
};

 


到此为止,已经可以实现node自带的eventEmit的功能了。On/addListener注册事件,加入callback集合中,然后通过emit/fire/trigger来触发这些事件。但是nevetProxy最重要的作用是下面这个_assign方法,它可以同时监听多个事件,只有这些事件都被触发以后才会执行回调函数,同时每一个事件都可以把参数传递给回调函数。

var _assign = function (eventname1, eventname2, cb, once) {
    
var proxy = this, length, index = 0, argsLength = arguments.length,
        callback, events, isOnce, times 
= 0, flag = {};
    
// Check the arguments length.
    if (argsLength < 3) {
        
return this;
    }
    events 
= [].slice.apply(arguments, [0, argsLength - 2]);
    callback 
= arguments[argsLength - 2];
    isOnce 
= arguments[argsLength - 1];
    
// Check the callback type.
    if (typeof callback !== "function") {
        
return this;
    }
    length 
= events.length;
    
var bind = function (key) {
            
var method = isOnce ? "once" : "bind";//函数有两种绑定方法,绑定一次或者绑定多次
            proxy[method](key, function (data) {
                    proxy._fired[key] 
= proxy._fired[key] || {};
                    proxy._fired[key].data 
= data;
                    
if (!flag[key]) {
                        flag[key] 
= true;
                        Times
++;        //计数器,每有一个事件被触发,计数器+1
                    }
                });
        };
    
for (index = 0; index < length; index++) {//对所有要监听事件调用上面的bind方法,为其绑定一个回调函数,当触发这个事件的时候,把数据保存下来。
        bind(events[index]);
    }
    
var all = function () {    //当计数器的计数等于事件数目(说明所有事件都被触发了一次),就把所有获取到的数据汇总,然后交给真正的回调函数cb执行。
        if (times < length) {
            
return;
        }
        
var data = [];
        
for (index = 0; index < length; index++) {
            data.push(proxy._fired[events[index]].data);
        }
        
if (isOnce) {    //如果只监听一次,则解除这个'all'的监听。反之则继续监听这个事件。
            proxy.unbind("all", all);
        }
        callback.apply(
null, data);
    };
    proxy.bind(
"all", all);    //绑定'all'事件和上面的all函数,而每当一个事件被fire的时候,都会抛出一个'all'事件,使得all函数被调用,去检查是否所有被监听的事件都被fire过了。
};
上面的函数被下面两个接口函数调用,实现两种不同的功能。 
//当所有事件都发生之后,执行一次cb,然后撤销监听。 EventProxy.prototype.assign = function (eventname1, eventname2, cb) {
    var args = [].slice.call(arguments);
    args.push(
true);
    _assign.apply(
this, args);
    
return this;
};  
//当所有事件都发生之后,执行一次cb,然后当任何一个事件再发生的时候,就会继续执行cb。
EventProxy.prototype.assignAll = EventProxy.prototype.assignAlways = function () {
    
var args = [].slice.call(arguments);
    args.push(
false);
    _assign.apply(
this, args);
    
return this;
};
assign运用:当在数据库取数据的时候,可以同时去取N个数据,当得到所有的结果之后,再执行后续的处理。
Assignall运用:
1.例如一个股票软件需要定时更新数据在模板ok之前来的数据也没有用的。只有在数据和模板都ok之后,才第一次渲染, 这之后,数据每次更新都需要重新渲染
2.对现有结果进行搜索的场景,之前在过滤数据之后,需要更新dom

通过eventProxy,可以充分的发挥js的异步/基于事件的特性,同步进行多个操作。同时也可以让代码更加简洁自然,消除多重嵌套。

 

posted on 2011-08-18 15:24 dead_horse 阅读(3256) 评论(0)  编辑 收藏 引用 所属分类: node.js

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理