前言
进入公司6个月后被安排到该项目中,据说该项目规模很大,而拆分到公司的就只是二十来个页面,而我就负责其中的3个页面和其他页面的脚本代码,后来负责项目的性能优化工作。至于业务逻辑方面确实没什么可说的,就是CRUD。由一个很好沟通的前辈和我们6个新人一起来搞。这里有两项内容很值得总结,它们都让我技术上增进不少。
目录
2.一键关闭web系统所有页面,不断尝试,却被奇怪的方法Kill了。
3.总结
状况:页面在IE(6,7,8)中加载时间为2分钟左右。没错,你没看错,这个时间忘不了,我手按秒表、写javascript代码和使用HttpWatcher分别测试了N次了。
分析原因:
1.页面体积大,足足7M+,小电影啊!!
2.页面控件数庞大。该页面有一张表格用来显示记录,这张表有19或24列,每列有1至2个控件,客户要求以每页200条记录进行分页(打死他也不肯少),悲催了,页面只算该表含有的控件数就超过8000个。
插曲:分析工作也弄了一个多星期,主要工作是了解请求/响应的整个过程和统计过程中各部分所花费的时间,找出时间消耗大户进行重点打击报复!!我了解到请求/响应过程如下:
(下面只是首次请求的过程)
1.客户端浏览器发送域名到DNS,DNS根据域名找到IP再发送回客户端浏览器;
2.浏览器根据IP向Web服务器发出请求(是Get方式,所以只有请求头),开始踏上不归路;
3.服务器接收到请求进行一连串处理(详细请参考:asp.net管道模型(管线模型)之一发不可收拾和Asp.net页面生命周期)
4.服务器返回响应(响应头、响应体)给浏览器;
5.浏览器边接收响应边将HTML代码解释构建成Dom树,遇到css、js等解释性语言就进行解释,如果是样式文件、脚本文件或图片链接就向服务器发送请求。其中请求和解析js时会阻塞Dom树的构建(后来知道设置属性defer="defer",就不会阻塞了),后面的页面内容无法显示,而css就不会。
最终发现问题出现在浏览器加载、解释、渲染、呈现上。不能不说句IE真的很慢……
优化前的做法:表格是手工创建的,里面全是各种web服务器控件。最后一列是含修改等按钮的操作列,每点一次就请求一次服务器然后该记录所在行变为可编辑状态。
下面说说我的优化方法吧!这里学到一个原则:让用户尽快看到页面的变化而不是一片空白!
1.首先将css文件引用放在head标签中,js文件引用放在页面代码的末尾;
2.分别合并css文件引用和js文件引用的请求(具体方法请参考:网页优化系列一:合并文件请求(asp.net版));
3.压缩css文件和js文件,主要就是去空白行、缩写变量名;(注意:这里要分发布版和开发版,因为压缩后的css和js文件真的是无法维护的)
现在优化效果不大,没办法控件多、页面体积大嘛!继续优化吧!
4.关闭表格中服务器控件的ViewState(大部分控件用于显示,每次回传都重新生成一次,启用ViewState太多余了),关闭后大大减小了页面体积;
5.压缩该页面的ViewState并后置。压缩ViewState进一步减小页面体积;因为ViewState默认是放在靠近<form>标签的地方,而ViewState对于浏览器来说是一堆放在隐藏控件的无用字符串,但浏览器同样要花力气去加载和解释它,将ViewState后置就可以尽快让浏览器加载解释可视化元素,但后置的前提是ViewState不大,否则页面貌似呈现完成,而因ViewState过大而实际仍然加载解释,此时用户点击某个服务器控件就悲催了。(具体方法请参考:网页优化系列三:使用压缩后置viewstate)
要知道ViewState是往返于B/S间的,能小则小啊!
到这一步页面体积已经减小了很多,页面加载时间降低到1分多钟了^_^!!但优化的步伐是不能就此停止的!!
6.模仿微博弄滑动分页。客户铁定每页200条记录,那我默默地变吧!!表格的显示区域最多能显示40条记录,于是以50条记录为一组进行滑动分页(为什么是以40条为一组呢?起码要弄条滚动条出来蒙一下小日本嘛^_^!!)。用Ajax异步请求服务端,服务端生成<tr>……</tr>这样的html标签加数据传递过来,然后加入到表格中。注意:Table标签除了TD的innerHTML属性可写可读外,其他标签的innerHTML属性为只读,因此我在前端用了一个全局变量保存已加载的记录,然后跟新的记录合并后重新生成表格,显示时感觉会有点突兀。现在想起来其实可以把只传递判断使用什么html标签的标识符和具体的内容数据,然后用js生成表格的结构,而因为这个操作的js文件比较大就可以在前一个页面进行预加载,当进入该页面时就可以直接读cache了。(具体方法请参考:实现滑动分页(微博分页方式))
7.异步修改、删除记录。点击每行的修改按钮时弹出一个div,异步取数据,修改完后发送异步请求保存数据并用js修改该行的新值;点击删除按钮时,异步发送请求给服务器删除记录,然后用js修改当前行的所有td为空白并在行内首个td中标明“该行已删除(もう削除しました!)”,操作列中的控件清空。
好了,现在页面剩下3000多个html标签,体积为1M左右,加载时间为5秒左右。客户基本满意,那这部分就算是交差了,(*^__^*) 嘻嘻……
2.一键关闭web系统所有页面,不断尝试,却被奇怪的方法Kill了
需求:在OA系统首页有一个“关闭系统”按钮可以关闭该系统的所有页面。
1.首次尝试:
这时我想到了树结构。
思路:每个页面作为一个节点,并保存其子节点,点击首页的“关闭系统”按钮时就层层遍历,首先是最底层的页面被关闭最后到首页被关闭。
问题:但操作过程中关闭了中间某个页面,点击首页的“关闭系统”按钮时由被关闭的页面打开的页面就无法被关闭。
2.二次尝试:
思路:将所有子、孙页面均保存到首页上。
实现:
首页部分:定义一个数组对象用于保存子、孙页面的window对象;使用var win = window.open()打开子页面,将win对象加入到子、孙数组中。
子页面部分:定义一个var parent = window.opener全局对象,然后将由该页面打开的子页面的window对象加入到parent.子、孙数组中。
孙页面部分:定义一个var parent = window.opener.parent对象,同上;
就是这样每个子、孙页面都有一个引用首页window的变量,从而操作首页的子、孙数组。
问题:到孙页面那一层就出现大概是运行时不知名错误的问题,找了很久都不知道什么原因,过阵子有空再研究一下吧!
插曲:系统中有个页面是模态窗口——var smd = window.showModalDialog(),这个smd不是指向模态窗口的对象而是它的返回值,所以无法通过引用对象.close()来关闭(他杀),这时想到用setTimeout来定时检查模态窗口的父页面是否还在,如果不在模态窗口就自杀去吧,问题解决咯!!
3.奇怪的方法:
这方法是日方客户从网上搜寻出来并规定我们使用的,为什么说它奇怪,看下去就知道了!
思路:打开的子页面均有名字,关闭时先以这些名字打开窗口并获取打开窗口的引用对象(var win=window.open("name","_blank","url")),因同名窗口只能存在一个,所以之前打开的同名子窗口将被覆盖。然后使用打开窗口的引用对象.close(),删除所有窗口。
优点:真的实现了该功能;
缺点:1.能打开的子窗口数有限。名字要规定好,该项目就规定了5个,也就是说最多只能打开5个子窗口。
2.关闭系统时会先出现空白页面然后它又自动关闭。如果要关闭的窗口多那也挺突兀的。
第一节中主要是客户端方面的优化,服务端其实还有可优化的地方,因为测试了一下发现服务端也用了2秒多,其中查数据就用了1秒多一点。第二节的第二次尝试失败后因客户要求使用他们提供的方法就没再深入研究了,这点要多多改进才行,反正学到的是自己的,多学总有好处。
继续努力从IT小小鸟向IT小鸟迈进!!