昨天想了想,其实 Node 把 API 都设计成为事件,或许是不太恰当的。UI 用事件的概念去理解,自然十分好,但后台的逻辑,本身都是串行书写的,即一步一步的思维。如果使用事件,等于把同步的逻辑支离破碎。
回归主题,看看 N 年前的写的事件广播类。下面文字都是 N 年前写的。
Event 类,就是一个提供事件服务的类,写得简简单单,不求多元、繁复(明显没有比 Ext JS 都考虑得多,那是一种方向)。 好像但凡研究 JavaSscript 到一定阶段的人,都要搞清楚事件吧,嗯~此为必修课。事件的立论基础大家可以从观察者模式(Observable)得到许多灵感,当然就是必须有第三方的“中立”观察者, 一边提供订阅事件的接口,一边让组件触发事件的 fireEvent()。前言:不得不承认,有时候从新写一个库是一件很辛苦的事情。但是相比较之下,直接使用别人写好的软件来修改,难道这样痛苦的程度就会减少吗?
基于事件模式的设计 JS 的一大好处就是对 Function 天然的良性支持。这一点纵观眼下多少语言都未必能够赋予这种地位,有的即使赋予了,但率直来讲,也没有 JS 的那样的流通性的地位。试问一下,现今有多少主流语言是对 Function 这一类型主打的?若没有 JS 带来的影响,可能这一观念认识知道的人,不计发烧玩家、专家级人马,少之又少,或是又是其他具有“忽悠”价值的概念来代替这一朴质的概念……
事件本身与 Function 形影不离。好多 GUI 系统都以 Function 为编程的中心,因为制定 GUI 的过程往往就是制定 Function 的过程。事件类型一旦明确用哪一个好之后,开发人员所关心的便是这个事件到底应该执行些什么过程。若过程可以以一个变量或对象来表示,再给他起个名字,我们从某个角度上理解,也可以说该过程被抽象化了。抽象有什么好处?就是把一些必要的过程先写好,以后在复用。直截了当的说,我定义函数为一变量,可以全局变量,也可以私有变量,看您怎么用它——还可以当作参数传来传去。也是一下子这样说有点跨越性太大,不易接受。如果是真的,操作起来究竟有什么好处呢?首先我们得从了解函数其意义的本质上去入手理解。什么?函数是什么??这不是初中层次的内容?……哎呀,插一句,不是怕大家见笑哦,我就是看了什么好东西,一味东拿来一点西拿来一点拼装一起,还自诩高级,其实没有扎实的基础知识联系,准吃亏!哎~回头来还比不过老老实实的说明一下基础内容。——各位已深明大义的看官请掠过。
/**
* @class $$.Event
*/
$$.event = function(){
var events = {};
this.addEvents = function(){
for(var i = 0, j = arguments.length; i < j; i++){
events[arguments[i].toLowerCase()] = [];
}
}
/**
* 添加一个事件侦听器。
* @param {String} name
* @param {Function} fn
* @return {this}
*/
this.addListener = this.on = function(name, eventHandler) {
var eventQueen = events[name.toLowerCase()];
if(!eventQueen){
throw '没有该事件!请使用addEvent()增加事件';
}
eventQueen.push(eventHandler);
return this;
}
/**
* 触发事件。
* @param {String} name
* @param {Array} args
* @return {Boolean}
*/
this.fireEvent = function(name) {
var eventQueen = events[name.toLowerCase()]; // listeners
var args = eventQueen.length && Array.prototype.slice.call(arguments, 1);
var result;
var output = [];
for (var i = 0, j = eventQueen.length; i < j; i++) {
result = eventQueen[i].apply(this, args);
if(result === false){
break;
}else{
output.push(result);
}
}
return output;
}
/**
* 移除事件侦听器。须传入原来的函数句柄。
* @param {String} name
* @param {Function} fn
* @return {this}
*/
this.removeListener = function(name, fn) {
if (events[name]) {
Array_Remove(events[name], fn);
}
return this;
}
}
/**
* 删掉某个元素。
* @param {Array} arr
* @param {Mixed} el
* @return {Mixed}
*/
function Array_Remove(arr, el){
var index = -1;
for(var i = 0, j = arr.length; i < j; i++){
if(arr[i] == el){
index = i;
break;
}
}
arr.splice(index, 1);
return el;
}
记得还在一次技术沙龙作过分享,似乎受众们的反馈不错 呵呵,在那时的确有点那么一点点技术含量。现在,呵呵都跑去看 Promise/Aysnc.js 了。
第二次重写,sea.js 封装。
/**
* 观察者类,用于推送消息或者广播事件。addEventListener 方法为对象新增消息类型,fire 方法进行消息推送。
* 使用该类后,增加一个 msgs 属性,结构如下:
* msg = Object : [
* {
* name : String,
* bodies : Object [
* {
* scope : Mixed,
* fn : Function,
* args : Mixed []
* }
* ]
* }
* ]
*/
define(function(require, exports, module) {
var $$ = require('common/lang');
module.exports = $$.event = $$.define(Object, function (){
var msg = {
name : '',
bodies : [{
scope : null,
fn : null,
agrs : []
}], /* ... */
isCustomDomEvent : false
}
function observe(){
// I'm Event
this.event = true;
}
this.init = observe;
/**
* @param {String} msgName 消息名称
* @param {Function} msgHandler 消息行为
* @param {*} scope 函数作用域
* @param {*|Array} args 消息行为的前期参数
* @return {Boolean}
*/
this.addEventListener = this.on = function (msgName, msgHandler, scope, args) {
if (!this.msgs) {
this.msgs = [];// 全部消息之容器
}
check_msgBody_Queen(this.msgs);
var msgObj;
var hasToken = false;
for (var i = 0, j = this.msgs.length; i < j; i++) {
msgObj = this.msgs[i];
if (msgObj.name == msgName) {
hasToken = true;
break; // 找到已存在的消息队列
}
}
if (hasToken == false) {
// 消息结构体 @todo remove create()
msgObj = Object.create(msg); // 新建消息对象
msgObj.name = msgName;
msgObj.bodies = []; // you must new it!
if (msgName)
msgObj.isCustomDomEvent = this.customDomEventNames
? (indexOf(msgName, this.customDomEventNames) != -1)
: false;
}
check_msgBody_Queen(msgObj.bodies);
if (!(typeof args instanceof Array)) { // 前期输入的参数
if (args == undefined) {
args = [];
} else {
args = [args];
}
}
msgObj.bodies.push({ // 压入消息队列
fn: msgHandler,
scope: scope || this, // 保存函数作用域
args: args
});
if (hasToken == false) {
this.msgs.push(msgObj);
}
return true;
}
/**
* 检查消息体队列其类型
* @param {Array} arr
*/
function check_msgBody_Queen(arr) {
if (!arr || !arr instanceof Array) {
throw new TypeError();
}
}
function indexOf(item, arr) {
for (var i = 0, j = arr.length; i < j; i++) {
if (item == arr[i]) {
return i;
}
}
return -1;
}
/**
* 根据 msgName,查找指定的消息并讲其分发。
* @param {String} msgName
* @param {Array/Mixed} args 可选
* @param {Mixed} scope 可选
*/
this.fire = function (msgName, args, scope) {
var foundMsgObj = false;
for (var msgObj, i = 0, j = this.msgs.length; i < j; i++) {
msgObj = this.msgs[i];
if (msgObj.name == msgName) {
foundMsgObj = true;
break;
}
}
if (foundMsgObj === false) {
debugger;
throw '没有此消息类型' + msgName;
}
var _fn, _body, _args;
for (var body, i = 0, j = msgObj.bodies.length; i < j; i++) {
body = msgObj.bodies[i];
_fn = body.fn;
_scope = scope || body.scope;
if(args == undefined){
_args = body.args;
}else{
// args 应该在数组前面,否则数组顺序会颠倒
if (args instanceof Array){
_args = args.concat(body.args);
}else{
_args = [args].concat(body.args);
}
}
_fn.apply(_scope, _args);
}
}
// event-->msg
this.customDomEvent = function (el, eventType, msgName) {
if (!this.customDomEventNames) {
this.customDomEventNames = [];
}
this.customDomEventNames.push(msgName);
el.addEventListener(eventType, (function (e) {
e = e || window.event;
this.instance.fire(this.msgName, e, this.instance);
}).bind({
instance: this,
msgName: msgName
}));
}
});
/**
* 占位用的空函数。
* @return {[type]} [description]
*/
module.exports.emptyHandler = function(){}
});