浏览器的底层渲染机制 及 页面运行时的性能优化

编写一个页面所需的技术栈:
   HTML & CSS 「less/sass/stylus...」
      开发者按照W3C规范去编写代码,浏览器本身也是按照W3C规范去编译解析我们的代码,最后渲染出我们想要的效果!!
   javascript 「ES6+/XMLHttpRequest/vue/react...」
      开发者按照ECMAScript-262规范去开发代码,浏览器也按照这个规范来解析

浏览器内核:
   谷歌:blink(webkit内核的分支)
   Safari:webkit
   火狐:Gecko
   欧朋:Presto(后期版本也改为webkit内核)
   IE:Trident
   Edge:chromemui
   国产浏览器:之前用的Trident内核,现在用webkit内核
   ...
   手机端浏览器基本上都是webkit内核的

===============
浏览器基于自己的内核(渲染引擎  webkit->V8引擎),自上而下、自左而右(有特殊情况)渲染和解析代码,最后在浏览器中绘制出对应的页面和效果!!

渲染过程中「基于GUI线程渲染」:
  遇到 <link href='...'> , 浏览器会单独分配一个线程「HTTP网络线程」去服务器获取资源文件信息,此时GUI继续向下渲染 => 异步操作
    + 但是不同浏览器可以允许同时并发的HTTP请求,有次数限制,一般是5~7次

  遇到 <style> ... </style>,无需分配线程去获取资源文件,只需要等待DOM TREE生成后,其他样式资源也获取后,按照顺序依次渲染即可!!

  遇到 <style> @import 'xxx.css'; </style>,阻碍GUI的渲染,需要等待服务器返回对应样式资源后,GUI才可以继续向下渲染!!

  遇到 <img src='xxx.png'> 也是分配新的线程去获取图片文件,GUI继续渲染 => 异步操作

  遇到 <script src='xxx.js'> , 也会阻碍GUI的渲染,虽然分配了一个新的HTTP线程去服务器获取JS文件,但是文件没有获取到之前,GUI是不进行渲染的!! 只有获取到JS代码,并且把它执行完,GUI才会继续渲染!! => 同步操作
     + 把<script>放在HEAD标签中,经常是获取不到DOM元素的
     解决办法:
     @1 把<script>放在BODY最底部,这样是等待DOM TREE生成后才执行的,此时可以获取DOM元素「推荐」
     @2 也可以基于 window.onload(所有资源加载完成) 或者 DOMContentLoaded(DOM TREE生成) 事件去监听
     @3 如果是导入外部的JS资源,还可以基于 async 或者 defer 属性做延迟异步处理!!
        async: 分配新的线程去获取JS,此时GUI继续向下渲染「改为异步操作」;当JS资源获取后,立即停止GUI渲染,先把获取的JS执行,执行完成后,再继续GUI渲染!!
        defer: 分配新的线程去获取JS,此时GUI继续向下渲染;等待GUI渲染完,而且所有设置defer的资源都获取到了,按照之前导入的先后顺序依次执行代码「和<link>类似」
        ---
        如果导入的JS资源不存在依赖关系,可以使用async;但凡需要依赖关系,则使用defer;

==================
1. 生成 DOM TREE
   GUI渲染中,遇到<link>、<img>...等,都分配HTTP线程去获取资源,GUI继续渲染;所以在资源获取到之前,首先会把当前页面中的HTML结构规划好,规划好的HTML结构就是 “DOM TREE”!!

2. 生成 CSSOM TREE
   DOM TREE生成后,等待请求的样式资源获取到,GUI渲染线程开始渲染和解析CSS代码「重点:等待所有样式资源都获取到之后,按照之前的导入的顺序依次渲染」

3. 生成 RENDER TREE 
   把 DOM TREE 和 CSSOM TREE 合并在一起,生成 RENDER TREE「渲染树中包含了每个节点的样式及节点之间的嵌套关系」

4. 浏览器按照 RENDER TREE 进行渲染
   4.1 Layout布局排列
       根据当前浏览器视口的大小,计算出每一个节点在视口中的位置等
   4.2 分层
       把节点根据对应的样式,放在指定的文档流中「创造新文档流的样式:float、position、transform...」
       并且规划出每一层具体的绘制步骤和要绘制的样式
   4.3 Painting绘制
       按照之前规划好的方式一层层进行绘制,最后呈现出页面的样式和效果

===============
根据浏览器渲染的机制,我们在每一个核心步骤中去做一些优化,以此来加快页面渲染的速度,缩短页面首次打开的时间「或者是减少页面白屏等待时间」 => CRP「关键渲染路径」优化方案

----优化首次渲染时间
@1 CSS代码较少的情况下使用“内嵌式”样式「尤其是移动端」;代码较多的情况下,使用“外链式”样式「尽可能把样式文件合并压缩为1个」;非必要,绝对不使用“@import导入式”样式,因为它会阻碍GUI渲染!!;
@2 JS代码都放在页面的底部「可以设置async|defer」;
@3 图片一定要做“懒加载”:第一次渲染页面,不去获取真实的图片资源,加快页面的渲染;第一次渲染完,再把可视窗口中出现的图片做懒加载!!
@4 减少HTML的层级嵌套、一定要使用语义化标签...加快DOM TREE的构建!!
@5 CSS选择器结构不要太深「扩展:雅虎CSS优化的36条建议」...加快CSSOM TREE的构建!!
@6 前端骨架屏:
   + 在页面真实内容渲染之前,先给一个Loading的效果(一般都是用 灰白框框 占位)!! 「提高人性化体验」
   + 开始最好只渲染首屏内容「PS:可以交给服务器来渲染」
   + 后期向下滑动,再渲染其他屏幕的内容
   + ...

----首次渲染完成后,页面运行时的性能优化
俗话说:操作DOM非常的消耗性能? => 因为操作DOM会导致页面的:回流(重排)和重绘
   重排:当某个节点的位置或者大小发生改变(含:新增或者删除节点、调整节点位置以及大小等),再或者浏览器视口大小发生改变,浏览器都需要重新计算每一个节点在视口中的布局位置(或者只调整节点所在文档流中的位置),也就是重新Layout,我们把这个操作称之为“重排(回流)”!!
   重绘:节点的位置和大小不变,只是修改了节点的背景颜色、文字颜色等样式,此时只需要重新绘制这些更改的样式即可
   ---
   第一次渲染页面必然会引发一次Layout和Painting
   触发重排必然会引发一次重绘
   ---
   所以操作DOM的性能优化,核心在于如何减少页面的重排次数!!

@1 告别直接操作DOM,基于数据驱动视图的渲染「框架:Vue、React、Angular...」

@2 分离“读写”「把设置样式和获取样式的代码分开」
   浏览器渲染队列机制:在当前上下文中,遇到修改元素样式的代码,浏览器并没有立即渲染,而是把其放在“渲染队列中”,如果再遇到其他修改样式的代码,也是一样放在“渲染队列中”...直到当前上下文执行结束,才会把“渲染队列中”的操作统一执行一次,引发一次重排!!

   box.style.width='100px';
   box.style.height='100px';
   box.style.margin='20px auto';

---
   刷新渲染队列:代码执行过程中,遇到获取元素样式的操作,会刷新渲染队列「立即把现有渲染队列中的操作处理一次,引发一次重排」

   box.style.width='100px';
   console.log(box.clientWidth); //100
   box.style.height='100px';
   box.style.margin='20px auto';

 ---
   //这样的写法是错误的「引发两次重排」

   box.style.top=box.offsetTop+10+'px';
   box.style.left=box.offsetLeft+10+'px';

//正确写法「引发一次重排」

   let t=box.offsetTop,
       l=box.offsetLeft;
   box.style.top=t+10+'px';
   box.style.left=l+10+'px';

@3 样式集中修改

   .active{
       width:100px;
       height:100px;
       margin:20px auto;
   }
   box.className='active';

  ---

   box.style.cssText='width:100px;height:100px;margin:20px auto;';

@4 使用CSS3中的“transform”修改样式,这种操作,浏览器内部开启了硬件加速「不会引发重排」
   即便不使用transform,我们也尽可能修改,脱离文档流的元素的样式「因为:这样的元素在一个新的平面上,后期重排也只是对这个平面中的元素进行重新排列...」

@5 批量新增元素
   //引发10次重排

   for(let i=0;i<10;i++){
       let div=document.createElement('div');
       document.body.appendChild(div);
   }

---
   //引发1次重排
   //问题:innerHTML+= 会把之前的内容当做字符串获取到,和新的字符串拼接起来,最后整体插入到容器中,这样之前容器中元素所绑定的事件都会消失!!

   let str=``;
   for(let i=0;i<10;i++){
       str+=`<div></div>`;
   }
   document.body.innerHTML+=str;

 ---
   //文档碎片:原来存储DOM元素的容器
   //引发1次重排

 let frag=document.createDocumentFragment();
   for(let i=0;i<10;i++){
       let div=document.createElement('div');
       frag.appendChild(div);
   }
   document.body.appendChild(frag);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值