从神奇的"$"函数开始
"$"函数将在文档加载完成之后为一个指定的button 绑定事件,这些代码在单个网页中工作正常。但是如果我们还有其它的网页,我们将不得不重复这个过程。
< script type ="text/javascript" >
// when dom ready, do something.
// bind click event to a button.
$( function (){
$( ' #sayHello ' ).click( function (){
alert( ' Hello world! ' );
});
});
</ script >
如果我们需要另一个行为的button怎么办?比如象这样:
< script type ="text/javascript" >
// when dom ready, do something.
// bind click event to a button.
$( function (){
$( ' #sayUnlike ' ).click( function (){
alert( ' I unlike it. ' );
});
});
</ script >
接下来,更多的问题出现了,我们需要很多这样的button, 这好象也不难。
< script type ="text/javascript" >
// Change to a class selector to match all the button elements.
$( function (){
$( ' .sayUnlike ' ).click( function (){
alert( ' I unlike it. ' );
});
});
</ script >
一个页面里面同种出现了两种button ......
< a href ="javascript:;" class ="sayUnlike" > Unlike it </ a >
< script type ="text/javascript" >
$( function (){
$( ' .sayHello ' ).click( function (){
alert( ' Hello world! ' );
});
$( ' .sayUnlike ' ).click( function (){
alert( ' I unlike it. ' );
});
});
</ script >
但是呢,不是所有的页面都会用到这两种的button,为了不在页面上使用额外的选择器,我们要作一些必要的调整,因为基于class的选择器的性能相对于id选择器开销很大,需要遍历所有dom元素,并使用正则表达式匹配class属性来选定满足条件的元素。
< script type = " text/javascript " >
$( function (){
$( ' .sayHello ' ) . click( function (){
alert( ' Hello world! ' );
});
});
</ script >
<? } ?>
<? if ( $page == ' B ' ){ ?>
< script type = " text/javascript " >
$( function (){
$( ' .sayUnlike ' ) . click( function (){
alert( ' I unlike it. ' );
});
});
</ script >
<? } ?>
我们的项目功能越来越复杂,经过一段时间以后,变成了这个样子, quick but dirty......
< script type = " text/javascript " >
......
</ script >
<? } ?>
<? if ( $page == " B " or $page == " E " and $page is not " X " ){ ?>
< script type = " text/javascript " >
.....
</ script >
<? } ?>
<? if ( $page == " B " or $page == " E " or $page == " C " ){ ?>
< script type = " text/javascript " >
.....
</ script >
<? } ?>
......
这真是太糟糕了,我们需要在一个页面上加载许多个代码片断才能绑定所有的事件,如果我们再将不同的代码分装入多个js文件中这将增加多个页面资源的http请求,不论是管理还是用户体验都将面临挑战,我们需要找到一个更佳的解决方案。
既然 class selector 的开销这么大,我们能不能在一次扫描中绑定所有的事件?我们可以尝试一下:
// Register global name space.
var Yottaa = Yottaa || {};
Yottaa.EventMonitor = function (){
this .listeners = {};
}
// Bind all event.
Yottaa.EventMonitor.prototype.subscribe = function (msg, callback){
var lst = this .listeners[msg];
if (lst) {
lst.push(callback);
} else {
this .listeners[msg] = [callback];
}
}
// Create the event monitor instance.
var event_monitor = new Yottaa.EventMonitor();
function load_event_monitor(root){
var re = / a_(\w+) / ; // using a regular expression to filter all event object.
var fns = {};
$( " .j " , root).each( function (i) {
var m = re.exec( this .className);
if (m) {
var f = fns[m[ 1 ]];
if ( ! f) { // 如果事件处理函数不存在则创建函数对象.
f = eval( " Yottaa.init_ " + m[ 1 ]);
fns[m[ 1 ]] = f; // 调用绑定函数.
}
f && f( this );
}
});
}
$( function (){
// when dom ready, bind all event.
load_event_monitor(document);
});
// Here is 2 sample components.
Yottaa.init_sayhello = function (obj){
$(obj).click( function (){
alert( ' Hello world! ' );
});
}
Yottaa.init_unlike = function (obj){
$(obj).click( function (){
alert( ' I unlike it. ' );
});
}
</ script >
我们的DOM元素这样写:
< a href ="javascript:;" class ="j a_sayhello" > Say Hello </ a >
< a href ="javascript:;" class ="j a_unlike" > Say Unlike </ a >
这样看起似乎好多了,我们只需要在页面加载的时候执行一次class selector(在上面的代码中就是所有'.j'的元素)就可以找到所有需要绑定事件的元素,具体绑定哪一个组件由 class 名称里面的 a_xxx 来决定,对应着 Yottaa.init_xxx,并将当前元素的引用作为参数传入事件逻辑中。
在这个处理模式下,我们不需要再次手动编写事件处理的逻辑并将它放到 $(function(){ .... }); 这样的初始化函数中,所有我们要做的事情仅仅是给组件的“容器”加上两个 class: "j a_XXX"程序即可帮我完成事件绑定工作,是不是很 cool ?象常用的展开/折叠效果,全选/反选效果, tab切换以致于一些其它的简单功能都可以使用这种方式。难道这就是传说中的银弹?不,事情没那么简单,我们应该看到这种处理方式一些弱点:
- 不能给组件传递初始化参数。
- 不能体现出组件的包含关系,也不能利用继承和多态等面向对象的特性使程序更容易编写和理解。
- 对于部分具体关联关系的组件在处理上略显麻烦,没有合理的事件通知机制。
我们来看看第一条:关于参数的传递,在许多场景下对于多个条目的列表,对应每一个条目我们一般会给元素分配一个唯一一的id,这些元素的行为类似,不同之处只是服务器端的编号不同,比如一个留言列表或者是一个产口列表。我们可以利用id属性为我们作一些事情,看下面的代码,我们用id属性把条目对应的服务器端编号告诉javascript,并在接下来的事件逻辑处理中作为服务器回调函数参数的一部分发回服务器端。
Yottaa.init_sampleajax = function (obj){
$(obj).click( function (){
var component_id = $( this ).attr( ' id ' ).split( ' - ' )[ 1 ];
$.get( ' /server/controller/method ' , {id: component_id}, function (data){
if (data){
alert( ' Message from server: ' + data );
}
});
});
}
</ script >
< a href ="javascript:;" class ='j a_sampleajax' id ='item-a' > Show server message. </ a >
< a href ="javascript:;" class ='j a_sampleajax' id ="item-b" > Another button with same action but different server side identifier. </ a >
在更复杂的一些场景中我们可以利用页面上的inline code给组件传递一些必要的信息。
User:{
familyName: "Jhone",
givenName: 'bruce'
},
Url:{
siteName: 'yottaa.com',
score: 98
}
}
Yottaa.componentMetaData = {
compoment_id_1:{ ...... },
component_id_2:{ ...... }
};
上面讨论了一种可能的代码组织办法,但是并非适用于所有的项目,我们要做的是:针对于目前的现状,找到一个在代价比较小的重构方案。我们考虑如下几点:
- 分离元素的事件绑定代码和组件代码:组件代码包括jquery库,相关扩展插件,以及我们自己编写的小部件,如chartbox等内容。
- 事件绑定及处理逻辑:按不同的组件划分为多个模块,每个模块放入一个function中。
- 页面需要指定哪些模块要在本页面上初始化,提供一个列表交由全局的事件绑定器统一处理。
下面来演示一下部分代码:
function init_loginPanel = function (){
var container = $( ' login_panel ' );
$( ' #login_button ' ).click( function (){
......
});
}
function init_chart = function (){
......
}
// global static init method
Yottaa.initComponents = function (components){
for ( var i = 0 ;i < components.length;i ++ ){
if ( typeof window[components[i]] == ' Function ' ){
window[components[i]]();
}
}
}
// above is in the 'all-in-one' assets file which is compressed to one file in production.
var components = [ ' init_loginPanel ' , ' init_chart ' ];
var metaData = {
loginPanel: {},
chart: {},
......
};
$( function (){
Yottaa.initComponents(components);
});
// here is inline script on the page.
</ script >