问题的提出
一年前刚开始做项目的时候,我对于前台技术还刚刚入门,用jquery写点小打小闹的交互效果还是没太大问题,但是要把所有这些效果干净,整洁的部署到全站,对我来说就有点头疼了.由于网站页面虽说不是特别多,但也有二三十个,如果每个页面写一个单独的js文件或者直接把js写在页面内,是不利于维护和有损前台性能的.按照yahoo的前台性能提升建议,应该尽量减少js文件个数.于是我决定想办法把除了jquery库及一些插件之外自写代码全部写到一个文件之中.虽然可能有时候某些页面载入的js会有用不到的代码,但是这样做对于一个中等规模的网站来说有如下的性能优势:
- js文件数量很少,浏览器对服务器的请求次数会大大减少,节约了服务器的资源;
- 由于几乎每个页面要载入的js文件都相同,这样在首页载入后,再访问其他页面,浏览器可以直接从缓存中读取全部js,浏览速度大大提升
- 如果这个文件组织的比较有条理,很方便以后的维护.
这样的部署,可以谓之'轻便'.现在的问题就是,如何组织这个文件,让其尽量有条理. 当然当时的我还是菜鸟,写js的程度还在只会使用jquery的阶段,对于扩展jquery是一无所知,更不用说js闭包,用js模拟其他语言的OOP使用,以及设计模式这些比较专业的技巧了(其实现在对这些技术还在学习当中). 我不可能凭空想出一个好的办法来,所以就开始参考其他性能比较出色的web2.0站的js代码. 国外网站(如digg,netvibes)的js都用packer之类的东西加了密,人读起来很困难,于是又转向国内,看了看校内等,js文件好多,毕竟是大型网站不适合.后来找到豆瓣,发现性能性能相当出色,而且当时的js也没加密,只有三个文件jquery.js,suggest.js以及douban.js.其中最后一个文件是值得参考的.
豆瓣代码研究
当时读豆瓣的代码也费了不少劲. 我发现在豆瓣的html中有些元素的类(class)名称很特别,比如(注意class属性的值)
<input type="text" name="search_text" class="j a_search_text greyinput" autocomplete="off"/>
而且这些元素一般都是有js来增强交互效果的,比如表单元素,推荐按钮等;在douban.js中也存在很多与上述一些元素类名称命名一致的变量,比如
Douban.init_search_text = function(o){
//这里是相关代码
}
于是我就想,豆瓣应该是这样想的:把js当CSS来用,当赋予某些元素特定的类名称时,该元素就会拥有相应的js特性,比如上面说的input元素拥有类名称a_search_text,于是就拥有了Douban.init_search_text这个变量(或者说函数)赋予的js特性.这是怎么实现的呢?为了方便说明,把douban.js简化如下,省略了细致末节,大家可以大致看出主干:
//------第一段------
var Douban = new Object();
Douban.EventMonitor = function(){
this.listeners = new Object();
}
Douban.EventMonitor.prototype.broadcast=function(widgetObj, msg, data){/*代码省略了*/};
Douban.EventMonitor.prototype.subscribe=function(msg, callback){/*代码省略了*/};
Douban.EventMonitor.prototype.unsubscribe=function(msg, callback){/*代码省略了*/};
//------第二段------
var event_monitor = new Douban.EventMonitor();
function load_event_monitor(root) {
var re = /a_(\w+)/;
var fns = {};
$(".j", root).each(function(i) {
var m = re.exec(this.className);
if (m) {
var f = fns[m[1]];
if (!f) {
f = eval("Douban.init_"+m[1]);
fns[m[1]] = f;
}
f && f(this);
}
});
}
$(function() {
load_event_monitor(document);
});
//------第三段------
Douban.init_evc = function(o) {/*...*/};
Douban.init_enb = function(o) {/*...*/};
Douban.init_folder_n = function(o){/*...*/};
Douban.init_unfolder = function(o){/*...*/};
...
可以把代码分为三段:
- 第一段,建立了一个叫做Douban的对象,为其添加了EventMonitor的属性. 这个Douban元素很重要,后来知道这样做的好处是这个Douban元素能作为命名空间,让自定的变量不会'污染'全局命名空间(就是window对象),最近才知道,这样的设计模式叫做singleton. 至于EventMoniter属性其实是一个构造函数,用来创建的对象能在页面范围内用observer的设计模式实现对事件的订阅(subscribe),发布(publish).不过在这一版本的douban.js文件中好像很少有这个东西的使用.
- 第二段很精彩,正是load_event_monitor函数实现了'把js当CSS来用'.具体实现是这样的:当一个html元素同时拥有名称为j和以a_开头的类时,在DOM之后js就执行相应的Douban对象的以init_开头的属性.比如:当input元素拥有class=j a_search_text属性时,js就会eval相应的Douban.init_search_text().在load_event_monitor内部,用re正则表达式去匹配出要找的元素属性,然后用eval来执行相应的代码;这个函数唯一的参数root就是待加入js特性的元素. 在DOM完成载入后,douban.js把document作为load_event_monitor的参数运行,让整个文档所有待加入js特性元素初始化.
- 第三段就是初始化相应元素的代码,也就是日后需要维护的地方.这里的代码比较整洁,都是以Douban.init_开头,看到变量名称,其功能就一目了然.
山寨版的代码以及小技巧
读懂豆瓣的代码后,就开始部署自己的山寨版代码,实施方法大致差不多(只不过把Douban对象换成了XM),就不再赘述,文章结尾有demo网址.这里介绍用这种方法的几种技巧.
- 可复用的confirm框.
XM.init_confirm = function(o) {
if(!o.name){
$(o).click(function(){
var text = o.title || $(o).text();
return confirm("确定要"+text+"?");
});
}
使用示例
<a href="/delete" title="删除这个帖子" class="j x_init_confirm">删除</a>
点击这个链接后会弹出确认框,提示"确定要删除这个帖子吗?",点击确定将前往删除页面,点"取消"可以取消操作
- 整理变量. 原理很简单,在XM对象下再建立命名空间来分类相应变量,比如
XM.URL = {
user_avatar: '/Content/i/ud.gif',
group_avatar:'/Content/i/gd.gif',
topic_thumb: '/Content/i/tnd.gif'
};
其他技巧会在本系列的后续文章中穿插提到. 大家有什么疑问和建议,欢迎评论,希望共同提高!
Demo地址(也就是项目地址):鲜芒 (欢迎拍砖)
douban.js在这里: 下载