我的艳遇——给网易163免费邮宽衣解带(3)

  • 硬着头皮解读js

    话说从163 免费邮下载Js后,并无法立即对其进行阅读。打开一看,排版错乱、凡是小写方法都被重新取名,而且取名规则杂乱无章,明显是使用过js 混淆器的迹象。但所幸的是,据笔者了解,当前并没有真正所谓的对js加密技术存在,因为目前无法逾越的是一个最重要读者——浏览器,也就是这个读者对其解读,方能产生代码想要的效果,所以混淆器只能从排版(如去空格、换行),去注释,类、方法、变量等重命名(不规则命名)等方式让代码的可读性变差,加上本身的复杂业务逻辑,就能够将大部分读者拒之门外。但笔者确信只要能够解决排版问题,就能使得阅读变成可能,就能改得动。

下载到本地的js 主要有:engine.jsmainmodule.jsaddress.jsjsstylepublic.jsoption.jsothers.jsjs3_rai.jsclosea_d.js 。虽然js 文件众多,但是其中最为核心的就数前3个文件:engine.jsmainmodule.jsaddress.js ,就这3 个文件代码合计就有2万余行。顾名思义,engine.js163 的引擎js文件,即核心类库代码(如果你写过js ,相信知道大名鼎鼎的prototypejQuerydojojs 组件库,这里你可理解成与他们等同功效的js 封装库),mainmodule.js 和各个主要功能模块(如文件夹管理模块、写信模块、发送模块、欢迎模块等)密切相关,address.js 独立性较强,为通讯录模块的js

大致了解了他们的中心思想,就让笔者带着你来揭开这神秘面纱吧!

engine.js :163 免费邮的核心类库,其编码风格与prototype 如出一辙,相信熟悉prototype 的读者见到engine.js 会有种一见如故的感觉,也许你会奇怪我的说法,别急,为了验证这个说法,我们首先得解决第一个问题——这一6 千多行的代码从哪里读起呢?没错,寻找第一入口,这当然是从主页寻起。所以在index.jsp 里可以寻找得到以下字样:

if(!window.start){ 
alert('因为浏览器原因登陆系统遇到问题,请尝试以下方法:\n;清除浏览网页的缓存\n;清除浏览网页的Cookies\n; Ctrl+F5 强制刷新页面'); 
} 
else{ 
start(); 
}

 正如你所见,start() 正是第一入口函数,而该函数在engine.js 中,如下:  

function start() { 
try {
lIae(); 
Ajax.init(); 
GE.init(); 
} 
catch (e) { 
window.setTimeout(start, 1500); 
} 
}

 start() 方法中主要执行了lIae()Ajax.init()GE.init()3 个方法。我们首先从第一个方法lIae() 看起,如下:

function lIae() { 
l1u(); 
l0u(); 
OOt(); 
lIw(); 
ReAwt(); 
}

   而它又依次由l1u()l0u()OOt()lIw() 等方法组成。同样地,我们仍然关注第一个方法——l1u() (是的,如你奇怪的,这些都是拜js 混淆器所赐的以小写字符为首的方法名),终于我们来到了l1u() 的具体实现。没让你失望吧,那种在他乡遇见故人的感觉是不是油然而生?其中代码String.prototype.stripTagsString.prototype.escapeHTML 等不就是prototype 活生生的原代码吗?怎么?还是没这种感觉?晕,那就是你和prototype 原本就非故人,所以在阅读engine.js 之前不妨先去了解一下prototype 吧?它目前的最新版本是1.6 ,如果你对js 有兴趣,就更加不能随便错过她,会很有帮助的:)

仔细端详l1u() 其中的代码,你会发现这位新朋友身上虽然透着故人气息——对StringNumber Function 对象的扩展、FieldArray 等对象的定义、El 对象(也就是prototype 里的Element 对象)的定义、EV 对象(也就是prototype 里的Event 对象)的定义,但并非完全是你之前认识的prototype ,相对prototype 而言,她显得更加小巧,完全就是依163 所需定制而出的,或者坦白的说,是163 根据需要有选择地copy prototype 的源代码(这句话终于还是说出来了-_||| ),当然除了prototype 的代码外,它也包含了一些除prototype 以外的封装,如Email 对象,这个正是163 的主题——邮件,最后你还可以看得到enginejs 内置的window.onerrorwindow.onbeforeunload 进行了覆盖,window.onerror 总是返回真,使得浏览器上不可能会出现js 错误的提示,所以你现在应该明白了为什么在调试过程中IE 左下角总不会有黄色的错误的提示,这在产品上线后多少可以给用户更加好的体验,但在开发调试过程中还是注释为好,帮助调试。而window.onbeforeunload 方法确保了当用户在写信时,刷新或关闭页面总能给于提示——这将会导致写信页面上未保存的数据丢, 从这2 点就可以看出163 的良好客户体验了。在这里就不赘述,参见更为详细的prototype 文档,对照着看。所以我们对l1u() 的总结是:有选择性地copy prototype 中的方法,并定义了个性的对象如Email 等,组装成自己的核心调用封装库。这里简列一下他们之间的异同:

1.     都对String 对象进行扩展:但engine 的扩展方法显得少,而且有些方法只是命名和prototype 不同,功能完全一样,如trim() 对应prototype 的是strip

2.     都定义了Field 对象:但engine 只包含clearactivate 两个同名同效方法。

3.     engine prototype 中的ElementEvent 对象分别易名成ElEV ,同样地少了一些方法。

4.     都对Number 对象进行扩展:但他们的扩展方向完全不相及,即不存在同效的方法。

5.     都定义了Array 对象:engine 显得更加小巧。

6.     都对Function 对象进行扩展:engine 只扩展了bind 方法。

7.     engine 根据实际业务,定义了Email 对象:该对象正是163 的主题,它包含了ValuematchgetgetNamegetAddressformat 等方法。其中Value()serValue(name,address)match() 是检验某一输入串是否符合Email 格式,是则返回Email.Value 对象,否则返回false

至此,我们完成了lIae() 里的第一个调用方法l1u() 的解释,紧接着第2 个方法登场——l0u() 。该方法主要是定义了四个非常重要的全局对象——CM,GE,MM,HM 。如以下字样:

window.CM = new CoremailManager
window.GE = new GlobeEngine();
window.MM = new ModuleManager();
window.HM = new HistoryManager();

接着,该方法执行了以下语句,向 ModuleManager 对象添加各个 Module 对象,如下:

var aj = new gModuleInfo(); for (var o in aj) { eval("ModuleManager.prototype." + o + "="+ "new ModuleStatus('" + aj[o][0] + "', " + aj[o][1] + ", " + aj[o][2] + ", " + aj[o][3] + ", " + aj[o][4] + "," + aj[o][5] + ", '" + aj[o][6] + "', " + aj[o][7] + ", " + aj[o][8] + ", '" + aj[o][9] + "' , '" + aj[o][10] + "', '" + o + "')"); }
 
  
 
  

   咋看代码有混乱的感觉,但实际上执行了

eval( "ModuleManager.prototype. folderMain = new ModuleStatus("***") );
 
  

     或许会经常看见MM["folderMain"]的写法,就是因为执行以上那句初始化代码。另外,你会在 mainmodule.js 中看到有 FolderMain 对象的定义,所以, MM[ "folderMain" ] 非但具有 其本身 FolderMain 对象的属性,而且还具有 ModuleStatus 对象的属性。所以某种程度上说, MM[ "folderMain" ]=  ModuleStatus + FolderMain 。 163 开发人员用这种方法实现了继承(但实际上这种做法不好,是吧?)。另外,该方法还执行了用于初始化各个模块的方法,如下:

    将其中各个变量替换后便是分别执行 mainmodule.js 中的fFolderMainModuleInit() 、fFolderModuleInit() 、fReadModuleInit() 、fComposeModuleInit() 、fSendModuleInit() 、fWelcomeModuleInit() 等6 个方法了。如他们所取名字,他们分别是为各个模块做初始化工作,如对象创建、变量赋值、方法指定等。所以我们对l0u() 方法的总结是:创建了四个非常重要的4 个全局对象——CM( 邮件核心管理者 ),GE( 全局引擎 ),MM( 模块管理者 ),HM( 历史记录管理者 ) ,以及MM( 模块管理者 ) 对象中6 个模块属性对象的定义——FolderMain( 所有文件夹管理模块) 、Folder (单文件夹管理模块)、Read (邮件阅读模块)、Compose (邮件撰写模块)、Send( 邮件发送模块) 、Welcome (欢迎模块)。

      至此,我们完成了lIae() 里的第2 个调用方法l0u() 的解释,紧接着第3 个方法登场——OOt () 。该方法职责明确,就是又一全局对象CC(CommonControl 的缩写) 对象的创建。该对象的主要是通用工具类,如showMsg ——显示右上方的那个“数据正在加载,请稍后.. ”的提示。
      很快的,我们进入lIae() 方法的最后一个调用方法——lIw() 。是的,再次让你感到熟悉,这个方法里包含了和prototype 类似的Ajax 封装,如Ajax.request() 方法,其中还包含了Util 对象的声明,该对象主要包含了为Ajax 服务的方法和对象转换方法,如str2Date 就显得很有用,它可将形如“yyyy-MM-dd HH:mm:ss ”的字符串转换成Date 对象。
      至此,我们将lIae() 方法里的调用方法全部解释完毕。紧接着其后的便是Ajax.init() 方法了。顾名思义,Ajax.init() 主要是实现了Ajax 对象的初始化工作,如确认了Ajax 请求的url 前缀,注意:这并不和我们之前看到的Ajax 对象声明相冲突,他们分工很明确,先声明后初始化。
      再来就是GE.init() 方法了,也就是上面我们提到的最后一个方法了,我们看到了曙光。没错,他所做的也就是初始化之前声明的GE ( GlobeEngine )对象。这个初始化就是开始进入后台的那个门槛。浏览GE.init() 中的具体实现代码,可以找到“O1t() ”字样。她就是第一个向我们后台发起请求的Ajax 调用——调用Sequential.java 这个servlet 的post 方法,完成3 件事:第一,查找所有联系人;第二,获取个人签名;第三,获取所有联系组,然后将数据返回给界面js 处理,从而将联系人、个人签名、联系组这3 大数据缓存在数组里。

      跟进O1t() 方法里,可以看到“IIl() ”字样,该方法主要是从servdata.jsp 里读取一些相对固定的数据如各个文件夹信息、系统参数配置等,而这些数据并非是Ajax 发起的请求,那么他们从何而来?没错,正是当用户登录系统时,系统从数据库中查询而出,放置session 或cookie 里的,而servdata.jsp 再从session 或cookie 里获取数据,不信?不信你可以看看servdata.jsp 里会有明显的session 或cookie 取值,如此一来,用户登录后便可获取各个文件夹信息和系统配置参数等数据。继续在IIl () 方法里摸索,在方法最后可以看得见MM.createModule("welcome") 就是它使得你可以看见欢迎界面。想想吧,每次你打开163免费邮的瞬间,后台程序原来处理了这么一些操作。

      目前为止,我们介绍完毕start() 方法,它的功能就是完成一系列初始化工作,让用户登录完毕后,就可以看得见左边文件夹相关信息、系统配置参数(如用户名等)、欢迎界面等可见信息,另外还有一些不可见的缓存数据,如联系人、联系组、个人签名等通讯录相关数据。

  • 搭建前后台数据衔接桥梁

目前为止总算对其 js 有了初步的了解,但是前后台的数据交互的格式是怎么样的呢?这时我想起了我们可爱的 firebug@firefox ,不得不承认,它起到了功不可没的作用。它可以抓住 Ajax 与后台数据交互的喉结——出与入的数据格式皆一览无余(见附件图)。这样一来,我们便可以得知前台 js 应该怎么拼凑数据给服务器端,而服务器端该输出怎么样的数据格式给前台 js 端。所以想要让 163 js 开始动起来,只要你的服务器端输出数据按照其规定的格式,那么, 163 就等于搬到你本地运行了。不过 从茫茫代码中寻找各个操作的入口,首先找到其对应的函数,杂乱无章的命名规则在这个时候粉墨登场了,它使得函数定位存在困难,占用了不少开发时间。另外,研究得知 163 的数据输入格式为 xml 格式,解析较为复杂,尝试对其进行改造成 JSON (相对于 querystring xml ,我相信数据交互量大时,应该忘记 querystring xml )。 Action 层的代码是前台与后台的一个关键管道,我们在这里做了数据流的格式封装,规限了数据输入与输出的格式,并且尝试改造了 163 的输入流格式,经过严格测试改造成功,某种程度上说这个改造降低了数据获取的难度,比 163 本身更进一步。

  • 具体问题具体分析

我们的用户需求和 163 业务需求并非如出一辙,修改别人的代码以适应我们的实际需求,该过程相对复杂。在连阅读都存在困难的情况下就要尝试去修改难懂的代码以符合我们的实际用户需求,的确令人无从下手——命名杂乱无章、找不到入口、尝试读一段代码却不知所云(缺少业务背景以及业务逻辑本身相对复杂)等等,这些都是刚接触它的第一感觉,容易令人沮丧,丧失再读下去的动力。可以尝试归纳的是,修整代码 = 首先读懂 + 尝试猜测 + 尝试修改 + 再修改 + 再猜测 ,有时,它是以上步骤不断循环的过程,繁琐、反复、复杂会是这个过程会出现的字眼。目前存在一个比较大的隐陷, 163 后台数据库的设计与我们系统现有的数据库设计不尽相同,所以可能存在一些未知的缺陷。如今可以预想的是可能无法满足用户的自定义文件夹需求。

以上,看到这儿需要你很多耐心。最后,似乎可以这样下个假论,从成功拷贝 163 免费邮的结果看来,如今想要拷贝哪个知名的大量使用 JS/Ajax 网站都是有可能的,大家不妨尝试一下百度有啊、淘宝等等(另外值得一提的是,网易旗下的其他邮箱如 126 免费邮、 Yeah.net 几乎只是皮肤 css 的改变, js 改变并不大,所以只要把这 2 个网站的 css 替换一下,即可生出 2 个崭新的系统)

你可能感兴趣的:(Js)