【浏览器】渲染机制
要想理解浏览器的运行环境,我们先要搞明白一些计算机组件以及它们的作用。
CPU
它可以串行地一件接着一件处理交给它的任务。很久之前的时候大多数CPU只有一个核心,不过在现在的硬件设备上CPU通常会有多个核心,因为多核心CPU可以大大提高手机和电脑的运算能力。
GPU
图形处理器 - 或者说GPU(Graphics Processing Unit)是计算机的另外一个重要组成部分。和功能强大的CPU核心不一样的是,单个GPU核心只能处理一些简单的任务,不过它胜在数量多,单片GPU上会有很多很多的核心可以同时工作,也就是说它的并行计算能力是非常强的。图形处理器(GPU)顾名思义一开始就是专门用来处理图形的,所以在说到图形使用GPU(using)或者GPU支持(backed)时,人们就会联想到图形快速渲染或者流畅的用户体验相关的概念。最近几年来,随着GPU加速概念的流行,在GPU上单独进行的计算也变得越来越多了。
在进程和线程上执行程序
进程可以看成正在被执行的应用程序,一个进程可以有一个或多个线程。
当你启动一个应用程序的时候,操作系统会为这个程序创建一个进程同时还为这个进程分配一片私有的内存空间,这片空间会被用来存储所有程序相关的数据和状态。当你关闭这个程序的时候,这个程序对应的进程也会随之消失,进程对应的内存空间也会被操作系统释放掉。
有时候为了满足功能的需要,创建的进程会叫系统创建另外一些进程去处理其它任务,不过新建的进程会拥有全新的独立的内存空间而不是和原来的进程共用内存空间。如果这些进程需要通信,它们要通过IPC机制(Inter Process Communication)来进行。很多应用程序都会采取这种多进程的方式来工作,因为进程和进程之间是互相独立的它们互不影响,换句话来说,如果其中一个工作进程(worker process)挂掉了其他进程不会受到影响,而且挂掉的进程还可以重启。
浏现代浏览器:多进程、多线程模型
浏览器会启动多个进程,每个进程里面有多个线程,不同进程通过IPC进行通信。
最新的Chrome浏览器架构,它采用的是多进程架构,Chrome会尽可能为每一个tab甚至是页面里面的每一个iframe都分配一个单独的进程。
采用多进程模型可以带来的好处:
避免因单个页面的不响应或者崩溃影响整个浏览器的稳定性
当第三方插件崩溃时,也不会影响整个浏览器的稳定性
安全
多进程架构也有它不好的地方,那就是进程的内存消耗。由于每个进程都有各自独立的内存空间,所以它们不能像存在于同一个进程的线程那样共用内存空间,这就造成了一些基础的架构(例如V8 JavaScript引擎)会在不同进程的内存空间同时存在的问题,这些重复的内容会消耗更多的内存。所以为了节省内存,Chrome会限制被启动的进程数目,当进程数达到一定的界限后,Chrome会将访问同一个网站的tab都放在一个进程里面跑。
当Chrome运行在一些性能比较好的硬件时,浏览器进程相关的服务会被放在不同的进程运行以提高系统的稳定性。相反如果硬件性能不好,这些服务就会被放在同一个进程里面执行来减少内存的占用
不同的进程负责浏览器不同部分的界面内容
1.Browser进程: 浏览器的主进程,负责浏览器界面的显示,和各个页面的管理, 浏览器中所有其他类型进程的祖先,负责其他进程的的创建和销毁。它有且只有一个!!!!!
2.Renderer进程: 网页渲染进程,负责页面的渲染,可以有多个。当然渲染进程的数量不一定等于你开打网页的个数
3.各种插件进程
4.GPU进程
除了上面列出来的进程,Chrome还有很多其他进程在工作,例如扩展进程(Extension Process)和工具进程(utility process)。如果你想看一下你的Chrome浏览器现在有多少个进程在跑可以点击浏览器右上角的更多按钮,选择更多工具和任务管理器:
浏览器的渲染
渲染引擎
一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块
HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树。
CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施
Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果。
布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型
绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果
渲染流程
浏览器渲染页面的整个过程:浏览器会从上到下解析文档。
1.构建DOM
遇到HTML标记,HTML解析器解析就会开始接收HTML数据解析接收到的文本数据并把它转化为一个DOM(Document Object Model)对象
2.子资源加载
遇见 style/link 标记 调用解析器 处理 CSS 标记并构建 CSS样式树(link标记会取后才去生成rander树,所以会导致css阻塞)。
遇见 script 标记 ,取后再分配JS引擎线程把JS代码先渲染了,都渲染完了,GUI再继续往下渲染(宏任务)
3.JavaScript会阻塞HTML的解析过程
当HTML解析器碰到script标签的时候,它会停止HTML文档的解析从而转向JavaScript代码的加载,解析以及执行。为什么要这样做呢?因为script标签中的JavaScript可能会使用诸如document.write()这样的代码改变文档流的形状,从而使整个DOM树的结构发生根本性的改变(HTML规范里面的overview of the parsing model部分有很好的示意图)。因为这个原因,HTML解析器不得不等JavaScript执行完成之后才能继续对HTML文档流的解析工作
4.生成Render树
将 DOM树 与 CSS树 合并成一个渲染树。
4.布局与绘制
根据渲染树来渲染,以计算每个节点的几何信息(布局)。将各个节点绘制到屏幕上(绘制)。
阻塞
CSS 被视为渲染阻塞资源 (包括JS) ,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕,才会进行下一阶段。
JavaScript 被认为是解释器阻塞资源,HTML解析会被JS阻塞,它不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性
回流和重绘
操作DOM 比较消耗性能:大部分性能都消耗在了“DOM 的重排(回流reflow)和重绘repaint”、
页面第一次渲染,必然会出现一次layout(回流)和painting(重绘);第一次渲染完成后:
重排(回流):如果浏览器的视口大小发生改变 或者 页面中元素的位置,大小发生改变,再或者DOM结构发生变化(删除,新增元素或者挪动位置)...浏览器都需要重新计算节点在视口中(本层)的最新位置【也就是layout】,完成后再分层和重新绘制-->此操作非常消耗性能,所以我们应该尽可能的减少重排(回流)的次数
重绘:视口/元素的位置大小都不变,只是修改了一些基础样式(例如:背景颜色,文字颜色,透明度),此时我们无需重新layout,只需重新painting即可-->重绘是必不可免的,只要想让页面第一次渲染完后还可以改变还可以再改变,必然需要重绘,而且触发一次回流,也必然会经历重绘
如果基于JS操作DOM,那么前端性能优化必做的事情:减少DOM的重排(回流)
基于vue/react/angular等框架进行开发,我们是基于数据驱动视图渲染,规避了直接操作DOM,我们只需要操作数据,框架内部帮助我们操作DOM(他们做了很多减少DOM重排的操作)