精华 对EventProxy模块代码的研究和心得。。。
发布于 3年前 作者 jackybing 6134 次浏览
   如果大家研究过node_club源码,想必不会对EventProxy模块感到陌生吧!该模块是由@朴灵所创作出来的,该模块有如下几大特点:
  • 利用事件机制解耦复杂业务逻辑
  • 移除被广为诟病的深度callback嵌套问题
  • 将串行等待变成并行等待,提升多异步场景下的执行效率
  • 无平台依赖,适合前后端,能用于浏览器和Node.js

想要获取eventproxy源码的童鞋们可以通过命令npm install eventproxy或者直接去github网站获取,网址为:[enter link description here][1]

[1]: https://github.com/JacksonTian/eventproxy 那里面有较为详细的介绍,好了,接下来我们步入正题,进行对eventproxy模块源码的探究之旅吧!

由于eventproxy模块中的其他方法都相对来说比较简单,这里,我就不再多费笔墨来解读呢,主要针对其中的_assign方法进行分析,它也是eventproxy的核心所在吧!

var _assign = function (eventname1, eventname2, cb, once){...}

很多内置的函数都使用到了该函数,比如:EventProxy.prototype.all和EventProxy.prototype.tail等方法。好了,接下来我们将其源码剪切下来,其中有对关键代码的详细注释,相信大家一看就很清楚呢!

    var _assign = function (eventname1, eventname2, cb, once) {
    var proxy = this, length, index = 0, argsLength = arguments.length,
        bind, _all,
        callback, events, isOnce, times = 0, flag = {};

    // Check the arguments length.
    if (argsLength < 3) {
        //由于参数列表的最后两项分别是回调函数
        //和是否只进行一次监听的标识位,因此加上要监听一事件,
        //所以参数列表的长度至少是3位或者3位以上,否则直接返回。
        return this;
    }
    
    //获取所有要进行监听的事件名
    events = Array.prototype.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;
    //bind函数是关键,主要用于各个需要监听的事件绑定到一个指定函数里,主要通过
    //method指定的方法进行事件的绑定(once或者bind函数),同时在function(data){...}
    //函数里对将传递过来的参数记录下来,以便之后传递给回调函数,并且在这个函数里地相应事件的触发
    //进行计数,以便在所有监听的事件都触发后,对callback函数进行回调,见下文
    bind = function (key) {
        var method = isOnce ? "once" : "bind";
        //proxy[method]事实就是指eventproxy模块上下文的bind和once事件,进行相应的绑定操作
        proxy[method](key, function (data) {
            proxy._fired[key] = proxy._fired[key] || {};
            //对相应事件传递过来的实参进行记录
            proxy._fired[key].data = data;
            if (!flag[key]) {
                flag[key] = true;
                //times用于对触发相应事件时完成对其的计数功能
                times++;
            }
        });
    };

    for (index = 0; index < length; index++) {
        //依次对监听的事件进行绑定,使用上面的bind函数(注意不是上下文的bind函数)
        bind(events[index]);
    }

    _all = function () {
        if (times < length) {
            //这里是重点,作用是为了判断是否所有的监听事件都已经完成了触发动作(可以通过emit事件)
            //,如果还有未触发的事件,则跳出当前函数,也就放弃了对回调函数的调用过程,理解这点对
            //assign函数的原理也就差不多理清楚了
            return;
        }
        var data = [];
        for (index = 0; index < length; index++) {
            //获取所有监听事件所传递过来的实参,以便给回调函数使用
            data.push(proxy._fired[events[index]].data);
        }
        if (isOnce) {
            //当isOnce为true时,则将all事件进行去除绑定,那么回调函数也只会在
            //所有监听事件触发完成后被调用一次,此后便不会再进行回调。
            //相反,如果当isOnce为false时,则在所有监听事件触发完成之后的时间里,
            //只要触发任意的监听函数都会对回调函数进行回调调用。这也就assign和assignAll的本质差异!!!
            proxy.unbind("all", _all);
        }
        //对回调函数进行回调(需要理解javascript的apply或者cal方法的使用)
        callback.apply(null, data);
    };
    //对_all事件进行绑定,以便能够正确对回调函数进行回调
    proxy.bind("all", _all);
};

以上便是对_assign方法的详细说明,注释已经写得比较清楚呢。如果对上述方法的原理理解透彻呢,想必会对看懂node_club源码有所帮助的。顺便提醒一个,上述函数中涉及到了javascript的函数闭包的内容,比如为什么_assign函数中的局部变量times能够一起存活下去?这里大家就需要对函数闭包的知识有所了解才能弄懂了,在这里只是顺带提一下,应该说闭包的内容还是比较复杂的,三言两语也说不清,不懂的就直接去找相关资料进行学习吧。如果有机会,自己也会把自己对对闭包的理解写出来,供大家评阅!!!上述有什么表述不清或者不准确的地方欢迎大家的随时拍砖!!!共同学习!!!

16 回复

貌似相对 step没啥特别的优势呢?

step?不太了解您所谓的step概念,是否可以说得具体些?至于eventproxy本身就是一个轻量级的异步/基于事件的的模块,至于本身所具有的特点已经在文章中有所提及呢。当然自己也完全可以参考本思路,自己开发出一个基于异步/基于事件的模块出来!

谢谢分享,看完eventproxy源码之后,再看nodeclue就简单易懂了。

虽然说eventproxy可以用在前端,不过好像也不太适合前端用。

共同进步!!!

今天才看到这个帖子。太感谢楼主了。

当初看step的时候,还不支持并行。

可能前端的应用场景不太相同。但是如果用模块和事件较多的话,它就会体现出价值的。

要用Jscex哇!

我用的时候出现了这个问题:Error: db object already connecting, open cannot be called multiple times 望指教~ 参考他的readMe的例子

var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {
  _.template(template, data, l10n);
});

$.get("template", function (template) {
  // something
  ep.emit("template", template);
});
$.get("data", function (data) {
  // something
  ep.emit("data", data);
});
$.get("l10n", function (l10n) {
  // something
  ep.emit("l10n", l10n);
});

似乎是因为我的get方法里面用了mongodb.open()这样的方法造成的不能多次打开数据库问题 我的代码:

var render = function (user, blogs) {
        res.render("user", {title:user.name, blogs:blogs});

    }
    EventProxy.create("user", "blogs", render);
    User.get(req.params.user, function (err, user) {
        if (!user) {
            req.flash("error", "用户不存在");
            return res.redirect("/");
        }
        proxy.emit("user", user);
    });
    Blog.get(req.params.user, function (err, blogs) {
        if (err) {
            req.flash("error", err);
            return res.redirect("/");
        }
        proxy.emit("blogs", blogs);
    });

Blog.get 代码:

Blog.get = function get(username, callback) {
        mongodb.open(function (err, db) {
            if (err) {
                return callback(err);
            }
            // 讀取 blogs 集合
            db.collection('blogs', function (err, collection) {
                //处理
            });
        });
    };

User.get代码

User.get = function (username, callback) {
    db.open(function (err, db) {
        if (err) {
            return callback(err);
        }
        db.collection("users", function (err, collection) {
            //处理
        });
    });
}

这个有什么好的解决方法吗?

我想问一下关于代码最前面的这段是什么意思,能详细说说么……

;
(function (name, definition) {
  // this is considered "safe":
  var hasDefine = typeof define === 'function',
    // hasDefine = typeof define === 'function',
    hasExports = typeof module !== 'undefined' && module.exports;

  if (hasDefine) {
    // AMD Module or CMD Module
    define(definition);
  } else if (hasExports) {
    // Node.js Module
    module.exports = definition();
  } else {
    // Assign to common namespaces or simply the global object (window)
    this[name] = definition();
  }
})

比较想问下这个模块相对 async 有什么优势?async 还不需要自己去关注事件点,用起来更加省心~

@gvforjob after方法很实用, 各有所长吧, 我一般都是混着用的

之前我用的也是这个模块,但对个人来说有些不适,后来自己就开发了一套,部分内容比起 eventproxy 要好很多。

eventproxy

需要先创建,然后再 emit 触发,不是很灵活。从上面的例子可以看出来。

howdo

而 howdo 却不一样。 howdo.task().task().task() 链式创建有顺序的任务。 这些任务怎么执行?有2个 API ,一个是串行(follow),一个是并行(together)。 项目地址:https://www.npmjs.org/package/howdo 同时,howdo 也支持浏览器端的异步流程控制。

回到顶部