浏览器渲染
浏览器渲染:HTML解释器将从网络或本地获取的字节流转换成dom树;
1、Bytes字节流——Characters字符流——Tokens词语——Nodes节点——Dom树;
2、渲染Dom树,解析html下的标签如meta、link、script,渲染Dom节点。
字节流先解码成字符流,然后通过各种词法分析器(HTMLTokenizer,XSSAuditor)解析成词语,再通过语法分析器(HTMLDocumentParser,HTMLTreeBuilder)解析成节点,最后节点(HTMLConstructionSite)组建成dom树。
进程与线程
进程:指的是系统正在运行的1个程序。
浏览器打开1个页面就是打开1个进程,进程都是由系统资源分配的独立地址空间的实体。
每个进程一般包含多个线程,同一个进程下的多个线程可以共享部分状态和读取同一块内存,每个线程拥有自己的寄存器和栈,其他线程可以访问这些栈内存。
线程:是进程的1个实体,就是去做具体事情的东西,一个线程只能专注做一件事情。
当一个线程修改了进程中的资源,它的兄弟线程可立即看到这种变化。
进程与线程的区别:
1、地址空间:进程地址空间相互独立,同一进程下的线程空间共享。网页A进程下的线程(如网页A打开文件)在网页B进程内不可见。
2、通信:进程之间通信需要(管道、消息队列、共享内存、套接字),同一进程下线程可以直接读写数据来进行通信。
浏览器可开辟多个进程与多个线程
浏览器从关闭到启动,然后新开一个页面至少需要:1个浏览器进程,1个GPU进程,1个网络进程,和1个渲染进程,一共4个进程
Chrome浏览器包括:1个浏览器主进程
,1个GPU进程
,1个网络进程
,多个渲染进程
,和多个插件进程
浏览器进程
: 控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作,同时提供存储功能
GPU进程
:负责整个浏览器界面的渲染。Chrome刚开始发布的时候是没有GPU进程的,而使用GPU的初衷是为了实现3D CSS效果,只是后面网页、Chrome的UI界面都用GPU来绘制,这使GPU成为浏览器普遍的需求,最后Chrome在多进程架构上也引入了GPU进程
网络进程
:负责发起和接受网络请求,以前是作为模块运行在浏览器进程里面的,后面才独立出来,成为一个单独的进程
插件进程
:主要是负责插件的运行,因为插件可能崩溃,所以需要通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响
渲染进程
:负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎 Blink 和 JS 引擎 V8 都是运行在该进程中,默认情况下Chrome会为每个Tab标签页创建一个渲染进程
浏览器呈现出页面过程中,大部分工作都是在渲染进程中完成。
渲染进程中的线程:
GUI渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行
JS引擎线程:一个页面只有一个JS引擎(单线程),负责解析和执行js,处理用户交互,操作DOM树和CSS样式树。
HTTP线程:如果请求有回调函数,则把回调函数添加到事件队列,排队等待JS引擎处理
定时器线程:因为JS引擎是单线程,如果阻塞定时器线程就不准确了,所以浏览器开辟了单独的线程来负责定时器线程。
DOM线程:主要用来控制事件循环,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,排队等待JS引擎处理
JS是采用事件驱动(event-driven)机制,来响应用户操作的,也就是单线程异步编程,JS是事件队列(EventQueue)+ 事件轮循(EventLoop)机制来完成单线程异步编程。
当处理一些不能立即执行的函数或者其他的代码时,会将对应的任务在其可以触发的时机,添加到事件队列的末端(比如:定时任务、鼠标点击、AJAX异步请求等)
事件循环机制会在JS引擎线程空闲时,循环访问事件队列的头部,如果有函数,则会将该函数推到执行栈中并立即执行
浏览器渲染页面
遇到style内嵌样式,GUI直接渲染
如果css代码量比较少,直接内嵌即可,拉取html的时候css也在里面,会直接渲染;
如果css代码量比较多,如果还是要内嵌,会影响拉取html的速度,渲染会比较慢,此时就应该是要link外部样式
遇到link,浏览器开启http线程去请求资源,同时GUI继续向下执行(异步)。GUI渲染完后等link加载完会再次执行GUI渲染页面。
浏览器同时能发送的http请求是有数量限制的。(谷歌:5~7个);
超过最大请求数的会排队,等待前面请求完成再请求。
http请求数量越少越好
遇到@import,浏览器开启http线程去请求资源,同时GUI暂停了(导入样式会阻碍GUI渲染),当资源请求回来后,GUI才继续渲染(同步)。
真实项目应该慎用@import
遇到<script src='xxx'></script>会阻碍GUI渲染(同步)。
加defer:请求是异步的,和link一样,不会阻碍GUI渲染,GUI渲染完后等defer加载完后才会去渲染js。不同的script文件可以建立依赖关系,GUI渲染完成后会等待所有的加defer文件请求完成后,按照编写顺序执行这个defer文件。
加async:请求是异步的会单独开启http请求,此时GUI继续渲染,等async请求回来后,GUI渲染会暂停,会去渲染js。
加async请求的不同文件之间不能有依赖关系,因为加async文件是谁先请求回来谁先执行。
项目优化点:
把link文件放在头部,在页面还没开始渲染就开启http去请求文件,dom渲染完,css也基本请求回来,更有效利用时间,提升页面渲染速度;把script放在底部,防止阻塞GUI渲染,如果不放在底部,就需要给script加async或defer。
预加载:谷歌浏览器的预加载扫描器会预先扫描html里面的src和link标签,在dom加载就开启http请求。但是GUI渲染的时候头部遇到script还是会被阻塞,会先渲染js然后才会继续GUI渲染。
webpack4.6.0已经实现了对预拉取和预加载的支持
import(/* webpackPreload: true */ 'xxx'); 预加载, 浏览器会优先获取该资源
import(/* webpackPrefetch: true */ 'xxx'); 预拉取, 浏览器会等空闲时获取该资源
import(/* webpackChunkName: "lodash" */ /* webpackPrefetch: true */ 'lodash');
webpackChunkName
是为预加载的文件取别名
webpackPreload
会在父 chunk 加载时并行下载文件
webpackPrefetch
会在浏览器闲置下载文件
DOM TREE(当dom树创建完成会触发DOMContentLoaded事件,在这里可能会渲染js) => CSSOM TREE => RENDER-TREE渲染树(浏览器未来会按照这个结构来绘制dom树) => Layout布局计算(回流/重排) => Painting绘制(重绘).
页面第一次加载一定会触发回流和重绘
如果改变了元素的大小和位置,浏览器需要重新计算,就会触发回流,一旦发生回流必定触发重绘(回流很消耗性能: dom操作消耗性能90%说的都是dom回流. vue和react之所以性能更高,是因为它的虚拟dom变成真实dom的过程避免多次操作dom,它会整合很多改动集中做dom渲染 )
如果改变了元素的普通样式改变,位置和大小不变,只需要重绘即可.
减少和避免回流
放弃传统dom操作,使用vue和react框架操作视图
读写分离,设置和读取样式分开操作,避免渲染队列刷新导致多次回流。https://blog.csdn.net/qq_27483091/article/details/120252230(轮播图应用读写分离使渲染队列立即执行)
缓存布局:div.style.left = div.style.left + 10 +'px';
div.style.width =div.style.width+ 100 +'px';
改为 var left = div.style.left,width = div.style.width ;
div.style.left = left + 10 +'px'; div.style.width= width + 100 +'px';
元素批量修改:
使用文档碎片 createDocumentFragment
模板字符串拼接
集中改变样式(老版浏览器): element.style.cssText ='width:50px;height:20;'; 尽量写一起
动画效果应该应用在position为absolute或fixed,脱离文档流,分层渲染
使用css3硬件加速(属于开启GPU进程做单独加速处理):不会引起回流和重绘,transform、opacty、filters