浏览器渲染页面的机制和原理
现在操作系统比如Mac OS X,UNIX, Linux, Windows等,都是支持“多任务”的操作系统。
-
单核CPU执行多任务:操作系统轮流让各个任务交替执行,由于CPU执行速度很快,所以我们感觉就像所有任务都在同时执行一样。
-
多核CPU执行多任务:真正的并行执行多任务只能在多核CPU上实现,但是由于任务数量远远多于多核CPU的核心数量,所以操作系统还是会自动把很多任务轮流调度到每个核心上执行。
有些进程可能不止同时干一件事,在干多件事的情况下,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程。
多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替进行,看起来就像同时执行一样。
DOM的回流与重绘
-
重绘:元素样式的改变(宽高,大小,位置不变)
-
回流:元素大小或位置发生变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
ps:因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流。
- 回流一定会触发重绘,重绘不一定会触发回流。
优化DOM交互
- 使用文档碎片减少DOM交互次数。DOM交互越多,性能越慢。
var list = document.getElementById("myList"),
item,
i;
for (i = 0; i <= 10; i++) {
item.document.createElement("li");
list.appendChild(item);
item.appendChild(document.createTextNode(" Item" + i));
}
上面代码每执行一次for循环都会向DOM插入新的元素,一旦for循环次数很多,那么严重影响代码性能,解决办法就是减少DOM交互。
可以使用createDocumentFragment方法创建虚拟节点,把要插入DOM的元素先插入该虚拟节点,循环完之后再把虚拟节点插入DOM,虚拟节点是不会渲染出来的,只会渲染它的子节点。改进代码如下:
var list = document.getElementById("myList");
fragment = document.createDocumentFragment(),
i;
for (i = 0; i < 10; i++) {
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item" + i));
}
list.appendChild(fragment);
当我们要批量修改DOM节点的时候,可以将DOM节点隐藏掉,然后进行一系列的修改操作,之后再将其设置为可见,这样就可以最多只进行两次重排。具体的方法如下:
// 未优化前
const ele = document.getElementById('test');
// 一系列dom修改操作
// 优化方案一,将要修改的节点设置为不显示,之后对它进行修改,修改完成后再•显示该节点,从而只需要两次重排
const ele = document.getElementById('test');
ele.style.display = 'none';
// 一系列dom修改操作
ele.style.display = 'block';
// 优化方案二,首先创建一个文档片段(documentFragment),然后对该片段进行修改,之后将文档片段插入到文档中,只有最后将文档片段插入文档的时候会引起重排,因此只会触发一次重排。。
const fragment = document.createDocumentFragment();
const ele = document.getElementById('test');
// 一系列dom修改操作
ele.appendChild(fragment);
- 使用innerHTML
有两种在页面上创建DOM节点的方法:诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用,由于内部方式是编译好的而非解释执行的,所以执行快的多。
- 使用事件委托
把事件绑定在祖先节点,由于有事件冒泡,当事件触发时根据event对象的target属性可以知道具体事件是在那个子元素发生的。从而执行不同的行为。这样就不必每个子节点都绑定事件。
假设你有一个列表,里面每一个列表项都需要绑定相同的事件,而这个列表可能会频繁的插入和删除。如果按照平常的方法,你只能给每一个列表项都绑定一个事件处理器,并且,每当插入新的列表项的时候,你也需要为新的列表项注册新的事件处理器。这样的话,如果列表项很大的话,就会导致有特别多的事件处理器,造成极大的性能问题。而通过事件委托,我们只需要在列表项的父节点监听这个事件,由它来统一处理就可以了。这样,对于新增的列表项也不需要做额外的处理。而且事件委托的用法其实也很简单:
function handleClick(target) {
// 点击列表项的处理事件
}
function delegate (e) {
// 判断目标对象是否为列表项
if (e.target.nodeName === 'LI') {
handleClick(e.target);
}
}
const parent = document.getElementById('parent');
parent.addEventListener('click', delegate);
- css硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们甚至可以不要回流重绘,css3的 transfrom / opacity / filters / ...这些属性会触发硬件加速,但不会引发回流和重绘,不过可能会导致过多使用后大量占用内存,性能消耗严重,字体模糊等。
-
动画效果应用到position属性值为absolute或fix的元素上,因为它们脱离文档流。
-
放弃传统操作dom的模式,采用基于vue/react数据影响视图的模式。
-
将经常访问到的DOM结点进行缓存
访问 DOM 会很慢。如果要多次读取某元素的内容,最好将其保存在局部变量中。但记住重要的是,如果稍后你会删除 DOM 的值,则应将变量设置为“null”,不然会导致内存泄漏。
- 分离读写操作
offsetTop、offsetLeft、clientTop、clientLeft、scrollWidth、scrollHeight、getComputedStyle...会刷新渲染队列。
拓展
link和@import区别
本质上,这两种方式都是为了加载css文件。
结论
强烈建议使用link
标签,慎用@import
方式。
这样可以避免考虑@import
的语法规则和注意事项,避免产生资源文件下载顺序混乱和http请求过多的烦恼。
区别
1.从属关系
@import
是 CSS 提供的语法规则,只有导入样式表的作用;link
是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。
2.加载顺序
加载页面时,link
标签引入的 CSS 被同时加载;@import
引入的 CSS 将在页面加载完毕后被加载。
3.兼容性区别
@import
是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link
标签作为 HTML 元素,不存在兼容性问题。
4.DOM可控性区别
可以通过 JS 操作 DOM ,插入link
标签来改变样式;由于 DOM 方法是基于文档的,无法使用@import
的方式插入样式。
5.权重区别
CSS 权重优先级顺序简单表示为:
!important > 行内样式 > ID > 类、伪类、属性 > 标签名 > 继承 > 通配符
在link
标签引入的 CSS 文件中使用@import
时,相同样式将被该 CSS 文件本身的样式层叠。
细节
在《CSS权威指南》中写道:
@import
一定要写在除@charset
外的其他任何 CSS 规则之前,如果置于其它位置将会被浏览器忽略,而且,在@import
之后如果存在其它样式,则@import
之后的分号是必须书写,不可省略的。
到此为止,似乎事情都弄清楚了,但是突然又有个疑点浮现出来:
在讨论区别的时候,不是说加载页面时,link
标签引入的 CSS 先于@import
引入的 CSS 加载吗,那link
标签引入的样式又怎会把@import
引入的样式层叠掉呢?
要回答这个问题,首先我们要一起明确一些有关浏览器的概念:
浏览器执行过程可以简单分为加载、解析、渲染,这三个步骤。
加载:根据请求的URL进行域名解析,向服务器发送请求,接收响应文件(如 HTML、JS、CSS、图片等)。
解析:对加载到的资源(HTML、JS、CSS等)进行语法解析,构建相应的内部数据结构(比如HTML的DOM树,JS对象的属性表,CSS的样式规则等)。
渲染:构建渲染树,对各个元素进行位置计算、样式计算等,然后根据渲染树完成页面布局及绘制的过程(可以理解为“画”页面元素)。
这几个过程不是完全孤立的,会有交叉,比如HTML加载后就会进行解析,然后拉取HTML中指定的CSS、JS等。
现在,我们应该已经了解了加载和渲染的概念,明白它们是两个不同的过程,那么对上文中抛出的疑问继续追问:
link
先于@import
加载,是不是也先于@import
渲染呢?
实际上,渲染的动作一般都会执行多次,最后一次渲染,一定是依据之前加载过的所有样式整合后的渲染树进行绘制页面的,已经被渲染过的页面元素,也会被重新渲染。
那么我们就可以把@import
这种导入 CSS 文件的方式理解成一种替换,CSS 解析引擎在对一个 CSS 文件进行解析时,如在文件顶部遇到@import
,将被替换为该@import
导入的 CSS 文件中的全部样式。
@import
虽然后被加载,却会在加载完毕后置于样式表顶部,最终渲染时自然会被下面的同名样式层叠。
参考: www.cnblogs.com/my--sunshin…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!