由网页假死现象查找到的资料

一、现象说明

前段时间,业务组同事,发现了一个现象;前台页面查询记录分页(500条记录时),(每条记录的属性又非常的多,达50个左右)页面一直卡在那里,等了将近5分钟左右;100条时虽然有些慢,但也可以在1-2分钟响应过来。第一反应,难道是这帮家伙写的sql后台。。。。又一想也不太可能啊,毕竟没有复杂的业务逻辑,并且数据量也不大。不要瞎猜了,chrome network,timing ;发现后端数据响应很快返回了,那就是页面处理的问题了。好吧,有人说是easyui处理的问题,那我们使用的是easyui,datagrid; 这个用法有这个问题么,上网搜索一下。

二、EasyUI问题

http://www.easyui.info/archives/1435.html------------jQuery EasyUI Datagrid性能优化专题
http://www.2cto.com/kf/201312/262455.html--------大数据加载效率慢,优化解决方法

三、JavaScript引擎和渲染引擎---http://cube.qq.com/?p=16



浏览器是如何把HTML/CSS结合起来的内容呈现到窗口上呢, 不同浏览器略微会有些不同。但基本上都是类似的,见下图:

JavaScript引擎和渲染引擎
JavaScript引擎和渲染引擎


浏览器把获取到的html代码解析成1个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象( tag)。dom树就是我们用firebug或者IE Developer Toolbar等工具看到的html结构,里面包含了所有的html tag,包括display:none隐藏,还有用JS动态添加的元素等。
浏览器把所有样式(主要包括css和浏览器的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而firefox会去掉_开头的样式。
dom tree和样式结构体结合后构建呈现树(render tree),render tree有点类似于dom tree,但其实区别有很大,render tree能识别样式,render tree中每个node都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到render tree中。注意 visibility:hidden隐藏的元素还是会包含到render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。根据css2的标准,render tree中的每个节点都称为box(Box dimensions),box所有属性:width,height,margin,padding,left,top,border等。
一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。
如果把这个过程比喻成盖楼的话,回流(reflow)就好比是拆掉重来,而重绘(repaint)就相当于在不破坏结构的情况下对外墙重新粉刷。因此,回流必然会重绘,而重绘不一定会回流。

回流的花销也不小,如果每句JS操作都去回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会把flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些style信息的时候,就会让浏览器flush队列,比如:
1. offsetTop, offsetLeft, offsetWidth, offsetHeight
2. scrollTop/Left/Width/Height
3. clientTop/Left/Width/Height
4. width,height
5. 请求了getComputedStyle(), 或者 ie的 currentStyle

当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。
减少回流、重绘其实就是需要减少对render tree的操作,并减少对一些style信息的请求,尽量利用好浏览器的优化策略。

html在浏览器中会被转化为DOM树,DOM树的每一个节点都会转化为RenderObject, 多个RenderObject可能又会对应一个或多个RenderLayer。浏览器渲染的流程如下:

  1. 获取 DOM 并将其分割为多个层(RenderLayer)
  2. 将每个层栅格化,并独立的绘制进位图中
  3. 将这些位图作为纹理上传至 GPU
  4. 复合多个层来生成最终的屏幕图像(终极layer)。
  5. 将这些位图作为纹理上传至 GPU
  6. 复合多个层来生成最终的屏幕图像(终极layer)。

四、HTML页面加载和解析流程

1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
2.浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
3.浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
4.浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;
5.浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
6.服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
7.浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;
8.Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码;
9.终于等到了</html>的到来,浏览器泪流满面……
10.等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
11.浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

五、JavaScript加载执行顺序

一、在HTML中嵌入Javasript的方法
直接在Javascript代码放在标记对<script>和</script>之间
由<script />标记的src属性制定外部的js文件
放在事件处理程序中,比如:<p οnclick=”alert(‘我是由onclick事件执行的Javascript’)”>点击我</p>
作为URL的主体,这个URL使用特殊的Javascript:协议,比如:<a href=”javascript:alert(‘我是由javascript:协议执行的javascript’)”>点击我</a>
利用javascript本身的document.write()方法写入新的javascript代码
利用Ajax异步获取javascript代码,然后执行
第3种和第4种方法写入的Javascript需要触发才能执行,所以除非特别设置,否则页面加载时不会执行。
二、Javascript在页面的执行顺序
页面上的Javascript代码是HTML文档的一部分,所以Javascript在页面装载时执行的顺序就是其引入标记<script />的出现顺序, <script />标记里面的或者通过src引入的外部JS,都是按照其语句出现的顺序执行,而且执行过程是文档装载的一部分。
每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。
变量的调用,必须是前面已经声明,否则获取的变量值是undefined。
1 <script type="text/javascript">//<![CDATA[
2    alert(tmp);  //输出 undefined
3    var tmp = '111';
4    alert(tmp);  //输出 111
5    //]]>
6 </script>
同一段脚本,函数定义可以出现在函数调用的后面,但是如果是分别在两段代码,且函数调用在第一段代码中,则会报函数未定义错误。
1 <script type="text/javascript">//<![CDATA[
2    test();    //浏览器报错
3    //]]>
4 </script>
5 <script type="text/javascript">//<![CDATA[
6    test();    //输出 fun!
7    function test(){alert('fun!');}
8    //]]>
9 </script>
document.write()会把输出写入到脚本文档所在的位置,浏览器解析完documemt.write()所在文档内容后,继续解析document.write()输出的内容,然后在继续解析HTML文档。
01 <script type="text/javascript">//<![CDATA[
02    document.write('<script type="text/javascript" src="test.js"><\/script>');
03    document.write('<script type="text/javascript">');
04    document.write('alert("222");')
05    document.write('alert("变量保存值" + tmpStr);');
06    document.write('<\/script>');
07    //]]>
08 </script>
09 <script type="text/javascript">//<![CDATA[
10    alert("333");
11    //]]>
12 </script>
test.js的内容是:

1 var tmpStr = '111';
2    alert(tmpStr);
在Firefox和Opera中的弹出值的顺序是:111、222、变量保存值111、333
在IE中弹出值的顺序是:222、111、333, 同时浏览器报错:tmpStr未定义
原因可能是IE在document.write时,并未等待加载SRC中的Javascript代码完毕后,才执行下一行,所以导致222先弹出,并且执行到document.write(‘document.write(“变量保存值” + tmpStr)’)调用tmpStr时,tmpStr并未定义,从而报错。

要解决这个问题,可以利用HTML解析时解析完一个HTML标签,再执行下一个的原理,把代码拆分来实现(上面第一段js分拆成两段):

01 <script type="text/javascript">//<![CDATA[
02    document.write('<script type="text/javascript" src="test.js"><\/script>');
03    //]]>
04 </script>
05 <script type="text/javascript">//<![CDATA[
06    document.write('<script type="text/javascript">');
07    document.write('alert("222");')
08    document.write('alert("变量保存值" + tmpStr);');
09    document.write('<\/script>');
10    //]]>
11 </script>
12 <script type="text/javascript">//<![CDATA[
13    alert('333');
14    //]]>
15 </script>
这样IE下和其他浏览器输出值的顺序一致了:111、222、变量保存值111、333。

三、如何改变Javascript在页面的执行顺序

利用window.onload
1 <script type="text/javascript">//<![CDATA[
2 window.onload = func1;
3 function func1(){alert('111');}
4 alert('222');
5 //]]>
6 </script>
输出值顺序是 222、111。


需要注意的是,如果存在多个winodws.onload的话,只有最后一个生效,解决的办法是:

1 window.onload = function(){f();f1();f2();.....}
利用2级DOM事件类型

1 if(document.addEventListener){
2 window.addEventListener('load',f,false);
3 window.addEventListener('load',f1,false);
4 ...
5 }else{
6 window.attachEvent('onload',f);
7 window.attachEvent('onload',f1);
8 ...
9 }
IE中可以利用defer,defer作用是把代码加载下来,并不立即执行,等文档装载完毕之后再执行,有点类似window.onload,但是没有window.onload那样的局限性,可以重复使用,但是只在IE中有效,所以上面的例子可以修改成为
01 <script type="text/javascript">//<![CDATA[
02 document.write('<script type="text/javascript" src="test.js"><\/script>');
03 document.write('<script type="text/javascript" defer="defer">');
04 document.write('alert("222");')
05 document.write('alert("变量保存值" + tmpStr);');
06 document.write('<\/script>');
07 //]]>
08 </script>
09 <script type="text/javascript">//<![CDATA[
10 alert("333");
11 //]]>
12 </script>
这样IE就不报错了,输出值的顺序变成:111、333、222、变量保存值111

当HTML解析器遇到一个脚本,它必须按常规终止对文档的解析并等待脚本执行。为了解决这个问题HTML4标准定义了defer。通过defer来提示浏览器可以继续解析HTML文档,并延迟执行脚本。这种延迟在脚本从外部文件载入时非常有用,让浏览器不必等待外部文件全部载入之后才继续执行,能有效的提高性能。IE是目前唯一支持defer属性的浏览器,但IE并没有正确的实现了defer属性,因为延迟的脚本总是被延迟,直到文档结束,而不是只延迟到下一个非延迟的脚本。这意味着,IE中延迟的脚本的执行顺序相当混乱,并且不能定义任何后面非延迟脚本并须的函数和变量。在IE中所有的defer的脚本执行时间应该都是HTML文档树建立以后,window.onload之前。

利用Ajax。
因为xmlhttpRequest能判断外部文档加载的状态,所以能够改变代码的加载顺序。
4、一些注意事项:

以上所讲的deffer看似用起来没什么问题,但是实际上无法兼容Mozilla。所以通常我们还是要借助window.onload来实现。但是,之后就没有问题了么?假设页面dom里有一张图片,像这样:

1 <img src="picture.jpg" >
而这张图片又非常之大(例如30MB,这个数字虽然有点极端,但真的在有些网站出现过!),那么在dom加载完毕之前,js是无法执行的。问题就在于假设dom提供了用户交互的功能。例如按钮,输入表单等,这个时候他们已经是被呈现了的,因此就很有可能产生无效的用户行为。

我们不能指望用户像我们预期的那样等待页面显示加载完毕后再发生动作,而要把用户考虑成随时随刻会到处乱点的朋友。

这个问题又如何解决呢?既然我们需要页面结构输出后执行js,我们不妨把js入口函数定义在页面最下面好了。

1 <head>
2  <script src="x.js" type="text/javascript"></script>
3 </head>
4 <body>
5   ......
6  <img src="picture.jpg" >
7  <script type="text/javascript">init();</script>
8 </body>
这样就达到我们的目的了,页面结构输出完毕后就执行js,不用考虑图片的加载。

但是在文档末尾嵌入一条js脚本,毕竟容易被忽略,把关键的程序入口放在这种渺小的角落,总觉得不太合适。那有什么预留退路的方法没有呢?

我们可以把结尾的脚本稍微修改一下:

1 <head>
2  <script src="x.js" type="text/javascript"></script>
3 </head>
4 <body>
5   ......
6  <img src="picture.jpg" >
7 <script type="text/javascript">window.onload();</script>
8 </body>
而在js里预先把入口定义给onload事件:


1 window.onload = function() {
2 alert("load over");
3 }
这时候页面结构加载完毕后就会调用onload函数,而即使漏写了dom里的onload入口,js自身里的onload定义也会在页面加载完毕后执行,这样退路就留出来了。

不过这时候有个问题,onload事件会执行两次,可以在js的onload实现里解决这个问题,改成这样:

1 var flag = false;
2 window.onload = function() {
3  if (flag) {return;}
4   flag= true;
5   alert("load over");
6 }
这样似乎已经解决我们所有的问题了,不过仍然有些小遗憾,因为最后一行代码,致使行为与结构没有分离开来,要 unobtrusive 就要 unobtrusive 的彻底,为了达到完美的分离,还有很大的讨论空间。

而对于js文件内部的onload事件,我们还可以参考 Simon Willison 的addLoadEvent函数来优化:

01 function addLoadEvent(func) {
02  var oldonload = window.onload;
03  if (typeof window.onload != 'function') {
04     window.onload = func;
05   } else {
06     window.onload = function() {
07      if (oldonload) {
08         oldonload();
09       }
10       func();
11     }
12   }
13 }
然后,我们就可以在js里肆无忌惮地不停地将各个不同的函数添加到onload事件响应中了:

1 addLoadEvent(funcA);
2 addLoadEvent(funcB);
3 addLoadEvent(funcC);
当然,同一个js里设置多个onload响应函数其实没什么必要,我们完全可以把funcA、funcB、funcC封装在一个函数里add,addLoadEvent函数,更理想的状态是为页面动态调用的多个js文件添加入口。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值