前言
一直想写点关于前端性能的东西,后来感觉所谓的性能优化最基本的前提是你要知道浏览器是如何针对web页面工作的.后来由于过年以及换工作等原因耽搁下来,只好利用这个休息的周末写一下.
本来打算好好研究一下关于前端性能优化的一些内容,看了一些发现有的地方不是很明了.所以觉得先把浏览器加载渲染这个过程摸清楚,本文是多方总结和我自己的一点研究,如果有不对的地方,还请指出,谢谢.
加载
首先我们要有一个概念,浏览器显示出一个页面,即使只拿前端抛开服务器和数据库,也是一个很复杂的过程.尽管它很复杂,但还是有一的步骤和流程的,了解这些步骤你才会清除的知道哪里耗费了过多的资源,从而进行人为的优化.
在一个页面上我们通常会看到种内容,HTML-CSS-Javascript.在一个文件中它们会通过各种方式引入,然后会被浏览器分别解析.当我们通过一个URL地址进入页面时,工作就开始了.首先浏览器将HTML解析为一个DOM-Tree,然后将CSS规则解析为一个CSS-Tree,最后则是Javascript的下载和执行.
在这个过程中,浏览器是按照一定顺序进行的,其中的任何一个步骤出现问题都可能导致页面无法显示.
解析
当完成了上面的解析过程后,浏览器引擎会根据这些解析的结果构建出Rendering-Tree,它会生成的规则将CSS样式应用到对应的element上,并且计算每个element的位置,这就是Reflow的过程.
在这一过程中,DOM-Tree 的生成相对容易,比较耗费性能的是CSS匹配HTML这一过程,所以对着CSS解析很多地方都有提出:DOM的结构要尽量简单,要小,不要过度嵌套,CSS尽量用class和id.
渲染
在没说之前我就要先告诉大家,渲染是一个很消耗性能的过程.这就意味着如果你要优化你的前端性能,可以在这一部分针对每一个动作进行检查和优化.
黄色部位就是渲染过程的几个动作,先进行样式的计算,然后够将Rendering-Tree,然后定位坐标以及大小等各种样式属性,最后画出来.上面有些线没有连贯是因为Javascript修改了DOM或者CSS导致必须重新Layout,又或者CSS规则没有匹配到.
如果你早先对前端性能有概念,那么Repaint和Reflow这两个词你一定不会陌生.
Repaint - 表示页面上一部分的内容要重新画,可能是颜色什么的,但是元素的大小和位置不需要改变,也就是不影响其相邻的内容.
Reflow - 表示页面上一部分内容的大小或者位置变化了,这会引起周围元素的连锁反应,导致整个Rendering-Tree重构.
通过描述我们可以看到Reflow的动作成本要比Repaint高得多,所以在平时的工作中我们要尽量避免浏览器的Reflow.对于CSS浏览器是可以并行下载的,但是要注意这其中可能会发生的Reflow.
JS的加载和执行
Javascript在整个浏览器渲染的进程中始终是个不安份的因素,更多的时候它充当着规则的破坏者这种角色.因为它可以操作DOM的特性,使得之前生成好的DOM-Tree很有可能发生改变.
浏览器对于Javascript奉行了两个原则,首先加载即执行,其次在其执行的期间阻塞其他内容(包括DOM的解析和其他资源的下载等).这是因为无法确定Javascript的执行会给DOM-Tree或者CSS带来怎样的变化,所以在JS执行的情况下,整个浏览器处于单核状态.看到这你就不难理解为什么很多地方都推荐将CSS写在页面最前面,而将Javascript写在页面最后的原因了.
IE从6开始支持一个defer属性,它加在<script>标签中,作用是使Javascript脚本并行下载,并且等到都下载完在顺序执行,但仅支持IE.
HTML5也不甘示弱,加入了类似的async属性,不过它坚定遵守Javascript加载后立即执行的原则,所以存在弊端,容易使有顺序的JS代码失控,并且不是所有版本的浏览器都支持,所以不常使用.
标准解决方案-动态DOM
为了解决Javascript脚本载入的问题,人们又找出通过创建script节点的方式.
function loadjs(script_filename) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', script_filename);
script.setAttribute('id', 'coolshell_script_id');
script_id = document.getElementById('coolshell_script_id');
if(script_id){
document.getElementsByTagName('head')[0].removeChild(script_id);
}
document.getElementsByTagName('head')[0].appendChild(script);
}
var script = 'a.js';
loadjs(script);
看一下代码,传递一个url参数,然后用创建element的方式创建一个script节点.这一过程就将脚本载入并且执行了.当然其实还有一种Ajax的方式,不过原理差不多,有兴趣的可以自行百度.
结尾
其实浏览器整个的处理比这写的要复杂的多,上面的过程只是简化了.了解整个浏览器的渲染进程那么我们对什么影响了网页的加载?这个问题应该有了一点认识,比较重要的一个Repaint和Reflow,另一个就是JS操作DOM.
一些建议:
- 如果能提前定义好一个class去赋值给DOM,就别一个个DOM节点的遍历然后逐个添加style了,在楼下大喊一句远比挨个单元去敲门省力.
- 创建的DOM结构尽可能简洁,层次少,另外不要再DOM节点中运算.
- table这种布局省事是省事,不过典型的牵一发而动全身,改一个地方其他都要变,所以尽量不要用.
- JS的性能浪费的不是在代码本身的执行,而是对DOM和CSS的操作.
受能力所限,写的可能有不对的地方还请谅解,欢迎大家留言讨论.
来源:oschina
链接:https://my.oschina.net/u/723632/blog/211703