0.1 + 0.2 != 0.3

计算机中数的表示

  • 64位浮点数, 1 位符号位 + 11 位指数(带偏移量) + 52 位的尾数
  • 32位浮点数, 1 位符号位 + 8 位指数(带偏移量) + 23 位的尾数

1 如何存储

1 * 2^0 = 1;

0 如何存储

0 * 2 ^ 0 = 0

0.1 如何存储

1.10011001100110011 * 2 ^ -4

0.2 如何存储

1.10011001100110011 * 2 ^ -3

NAN 如何存储

NaN在内存中的值则是阶码全1,尾数不全0

Infinity 如何存储

在内存中的值是阶码为全1,尾数全0

Max如何存储

参考文档

  1. 查看浮点数存储
  2. 掘金0.1 + 0.2 != 0.3
  3. IEEE754维基百科

    TODO:小数在计算机中的表示

TODO:0.1 + 0.2 发生了什么

HTTP2.0特征

多路复用

多路复用代替原来的序列和阻塞机制, 所有就是请求的都是通过一个TCP连接并发完成. 同时也很好的解决了浏览器限制同一个域名下的请求数量的问题

在HTTP/2中,有了二进制分帧之后, HTTP/2不再依赖TCP链接去实现多流并行了,在HTTP/2中:

  1. 同域名下所有的通信都在单个链接上完成, 同个域名只需要占用一个TCP链接,使用一个连接并行发送多个请求和响应
  2. 单个连接可以承载任意数量的双向数据流,单个连接上可以并行交错的请求和响应,之间互不干扰
  3. 数据流以消息的形式发送, 而消息又由一个或多帧组成, 多个帧之间可以乱序发送, 因为根据帧首部的流标识可以重新组装, 每个请求都可以带一个31bit的优先值, 0标示最高优先级, 数值越大优先级越低

帧和流

帧(frame)

HTTP/2 中数据传输的最小单位, 因此帧不仅要细分表达 HTTP/1.x中的各个部分,也优化了HTTP/1.x表达不好的地方, 同时还增加了HTTP/1.x表达不了的方式

每一帧包含几个字段, 有 length、type、flags、stream、identifier、frame playload等, 其中 type 代表帧的类型

在HTTP/2的标准中定义了10种不同的类型

  1. HEADERS frame
  2. DATA frame
  3. PRIOPRITY (设置流的优先级)
  4. RST_STREAM (终止流)
  5. SETTINGS(设置此连接的参数)
  6. PUSH_PROMISE (服务器推送)
  7. PING(测量RTT)
  8. GOAWAY(终止连接)
  9. WINDOW——UPDATE(流量控制)
  10. CONTINUATION (继续传输头部数据)

在HTTP2.0中, 它把数据报的两达部分分成了Header frame 和 data frame 也就是 头部帧和数据体帧

流(stream)

流: 存在于连接中的一个虚拟通道.流可以承载双向消息, 每个流都有一个唯一的整数ID. HTP/2 长连接中的数据报不是按照请求-响应顺序发送的, 一个完整的请求或响应(称一个数据流stream, 每个数据流都有一个独一无二的编号)可能会分成非连续多次发送.

特点:

  1. 双向性: 同一个流内,可同时发送和接受数据
  2. 有序性: 流中被传输的数据就是二进制帧. 帧在流上的被发送于被接收都是按照顺序进行的
  3. 并行性: 流中的二进制帧 都是被并行传输的,无需按顺序等待
  4. 流的创建: 流可以被客户端和服务器单方面建立, 使用、共享
  5. 流的关闭: 流也可以被任意一方关闭
  6. HEADERS 帧在DATE 帧前面
  7. 流的ID都是奇数,说明是由客户端发起的,服务端发起的就是偶数

发展历程

从 HTTP/0.9 到 Http/2 要发送多个请求, 从多个TCP连接 => Keep-alive => 管道化 => 多路复用 不断的减少多次创建 TCP 等等带来的性能损耗

多个TCP连接

在最早的时候没有 keep-alive 只能创建多个TCP连接来做多次请求. 一次请求完成就会关闭本次的TCP 连接, 下个请求又要重新建立新的TCP连接传输完成数据再次关闭,造成很大的性能消耗

Keep-alive

Keep-alive 解决的核心问题是: 一定时间内,同一域名多次请求数据,只建立一次HTTP请求,其他请求可复用每一次建立的连接通道,以达到提高请求效率的问题, 这里面所说的一定时间是可以配置的,不管你用的是Apache还是nginx

以往,浏览器判断响应数据是否接收完毕,是看连接是否关闭.在使用持久连接后,就不能这样了,这就要求服务器对持久连接的响应头部一定要返回content-length标识body的长度,供浏览器判断界限.有时,content-length的方法并不是太准确,也可以使用transfer-encoding:chunked头部发送一串一串的数据,最后长度为0的chunked标示结束

多次http请求效果如下图所示

Keep-alive 还是存在如下问题:

  1. 串行的文件传输
  2. 同域并行请求限制带来的阻塞(6~8)个

管线化

HTTP管线化可以克服同域并行请求限制带来的阻塞,它是建立在持久连接上的,是把所有的请求一并发给服务器, 但是服务器需要按照顺序一个一个的响应,而不是等到一个响应回来才能发下一个请求,这样就节省了很多请求到服务器的时间,不过HTTP管线化依旧有阻塞的问题,若上一响应迟迟不回,后面的响应都会被阻塞到

多路复用

多路复用代替原来的序列和阻塞机制.所有就是请求的都是通过一个TCP连接并发完成.因为在多路复用之前所有的传输是基于基础文本的,在多路复用中是基于二进制数据帧的,传输、消息、流,所以可以做到乱序的传输.多路复用对同一域名下所有请求都是基于流,所以不存在同域并行的阻塞.多次请求如下图:

HTTP/1.x默认开启持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟

HTTP/2.0 支持多路复用,这是HTTP/1.x持久连接的升级版, 多路复用,就是在一个TCP 连接中可以存在多条流, 也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求), 通过重新排序还原请求, 多路复用允许并发多个请求,每个请求及该请求的响应不需要等待其他的请求或响应,避免了线头阻塞问题.这样某个请求任务耗时严重,不会影响其他连接的正常执行, 极大的提高传输性能

总结

在 HTTP/2 中, 有两个非常重要的概念,分别是帧(frame)和流(stream).

帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流, 流也就是多个帧组成的数据流

HTTP2 采用二进制数据帧传输, 取代了HTTP1.x的文本格式,二进制格式解析更高效.

多路复用代替了 HTTP1.x的序列和阻塞机制没,所有的相同域名请求都通过同一个TCP连接并发完成.同一TCP中可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求,通过这个技术,可以避免HTTP旧版本中的队头阻塞问题,极大的提高传输性能.

面向切面编程

面向切面编程

面向切面编程(AOP,Aspect Orient Programming) 主要实现的目的是针对业务处理过程中的切面进行提取,所面对的是处理过程中的某个步骤或阶段, 以取得逻辑过程中各个部分之间的低耦合行的隔离效果

AOP 是对OOP的一个横向的补充,主要作用是把一些业务无关的功能抽离,例如日志打印、统计数据、安全控制、异常处理等.这些功能都与核心业务无关,但又随处可见.将其抽离出来用动态插入的方式嵌入到各个业务逻辑中.好处是业务模块可变得比较干净、不受污染,同时功能点能够得到很好的复用,给模块解耦

关键词解释

  1. 切面(Aspect): 共有功能的实现,如日志切面、权限切面、事物切面等.
  2. 通知(Advice): 切面的具体实现,以目标方法为参考点,根据防止的位置不同,可分为前置通知(Before)、后置通知(AfterReturing)、异常通知(AfterThrowing)、最终通知(After)、环绕通知(Around) 5种.
  3. 连接点(JoinPoint): 程序运行过程中能够插入切面的地点,例如方法调用、异常抛出、字段修改等
  4. 切入点(Pointcut): 用于定义通知应该切入到哪些连接点上,不同通知通常需切入到不同连接点上,这种精确的匹配是由切入点的正则来定义的
  5. 目标对象(Target): 是哪些即将切入切面的对象,即哪些被通知的对象.这些对象已经只剩下干干净净的核心业务逻辑代码了
  6. 代理对象(Proxy): 将通知应用到目标对象之后被动态创建的对象,简单理解为代理对象的功能等同于目标对象的核心业务逻辑功能加上公有功能呢,代理对象对于使用者而言是透明的,是程序运行过程中的产物
  7. 织入(Weaving): 将切面应用到目标对象从而创建一个新的代理对象的过程.

实例展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.before = function (fn) {
const __self = this;

return function () {
fn.apply(this, arguments);
return __self.apply(this, arguments);
};
};

Function.prototype.after = function (fn) {
const __self = this;
return function () {
const result = __self.apply(this, arguments);
fn.apply(this, arguments);
return result;
};
};

function test() {
console.log(2);
return "test";
}

test.after(() => console.log(3)).before(() => console.log(1))();

参考

JS面向切面编程

JS IN CSS

先要了解 CSS IN JS

现代一般都使用webpack来将CSS 编译打包进JS,配置如下

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
],
},
}

CSS 不能算是编程语言,只是网页样式的一种描述方法,为了能让CSS也能适用软件工程方法,我们想出了各种解决办法, Less, SASS, PostCSS, CSS in JS

CSS Moudles 不一样 功能单纯, 只是加入了局部作用域和模块依赖,可以保证某个组件的样式,不会影响到其他组件

index.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:root {
--color: red;
}

:global(.title) {
color: green;
}
.test {
width: 500px;
height: 500px;
background: var(--color);
}
.bb {
color: 12;
}

如果要脱离module进入 全局样式需要适用:global语法 指明这些样式适用于全局

index.js

1
2
3
import index from './index.css'
console.log(index)
// {test: "_346xYzrQcc1jTx0FqWrEBf", bb: "_2OaN2P3RC7-bMTTq9-XUm-"}

JS 只会打印出是CSS Module 的 Map对象, 对象的键为class或id名称, 值为[hash:base64]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="_346xYzrQcc1jTx0FqWrEBf">
<div class="title">123</div>
</div>
<script src="./dist/main.js"></script>
</body>
</html>

使用的时候直接使用修改后的hash值,调用从而避免全局污染,

原理

CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效

产生局部作用域的唯一方法就是适用一个独一无二的class名字,不会与其他选择器重名

参考资料

CSS Modules 用法教程

再了解 JS IN CSS

for-in与Object.keys的区别

区别

for-in是javascript中最常见的迭代语句,常常用来枚举对象的属性, 某些情况下,可能按照随机遍历数组元素

而Object构造器又一个实例属性为keys,则可以返回以对象的属性为元素的数组,数组中的属性名的顺序跟使用for-in遍历的返回顺序是一样的

for-in 循环会枚举对象原型链上的可枚举属性, 而 Obejct.keys不会

for in

  • 遍历对象及其原型链上的可枚举属性
  • 如果用于遍历数组,除了遍历其元素外,还会遍历开发者对数组对象自定义的可枚举属性及其原型链上的可枚举属性
  • 遍历对对象返回的属性名和遍历数组返回的索引都是string类型
  • 某些情况下,可能按随机顺序遍历数组元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array.prototype.getLength = function () {
return this.length
}

var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g']

arr.name = 'yd'

Object.defineProperty(arr, 'age', {
enumerable: true,
value: 5,
writable: true,
configurable: true,
})

for (var i in arr) {
console.log(i)
}

Object.keys

  • 返回对象自身可枚举属性组成的数组
  • 不会遍历对象原型链上的属性以及Symbol属性
  • 对数组的遍历顺序和for in一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Company() {
this.name = 'yd'
}

Company.prototype.getName = function () {
return this.name
}

const yd = new Company()

Object.defineProperty(yd, 'age', {
enumerable: true,
value: 5,
writable: true,
configurable: true,
})

Object.defineProperty(yd, 'address', {
enumerable: false,
value: '北京',
writable: true,
configurable: true,
})

console.log(Object.keys(yd))

for of

  • es6 中添加的循环遍历语法
  • 支持遍历数组, 类数组对象(DOM NodeList), 字符串, Map对象, Set对象
  • 不支持遍历普通对象
  • 遍历后输出的结果为数组元素的值
  • 可搭配实例方法entries(), 同时输出数组的内容和索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 不会遍历到对象属性及其原型属性
Array.prototype.getLength = function () {
return this.length
}

const arr = ['a', 'b', 'c', 'd', 'e']

arr.name = 'yd'

Object.defineProperty(arr, 'age', {
enumerable: true,
value: 17,
writable: true,
configurable: true,
})

for (let i of arr) {
console.log(i) // a, b, c
}

// 如果遍历对象, 可与Object.keys配合
const company = {
name: 'yd',
age: 5,
city: 'beijing',
}

for (let key of Object.keys(company)) {
console.log(company[key]) // yd, 5, beijing
}

// 配合entries 输出数组索引和值/对象的键值
const arr = ['a', 'b', 'c']
for (let [index, value] of Object.entries(arr)) {
console.log(index, ':', value)
// 0:a, 1: b, 2: c
}

const obj = {
name: 'yd',
age: 5,
city: 'beijing',
}

for (let [key, value] of Object.entries(obj)) {
console.log(key, ': ', value)

// name: yd, age: 5, city: beijing
}

Object.entries

Object.entries(obj): 如果参数的数据结构具有键和值,则返回一个二元数组, 数组的每个元素为参数的[key, value]数组

1
2
3
4
5
6
7
8
9
// Symbol 属性会被忽略

Object.entries({
[Symbol()]: 1,
name: 'yd',
age: 5,
})

// [[name, yd], [age, 5]]

css重回重排

什么是重绘、重排,如何避免

重绘重排

当我们改变了一个元素的尺寸位置属性时, 会重新进行样式计算(computed style)布局 (layout) 绘制(paint)以及后面的所有流程, 这种行为称为重排

当改变了某个元素的颜色属性时不会重新触发布局, 但是还是会触发样式计算和绘制这就是重绘

我们可以发现重绘和重排都会占用主线程,还有JS也会运行在主线程,所以就会出现抢占执行时间的问题,如果你写了一个不断导致重绘重排的动画,浏览器则需要在每一帧都运行样式计算布局和绘制的操作

优化方式

我们知道当前页面以没60帧的刷新频率刷新时才不会让用户感觉到卡顿,如果在运行动画还有大量js任务需要执行,因为布局,绘制和js执行都是在主线程运行的,当在一帧的时间内布局和绘制结束后,还有剩余时间js就会拿到主线程权,如果js执行时间过长, 就会导致在下一帧开始时js没有及时归还主线程,导致下一帧动画没有按时渲染,就会出现页面卡顿

优化方案一

requestAnimationFrame, 它会在每一帧被调用,通过回调API的回调,可以把JS运行任务分成一些更小的任务快,在每一帧时间用完前暂停js执行归还主线程,这样的话在下一帧开始时,主线程就可以按时执行布局和绘制

优化方案二

栅格化的整个流程不占用主线程, 只在合成线程和栅格线程中运行,这就意味着它无需和js抢占线程, 如果反复进行绘制和重排可能会导致掉帧,这是因为有可能js执行阻塞了主线程, 而css中有个动画属性transform,通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格线程中的,所以不会受到主线程中js执行的影响.更重要的是要听过transform实现的动画由于不需要经过布局绘制样式计算等操作,所以节省了很多运算时间

避免重绘重排具体方案

CSS

  • 使用transform代替top等位移;
  • 使用visibility代替display:none;
  • 避免使用table布局
  • 尽可能在DOM树的最末端改变class
  • 避免设置多层内联样式,尽可能层级平铺
  • 将动画效果应用到position属性为absolute或fixed的元素上
  • 避免使用css表达式
  • 将频繁重绘或者回流的节点设置为图层,比如video,iframe
  • css3硬件加速(GPU加速),可以是transform: translateZ(0)、opacity、filters、will-change、Will-change提前告诉浏览器元素会发生什么变化

JS

  • 避免频繁操作样式,合并操作
  • 避免频繁操作DOM,合并操作
  • 防抖截流控制频率
  • 避免频繁读取会引发回流/重绘的属性,比如上面的C、O、S、属性
  • 对具有复杂动画的元素使用绝对定位

实例

1
2
3
4
5
6
7
8
9
div {
height: 100px;
transition: height 1s linear;
}

div:hover {
height: 200px;
}

如果代码变成了

1
2
3
4
5
6
7
8
div {
transform: scale(0.5);
transition: transform 1s linear;
}

div:hover {
transform: scale(1.0);
}

也就是 Main Thread 不用重排, 不用重绘, Draw 也不是他完成的,

重绘是以合成层为单位的

合成层提升前后的Paint步骤

一帧发生了什么

浏览器渲染图

  1. Vsync 接收到Vsync信号
  2. Input event handlers之前Compositor Thread接收到的用户UI交互输入在这一刻会被传入给主线程,触发相关event的回调
  3. requestAnimationFrame : 图中的红线的意思是你可能会在JS里Force Layout,也就是我们说的访问了scrollWidth、clientHeight、ComputedStyle等触发了强制重排,导致Recalc Styles和Layout前移到代码执行过程当中。
  4. parse HTML : 如果有DOM变动,那么会有解析DOM的这一过程。
  5. Recalculate Styles: 如果你在JS执行过程中修改了样式或者改动了DOM,那么便会执行这一步,重新计算指定元素及其子元素的样式。
  6. Layout: 重排reflow。如果有涉及元素位置信息的DOM改动或者样式改动,那么浏览器会重新计算所有元素的位置、尺寸信息
  7. update layer tree : 更新Render Layer的层叠排序关系
  8. Paint : 其实Paint有两步,第一步是记录要执行哪些绘画调用,第二步才是执行这些绘画调用。第一步只是把所需要进行的操作记录序列化进一个叫做SkPicture的数据结构里
  9. Composite : 主线程里的这一步会计算出每个Graphics Layers的合成时所需要的data,包括位移(Translation)、缩放(Scale)、旋转(Rotation)、Alpha 混合等操作的参数,并把这些内容传给Compositor Thread
  10. Raster Scheduled and Rasterize: 第8步生成的SkPicture records在这个阶段被执行。
  11. commit:
    1. 如果是Software Rasterization,所有tile的光栅化完成后Compositor Thread会commit通知GPU Thread,于是所有的tile的位图都会作为纹理都会被GPU Thread上传到GPU里
    2. 如果是使用GPU 的Hardware Rasterization,那么此时纹理都已经在GPU中。接下来,GPU Thread会调用平台对应的3D API(windows下是D3D,其他平台都是GL),把所有纹理绘制到最终的一个位图里,从而完成纹理的合并。
      同时,非常关键的一点:在纹理的合并时,借助于3D API的相关合成参数,可以在合并前对纹理transformations(也就是之前提到的位移、旋转、缩放、alpha通道改变等等操作),先变形再合并。合并完成之后就可以将内容呈现到屏幕上了。

      浏览器中的进程

Renderer Process

浏览器一个标签页的周边容器

  1. Compositor Thread
    负责接收浏览器传来的垂直同步信号,也负责接收从OS传来的用户交互, 比如滚动、输入、点击、鼠标移动等等,从而唤起 Main Thread 执行相关操作

  2. Main Thread
    某段 JS的执行, Recalculate Style, Update Layer tree, Paint, Composite layers 等等

  3. Compositor Tile Worker
    专门处理 til的Rasterization(光栅化)

GPU process

为浏览器所有的标签页和周边进程提供服务的单个进程

参考文档

https://github.com/hushicai/hushicai.github.io/issues/5
https://juejin.cn/post/6844903506059477000
http://dev.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome

渲染流程

浏览器渲染流程详细版

DOM树构建

渲染器进程接受到的数据也就是HTML.渲染器进程的核心任务就是把html、css、js、image等资源渲染成用户可以交互的web页面.DOM也就是文档对象模型,是浏览器对页面在其内部的表示形式,是web开发程序员可以通过JS与交互的数据结构和API.html首先通过tokeniser标记化,通过词法分析将输入的html内容解析成多个标记,根据识别后的标记进行dom树构建,在DOM树构建过程中会创建document对象,然后以document的为根节点的DOM树,不断的进行修改,向其中添加元素

渲染阻塞

html代码中往往会引入一些额外的资源,比如图片、CSS、JS脚本等, 图片和CSS这些资源需要通过网络下载或从缓存中直接加载,这些资源不会阻塞html的解析,因为它们不会影响DOM树的生成,但当HTML解析过程中遇到script标签,就会停止html解析流程,转而去加载过程中遇到的script标签,就会停止html解析流程,转而去加载解析并且执行js.这是因为浏览器并不直到js执行是否会改变当前页面的HTML结构, 如果js代码里面用到了docuemnt.write方法来修改html,之前的和html解析就没有任何关系了,这也就是为什么我们一直说要把script标签要放到合适的位置,或是使用async或defer属性来异步加载执行js

Layout Tree

在html解析完成后,我们就会获得一个DOM Tree, 但是我们还不知道DOM Tree上的每个节点应该长什么样子,主线程需要解析css,并确定每个节点的计算样式,及时你没有提供自定义的css样式, 浏览器会有自己默认的样式表,比如h2的字体要比h3的字体大.在直到DOM结构和每个节点的样式后,我们接下来需要知道每个节点需要坊在页面的哪个位置上,也就是节点的坐标以及该节点需要占用多大的区域,这个阶段被成为layout布局,主线程通过遍历dom和计算好的样式来生成Layout Tree, Layout Tree上的每个节点都记录了x,y坐标和边框尺寸.这需要注意的是DOM Tree和Layout Tree 并不是 — 对应的,设置了display:none的节点不会出现在Layout Tree中,而在before伪类中添加content值的元素content中的内容会出现在LayoutTree中,不会出现在DOM树中,这是因为DOM是通过HTML解析获得的,并不关系样式,而LayoutTree是根DOM和计算好的样式来生成,Layout Tree和最后显示在屏幕上的节点是对应的

绘制(Paint)

现在我们已经知道了元素的大小形状和位置,但还不知道什么样的顺序绘制(paint)这个节点, 例如 z-index 这个属性会影响即诶单绘制的层次关系,如果按照dom的层次结构来绘制页面会导致错误渲染, 所以为了保证在屏幕上展示正确的层级,主线程遍历Layout Tree创建一个绘制记录表(Paint Record),该表记录了绘制顺序,这个阶段配称为绘制(Paint)

栅格化

现在知道了文档的绘制顺序,终于到了该把这些信息转换称像素点显示在屏幕上了,这个行为被称为栅格化(Rastering) chrome最早使用了一种很简单的方式,只栅格化用户可视区域的内容,当用户滚动页面时,再栅格化更多的内容来填充确缺失的部分,这种方式带来的问题就是会导致展示延迟,现在chrome进行了优化升级,使用了更为复杂的栅格化流程叫做合成(compositing), 合成是一种将页面各个部分分成多个图层,分别对其进行栅格化,并在合成线程(Composition Thread) 中单独进行合成页面, 简单来说就是页面所有的元素按照某种规则进行分图层,并把图层都栅格化好了,然后只需要把可是区域的内容组合成一帧展示给用户即可

Layer Tree

主线程遍历Layout Tree 生成 layer Tree ,当 layer Tree 生成完毕和绘制循序确定后, 主线程会将这些信息传递给合成器线程,合成器线程将每个图层栅格化,由于一层可能想页面的整个长度一样大,因此合成器线程将它们切分成许多图块(tiles), 然后将每个图块发送给栅格化线程(Raster Thread) , 栅格化线程栅格化每个图块,并将他们存储在CPU内存中,当图块栅格化完成后,合成器线程将收集成为draw quads 的图块信息, 这些信息里记录了图块字段内存中为位置和页面的那个位置绘制图块的信息, 根据这些悉尼下合成线程生成一个合成器帧(Compositor Frame) 然后合成Frame(帧)通过IPC传递给浏览器进程,接着浏览器进程将合成帧传送到GPU, 然后GPU渲染展示到屏幕上

当页面发生变化时,比如滚动了当前页面,都会生成一个新的合成器帧,新的合成器再传给GPU, 然后再次渲染到屏幕

浏览器渲染流程简要版本

  1. html解析生成DOM树
  2. 遇到css时,css解析器将计算并生成cssDOM
  3. 将DOM树和cssDOM树合成渲染树,并计算元素布局信息
  4. 将渲染树生成合成树
  5. 将渲染主线程生成渲染绘制指令列表提交给合成器线程
  6. 合成器线程利用栅格化生成位图,此时会用GPU进程来进行加速
  7. 提交给浏览器主进程进行页面展示