浏览器解析过程,进程和线程,性能优化

302 阅读5分钟

页面加载顺序

// 3
window.onload = function () {
    // 触发条件:所有资源都加载完成(包含DOM TREE/CSS/图片等资源)
    console.log(document.getElementById('box'));
};

// 2
window.addEventListener('DOMContentLoaded', function () {
    // 触发条件:DOM TREE加载完成即可
    console.log(document.getElementById('box'));
});

// 1
setTimeout(() => {
    console.log(document.getElementById('box'));
}, 0);

一个页面从服务器访问回来后,浏览器拿到页面源代码,之后做的一些事情

1.生成 DOM TREE  =>对HTML的处理
   基于HTTP获取的是流文件(进制编码)
   把进制编码编译为具体的字符
   按照TOKEN进行解析(词法解析)
   生层具体的节点(元素节点/文本节点...)
   按照相互的嵌套关系生成一个DOM树(节点树)
2.生成 CSSOM TREE  =>对CSS的处理
3.生成 RENDER TREE (渲染树)
  DOM TREE + CSSOM TREE = RENDER TREE
  对于开始设置为display:none样式的元素是不会在渲染树中生成的(开始加载页面这些元素是不进行渲染的)
4.布局/回流/重排 Layout
  按照渲染树计算出每一个元素在视口中的位置和大小
5.分层
  按照计算出来的样式进行分层
  定位
  设置透明度  RGBA
  设置滤镜
  文本超过盒子大小,我们把它裁切了
  ...
  单独计算每一层的绘制列表(具体怎么绘制)
---------->以上的操作都是交给“GUI渲染线程”来完成的
6.绘制/重绘  Painting 
  把生成的绘制列表提交给“合成线程”
  “合成线程”进行我们最后的绘制,呈现在浏览器的页面上

进程和线程

进程:一个程序(浏览器新建一个页卡就是一个进程) 【工厂】

线程:一个进程中可能会包含多个线程,每个线程同时可以做一件事情 【工人】

 真正的同时做多件事情必须依赖多线程(浏览器是多线程的)
    但是JS本身是单线程的 [因为浏览器本身只分配一个线程 “GUI渲染线程” 运行JS代码]
    JS本身从本质来讲是不能同时做多件事情的
    同步:上一件事情完成,再去做下一件事情
    异步:上一件事情没有完成(我们把它做一些特殊处理),下一件事情继续执行 [但是绝对不是JS可以同时处理两个事情]
    浏览器生层DOM TREE/CSSOM TREE...的过程也是单线程的“GUI渲染线程”(配合浏览器的多线程去完成一些事情,例如:资源请求就是利用的就是浏览器的HTTP网络线程去做的)

浏览器具体的解析过程 “GUI渲染线程”

1. 自上而下解析完所有的HTML标签/各种节点后,DOM TREE就生成了
2. 但是过程中还会遇到一些比较特殊的
   link href='xxx.css' 外链式
     =>浏览器会分配一个新的HTTP网络线程去加载资源文件
     =>不会阻碍DOM树的渲染
     <style> ... </style> 内嵌式
     =>不用去请求新的资源文件了,但是此时样式还没有处理,浏览器会做一个记录,它会等待所有的CSS资源加载回来之后,按照先后顺序依次渲染CSS,从而生成CSSOM树
   @import 'xxx.css' 导入式
     =>虽然也是分配网络HTTP线程去加载资源文件
     =>但是此时GUI渲染线程会被阻塞掉[阻碍DOM树渲染](只有等资源加载回来,才会继续渲染DOM)    
     遇到script 内嵌js代码的
     =>立即执行JS(阻碍DOM TREE的渲染)
   遇到script 外链js代码的
     =>阻碍DOM TREE的渲染,同时分配一个HTTP线程去加载资源文件
     =>加载回来后立即执行JS
     =>如果JS中没有采用异步,直接获取DOM元素,而DOM元素此时还没有渲染,JS是获取不到的
   遇到img
     =>老版本浏览器会阻碍DOM渲染
     =>新版浏览器虽然不会阻碍DOM渲染,但是图片资源的请求会占用HTTP线程(浏览器同时只能开67个HTTP线程,这样图片/音视频资源加载本来就会慢一些,会影响其他资源link/script等的加载)
     =>图片资源的渲染也是比一般资源耗时间的,也会拖累页面的渲染速度
     ...

性能优化

1. 不用@import
2. link放到HEAD中(尽可能提前去加载资源文件,这样等DOM树渲染完,资源可能也加载回来了): 当代浏览器的机制越发完善,Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离
3. 如果CSS代码比较少,尽可能使用内嵌式,可以减少HTTP请求;但是如果样式比较多,采用内嵌式,第一次加载HTML都会浪费很长的时间,这样还不如基于link分开加载;移动端开发都是内嵌有先(当然也要考虑CSS代码量的);
4. 减少DOM或者减少DOM的层级嵌套,以及标签语义化...(当代前端开发,开始只把首屏的结构/内容写出来,渲染只是首屏的,当首屏加载完,页面滚动的时候,再基于JS创建其他屏幕的结构和内容 => 骨架屏/SSR  =>客户端骨架屏,开始首屏结构都没有,只有一个loading或者占位图而已...)
5. 把script放到页面的底部(先渲染DOM TREE,再执行JS,也可以获取到DOM元素了),也可以基于事件DOMContentLoaded/load等到结构加载完再去获取DOM元素
6. async / defer 给script设置的属性
	 =>async是开辟HTTP线程加载资源文件,此时DOM TREE继续,但是资源文件一但加载回来,停止DOM TREE,先执行JS代码(不考虑JS引入顺序,谁先加载回来谁先执行)
 	 =>defer也是开辟HTTP线程加载资源文件,即使资源文件加载回来,也会等待DOM树渲染完,然后按照JS的导入顺序依次执行JS(不兼容低版本浏览器)
7. 图片合并(Sripte)/ BASE64(好用但是慎用) / iconfont(CSS绘制) / svg
	 => 图片的懒加载
   ....
8. CRP(critical rendering path)浏览器关键路径节点优化