最近在极客时间学习
<浏览器工作原理>一课的一些笔记
开篇词的学习
浏览器进化的三个阶段
- 应用程序化
 - web应用移动化
 - web操作系统化
 
了解浏览器工作原理的两大优势
- 准确评估项目的可行性(需多语言基础,及后端知识)
 - 更高纬度审视页面,如:
- 用户请求首屏加载慢
- ssr技术 , 如Airbnb主
 - 缓存(cache-control) , 优化接口请求次数 , 打包文件体积优化(gzip)
 
 - 操作dom某个功能元素,如button反应迟钝
- 优化该元素功能函数的内部逻辑(减少console.log()、优化冗余逻辑)
 - 审视当前页面的性能消耗 , 如减少监听器 或 通过eventhub实现发布订阅等
 
 - web中动画没有60帧
- ??? 没遇到过做动画的情况
 - 2020-08-15 补充 : 通过设置 CSS 
will-change属性,使元素单独分层,优化动画。 
 
 - 用户请求首屏加载慢
 
首屏加载问题
- DNS, HTTP解析
 - DOM 解析
 - CSS 阻塞
 - JS 阻塞
 
以上任意一步产生问题,均将造成加载延迟
宏观视角下的浏览器的学习
第一课 (打开chorme有4个进程)
打开浏览器,如
chorme会有4个进程
进程和线程的四大特点 :
- 进程 中任意线程出错 , 导致整个 进程 崩溃
 线程之间共享进程中的数据- 当一个进程关闭之后,操作系统会回收进程所占用的内存
 - 进程之间的内容相互隔离
 
单进程浏览器缺点:
- 不稳定
- 一个插件会引起整个浏览器崩溃
 - 复杂的JavaScript代码也会引起浏览器崩溃
 
 - 不流畅
- 如代码中有无限循环的判断条件,单进程会独占所有内存来执行
 
 - 不安全
- 插件可以通过 c/c++来编写,即 可以访问操作系统人以资源
 - 页面脚本则可通过浏览器漏洞获取系统权限(盗号)
 
 
- 不稳定
 多线程浏览器解决以上问题
- 不稳定
- 进程相互隔离,不会导致浏览器崩溃
 
 - 不流畅
- 即使 js文件 渲染阻塞也仅仅影响 
当前页面,同时内存泄露也只需关闭当前页面便能垃圾回收 
 - 即使 js文件 渲染阻塞也仅仅影响 
 - 不安全 
- 多进程有额外的安全沙箱, chorme 吧插件进程所在安全沙箱内(safe sandbox)
 
 
- 不稳定
 最新浏览器具备5个进程
- 浏览器主进程
- 界面显示,用户交互,子进程管理,存储等
 
 - GPU进程
- 实现 3D CSS 效果
 
 - 网络进程
- 负责网络资源加载
 
 - 渲染进程
- HTML,CSS,JS文件的渲染
 
 - 插件进程
- 运行插件
 
 
打开浏览器至少有以上4个进程(除插件)
- 浏览器主进程
 
第二课 (TCP)
FP(first paint):指从页面加载到首次开始绘制的时长.
首先要确定一个观点, 互联网中文件传输是通过数据包来传输的.
一个数据包从
主机A到主机B的传输过程:主机A的上层传递数据包给主机A网络层主机A的网络层添加IP头并组成新的IP数据包传到底层主机A底层通过物理传输(链路层)给目标主机B主机B网络层接受并开始解析传递过来的数据包(拆开IP头)并把拆下IP头的数据包传递给主机B的上层主机B的上层接受网络层传过来的数据包
UDP协议: 用户数据包协议(User Datagram Protocol)
- UDP不保证数据可靠性,传输速率快
 - 此时需在基于添加
IP头的 网络层之上的传输层添加UDP协议 - 一个数据包从
主机A到主机B的传输过程(包含UDP协议):主机A的上层传递数据包给主机A传输层主机A的传输层添加UDP头并组成新的数据包传到 网络层主机A的网络层添加IP头并组成新的IP数据包传到底层主机A底层通过物理传输(链路层)给目标主机B主机B网络层 接受并开始解析 传递过来的数据包(拆开IP头)并把拆下IP头的数据包传递给主机B的 传输层主机B网络层解析完毕后的数据包传递给主机B的 传输层,并开始解析(拆开UDP头),并把拆下后的数据包传递给主机B的上层主机B收到来自主机B传输层的数据包
 - UDP协议的缺点:
- 数据包在传输过程中容易丢失
 - 大文件拆分来传递,UDP并不能组合这些数据包
 
 
TCP:把数据完整地送达应用程序
- TCP协议解决UDP协议的两个问题:
- 对于数据包丢失的情况,TCP 提供重传机制
 - TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件
 
 - 数据传输流程同UDP协议,唯一不同的是协议由UDP头变更为TCP头
 - 完整的TCP链接又包含3个步骤
三次握手,建立链接(以下以客户C(client)端和服务器S(sever)来举例)- C->S 传递一个数据包,并对S说:我能建立链接了吗 (第一次)
 - S->C S收到数据包 , 确认过后 , 对C说 : 可以建立链接了 (第二次)
 - C->S C收到S的数据包 , 确认可以连接了 , 此时三次握手完毕 (第三次)
 
- 传输阶段 开始进行数据传输
 四次挥手,断开链接- S->C 传输完所有数据后,对C说,我数据传输完毕了。
 - C->S 接受后对 S 说,哦,我知道了。
 - C->S 再次发送消息,我也传输完了。
 - S->C 接受后,哦,我知道了,此时完成四次挥手。
 
 
- TCP协议解决UDP协议的两个问题:
 总结
- 数据传输通过数据包链接,数据包在传输过程中易丢失和出错
 - IP 负责把数据包送达目的主机
 - UDP 负责把数据包送达具体应用
 - TCP 保证了数据完整地传输,它的连接可分为三个阶段:建立连接、传输数据和断开连接
 
第三课 (构建HTTP请求)
主要是关于浏览器构建HTTP请求的过程
HTTP是 建立在 TCP 连接基础 之上的, 一种允许浏览器向服务器获取资源的协议,是 Web 的基础
浏览器端发起 HTTP 请求流程:
- 构建请求:一般是 
GET / index.html HTTP 1.1 - 查找缓存(解决两个问题)
- 缓解服务器压力,提升性能
 - 对网站来说,缓存是实现快速资源加载的重要部分
 
 - 准备IP地址和端口(DNS查询)
 - 等待TCP队列(TCP队列不超过6个)
 - 建立TCP连接(三次握手建立连接,传输数据,四次挥手断开连接)
 - 发送HTTP请求
 
- 构建请求:一般是 
 服务器端处理 HTTP 请求流程 :
- 返回请求
- 带上HTTP报文 和 HTTP 实体 返回 响应
 - 响应包括响应行 响应头 响应体
 - 通过状态码来查询响应的状态
 
 - 断开连接(如果加入Connection:Keep-Alive,TCP便不会断开连接)
 - 重定向
 
- 返回请求
 为什么很多站点第二次打开速度会很快?
- 因为缓存的机制
 - 浏览器构建HTTP请求,从服务器得到响应(添加了cache-control,此时便有缓存)
 - 第二次进入相同站点,服务器收到HTTP请求会先查询是否有cache-control,如果未过期,则直接跳过该部分资源的请求
 - 如果缓存过期,则请求该缓存部分最新数据,再次进行缓存
 
登录状态是如何保持的?
- cookie 通过 服务器 响应 添加 
Set-Cookie进行设置的 - 用户登录后 发送请求 携带上 
Cookie - 服务器收到 请求后对比 cookie 检验是否过期,未过期则保持登录状态
 
- cookie 通过 服务器 响应 添加 
 
第四课(为4-6的总结 页面从导航到展示的过程)
只解决一个问题,从输入URL到页面展示,这中间发生了什么?
简易流程
- 浏览器进程收到用户输入的URL请求,浏览器进程将URL转发给网络进程
 - 网络进程发起真正的URL请求
 - 网络进程收到响应头,开始解析响应头数据,并将数据转发给浏览器进程
 - 浏览器进程收到网络进程响应头数据后,发送
提交导航消息给渲染进程 - 渲染进程接到
提交导航消息后 ,开始准备接受HTML数据,接受方式是与网络进程建立连接通道 - 最后渲染进程向浏览器进程
确认提交 - 浏览器进程收到渲染进程的
提交文档消息后,开始移除之前的旧文档,更新浏览器进程中的页面状态 - 小结:用户发出的URL请求到页面开始解析的过程叫导航
 
详细流程
- 用户输入(判断输入关键字还是URL)
- 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
 - 如果是链接,浏览器加上HTTP协议,并传递给网络层
 
 - URL请求过程
- 查找缓存
- 有缓存: 进入网络进程解析响应流程
 - 无缓存: 
- 进行DNS解析, 获取IP地址和端口
 - 利用IP地址和服务器建立TCP连接
 - 构建请求头
 - 发送请求头
 - 进入网络进程解析响应流程
 
 
 
 - 查找缓存
 - 网络进程解析响应过程:
- 1开头, 表明连接建立 , 还在处理中
 - 2开头, 表明响应成功 , 走到第 4 步
 - 3开头, 表示重定向 , 重新由步骤 2 开始 , 向重定向的地址发送请求
 - 4开头, 表示客户端错误 , 返回错误信息
 - 5开头, 表示服务器错误 , 返回错误信息
 
 - 渲染进程接收
提交导航- 将HTML文件转换成浏览器看得懂的语言, 生成DOM树
 - 将CSS文件转换成浏览器看得懂的语言 
stylesheet, 并计算DOM树中各个节点的CSS属性 - 渲染进程根据DOM树中各个节点 , 生成对应的 
布局树 - 对布局树进行分层 , 并生成 
分层树z-index - 为每个图层生成绘制列表 , 并提交给 
合成进程 合成进程将图层分成土块,并在光栅化线程池中将图块转换成位图- 合成线程发送绘制图块命令 
DrawQuad给浏览器进程 - 浏览器进程根据 
DrawQuad消息生成页面, 并显示到显示器上 
 
- 用户输入(判断输入关键字还是URL)
 以上就是浏览器输入URL到页面展示过程的完整过程
总结
- 优化首屏加载速度可以从4个方面入手
- JS文件阻塞
 - CSS文件阻塞
 - HTML文件阻塞
 - DNS, HTTP解析
 
 - 浏览器渲染进程中,冗余的css会造成浏览器的额外开销
- 重排: 更新元素几何属性, 例如: 高度, 宽度 , 开销最大
 - 重绘: 更新元素的绘制属性, 例如: 背景色 , 开销其次
 - 直接合成: 如transition, 开销最小
 - 减少重排,重绘的解决办法
- 用class替代style
 - 避免table布局
 - 批量操作DOM,如框架
 - 禁用浏览器的Debounce resize 事件
 - 对DOM属性进行读写分离
 - will-change(上下层叠属性):transform 优化
 
 
 
- 优化首屏加载速度可以从4个方面入手
 
浏览器中JavaScript执行机制
第一课 (变量提升)
需要搞懂js 的执行上下文
以
var a = 1为例, 到输出结果的过程- 对语句进行词法分析, 结果为 
var , a , = , 2 - 进行语法分析, 形成抽象语法树, 其根(root)为 var 的顶级节点, 叶子节点为 a 和 2
 - 代码生成, 创建一个 a 的变量(LHS), 并将 2 赋值给 a (RHS), 过程为 , 
var a和a = 2 
- 对语句进行词法分析, 结果为 
 变量提升
- 上述过程清晰阐述了语句 
var a = 1的过程, 在期间,使用var来声明变量, 会使得var a语句提升到 全局执行上下文的最前面, 再对其进行赋值操作. - 这就是为什么 console.log(a) 在前 var a = 2 在后 ,却能打出 a 的值为2的分析过程
 - 如果在此过程中声明函数
function a(){}, a 会被 function 覆盖. 
- 上述过程清晰阐述了语句 
 
第二课 (调用栈)
总得来说, 执行函数, 是先将函数压入调用栈, 执行完毕后, 再弹出调用栈的操作
栈的特点是后进先出, js的调用栈称为执行上下文栈
1  | var a = 2  | 
- 考虑上述代码 , 其执行顺序为 :
- 从全局执行上下文中,取出 add 函数代码。
 - 对 add 函数的这段代码进行编译,并创建该函数的执行上下文和可执行代码。
 - 执行代码,输出结果。
 
 - 抽象对js函数执行的理解 , 大致步骤如下 :
- 创建全局上下文,并将其压入栈底。
 - 第二步是调用 add 函数。
 - 如果 add 函数内部还有函数 , 将 add 内部执行函数 压入 调用栈
 - 当 
add 内部函数执行完毕 , 返回结果 , 并弹出栈 , 开始执行 add 函数 - 当 add 函数 执行完毕 , 返回值
 
 
第三课 (作用域和闭包)
作用域是指 变量与函数的可访问范围
ES6之前 , 作用域分 全局作用域 和 函数作用域
ES6之后 , 出现 块级作用域
- 全局作用域 : 对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。用 
var声明的变量, 存储在变量环境中 - 函数作用域 : 函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
- 闭包 :
 
 - 块级作用域 : 比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域 , 块级作用域内的变量 仅在块级作用域内有效。
- 在块级作用域内使用 
let和const声明变量 , 会形成 暂死区 , 此时得不到变量的值,但是词法环境中却存在该变量 
 - 在块级作用域内使用 
 - 作用域链 : 当前执行函数, 如果引用了本身没有的变量 , 会从本层的外层寻找该变量 , 考虑函数执行上下文 , 并分析 outer 为 上一个函数的作用域 , 还是 全局作用域.
 
第四课 (this)
关键字
this不会凭空出现 , 分析其 执行上下文 , 就能得出this的指向
1  | let obj = {  | 
- 绑定 
this的 三种形式 :- 显示绑定 : 
- obj.getName.call(obj)
 - obj.getName.apply(obj)
 - obj.getName.bind(this)()
- .bind()操作只返回一个函数,需要自己手动调用
 - .call()和.apply()不需要手动调用,会自动调用,仅仅是传参不同
 
 
 - 隐式绑定 :
- x() === window.x.call(window) , 结果为 window 和 空
 
 - new 操作的 this 指向 new 出来的那个对象 , new 的操作如下
- 新建一个临时对象 let newTemp = {}
 - 调用 createObj.call(newTemp)
 - 执行 createObj 函数
 - 返回这个临时对象
 
 - 箭头函数中的 this 与执行上下文中的 this 相同
 
 - 显示绑定 : 
 
V8工作原理
第一课(栈和堆)
JavaScript 的 7 种基本类型分别是
Number,String,Boolean,BigInt,Symbol,Null和Undefined.
JavaScript 的 引用类型只有一种 Object
- js存储方式
- 基本类型 存储于 
栈内存中, 栈内存仅仅只存储引用类型的地址 - 引用类型 存储于 
堆内存中, 堆中的数据是通过引用和变量关联起来的 
 - 基本类型 存储于 
 
第二课(垃圾回收)
有些数据被使用之后,可能就不再需要了,我们把这种数据称为垃圾数据。
如果这些垃圾数据一直保存在内存中,那么内存会越用越多,
所以我们需要对这些垃圾数据进行回收,以释放有限的内存空间。
- 不同语言的垃圾回收政策
- 手动回收
- 手动使变量的值为null
 
 - 自动回收
- JS执行过后的变量垃圾也分为两种
- 新生代的垃圾
- 副垃圾回收器,主要负责新生代的垃圾回收。
 
 - 老生代的垃圾
- 主垃圾回收器,主要负责老生代的垃圾回收。
 
 
 - 新生代的垃圾
 
 - JS执行过后的变量垃圾也分为两种
 
 - 手动回收
 - 垃圾不回收造成的问题
- 内存泄漏
 - 直观的感受就是页面越来越卡
 
 - 避免以上的原因
- 尽量少使用闭包, 闭包会使函数的引用变量一直存放在内存中, 长时间占用内存
 - 确定不使用的临时变量时使其值为 null
 
 
第三课(V8如何执行一段JS代码)
需要理解的几个重要概念
编译器(Compiler)、解释器(Interpreter)、抽象语法树(AST)、
字节码(Bytecode)、即时编译器(JIT)
- 在编译器语言编译过程中
- 编译器对源代码进行词法分析和语法分析, 生成抽象语法树
 - 优化代码
 - 最后生成处理器能理解的代码
- 如果编译成功,将会生成一个可执行的文件。
 - 如果编译过程发生了语法或者其他的错误,那么编译器就会抛出异常,最后的二进制文件也不会生成成功。
 
 
 - 在解释型语言的解释过程中
- 同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST)
 - 不过它会再基于抽象语法树生成字节码
 - 最后再根据字节码来执行程序、输出结果。
 
 - 抽象语法树
- 是源代码语法结构的一种抽象表示
 
 - 字节码
- 字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。
 
 - 即时编译器
- 字节码配合解释器和编译器是最近一段时间很火的技术,比如 Java 和 Python 的虚拟机也都是基于这种技术实现的,我们把这种技术称为即时编译(JIT)
 - 具体到 V8,就是指解释器 Ignition 在解释执行字节码的同时,收集代码信息,当它发现某一部分代码变热了之后,TurboFan 编译器便闪亮登场,把热点的字节码转换为机器码,并把转换后的机器码保存起来,以备下次使用。
 
 - JS在执行一段代码的过程
- 生成抽象语法树(AST)和执行上下文
- 第一阶段是 
分词, 如 : var a = 1, 将被分为var,a,=,1 - 第二阶段是 
解析, 又称语法分析, 其作用是将上一步生成的 token 数据,根据语法规则转为 AST。- 如果源码符合语法规则,这一步就会顺利完成。
 - 但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。
 
 - 生成字节码
 - 执行代码
 
 - 第一阶段是 
 
 - 生成抽象语法树(AST)和执行上下文
 - 综上所述, 包括之前的知识, 对JS性能优化的总结
- 提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程,这样可以使得页面快速响应交互
 - 避免大的内联脚本,因为在解析 HTML 的过程中,解析和编译也会占用主线程
 - 减少 JavaScript 文件的容量,因为更小的文件会提升下载速度,并且占用更低的内存
 
 - 为什么V8代码执行时间越久,执行效率越高?
- 即时编译(JIT)技术的存在
 - 解释器执行字节码的过程中,如果发现有热点代码(HotSpot),
 - 那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,
 - 然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,
 - 这样就省去了省去了字节码“翻译”为机器码的过程大大提升了代码的执行效率。
 
 
浏览器中页面循环系统
消息队列和事件循环
JS处理代码时的顺序
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个”任务队列”(task queue)。
只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
一但”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。
那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步。
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。
这个过程会不断重复,这种机制就被称为事件循环(event loop)机制。
消息队列是一种数据结构,可以存放要执行的任务。它符合队列“先进先出”的特点。
也就是说要添加任务的话,添加到队列的尾部;要取出任务的话,从队列头部去取。
- 总结
- 如果有一些确定好的任务,可以使用一个单线程来按照顺序处理这些任务,这是第一版线程模型
 - 要在线程执行过程中接收并处理新的任务,就需要引入循环语句和事件系统,这是第二版线程模型。
 - 如果要接收其他线程发送过来的任务,就需要引入消息队列,这是第三版线程模型。
 - 如果其他进程想要发送任务给页面主线程,那么先通过 IPC 把任务发送给渲染进程的 IO 线程,IO 线程再把任务发送给页面主线程。
 - 消息队列机制并不是太灵活,为了适应效率和实时性,引入了微任务。
 
 
Web Api
setTimeout 和 XMLHttpRequest (宏任务)
- setTimeout
- 如果当前任务执行时间过久,会影响定时器任务的执行。
 - 如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒。
 - 未激活的页面,setTimeout 执行最小间隔是 1000 毫秒。
 - 延时执行时间有最大值。
 - 使用 setTimeout 设置的回调函数中的 this 不符合直觉。
 
 - XMLHttpRequest(AJAX)
- 创建 XMLHttpRequest 对象。
 - 为 xhr 对象注册回调函数。
 - 配置基础的请求信息。
 - 发起请求。
 
 
1  | setTimeout  | 
微任务和宏任务
微任务可以在实时性和效率之间做一个有效的权衡。
- 宏任务
- 渲染事件(如解析 DOM、计算布局、绘制);
 - 用户交互事件(如鼠标点击、滚动页面、放大缩小等);
 - JavaScript 脚本执行事件;
 - 网络请求完成、文件读写完成事件。
 
 - 微任务 
- 把异步回调函数封装成一个宏任务,添加到消息队列尾部,当循环系统执行到该任务的时候执行回调函数。
 - 执行时机是在主函数执行结束之后、当前宏任务结束之前执行回调函数,这通常都是以微任务形式体现的。
 
 - 结论
- 微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。
 - 微任务的执行时长会影响到当前宏任务的时长。比如一个宏任务在执行过程中,产生了 100 个微任务,执行每个微任务的时间是 10 毫秒,那么执行这 100 个微任务的时间就是 1000 毫秒,也可以说这 100 个微任务让宏任务的执行时间延长了 1000 毫秒。所以你在写代码的时候一定要注意控制微任务的执行时长。
 - 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。
 
 - 每一次事件循环顺序都是宏->微->宏,每次清空当前消息队列时会检查微任务队列中是否已清空。
 
Promise、Async/Await
Promise 解决回调地狱的问题
Async/Await 用同步的写法来编写异步代码
- Promise
.then()的写法消灭嵌套调用.catch()合并多个错误处理.race()多个请求只取最快.all()等待请求全部完成.finally()无论resolve或reject最后都会走到这个分支
 - Async/Await
- 该方法是 generator 生成器,调用 generator.next().value 的语法糖
- 生成器函数是一个带星号函数,而且是可以暂停执行和恢复执行的。
 - 在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JavaScript 引擎将返回关键字后面的内容给外部,并暂停该函数的执行。
 - 外部函数可以通过 next 方法恢复函数的执行。
 
 - Async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。
 
 - 该方法是 generator 生成器,调用 generator.next().value 的语法糖
 
浏览器中的页面
Chrome开发工具
Chrome 开发者工具(简称 DevTools)是一组网页制作和调试的工具,
内嵌于 Google Chrome 浏览器中。
- Elements面板
- 可查看DOM结果、编辑CSS样式,用于测试页面布局和设计页面。
 
 - Console面板
- 可以看成是JavaScript Shell,能执行JavaScript脚本。通过Console在页面中与JavaScript对象交互。
 
 - Network面板
- 展示页面中所有请求内容列表,能查看每项请求的请求行、请求头、请求体、时间线以及网络请求瀑布图等信息。
 - 可根据网络请求来观察HTTP 1.1 下 当前浏览器最多支持 6 个 TCP 连接。
- 把站点升级到 HTTP 2 , 可突破上述问题限制,因为 HTTP 2 的多路由复用机制。
 
 
 - Source面板
- 查看Web应用加载的所有文件
 - 编辑CSS和JavaScript文件内容
 - 将打乱的CSS文件或者JavaScript文件格式化
 - 支持JavaScript的调试功能
 - 设置工作区,将更改的文件保存到本地文件夹中
 
 - Performance面板
- 记录和查看Web应用生命周期内的各种事件,并用来分析在执行过程中一些影响性能的要点。
 
 - Memory面板
- 用来查看运行过程中JavaScript占用堆内存情况,追踪是否存在内存泄露的情况等
 
 - Application面板
- 查看Web应用的数据存储情况
- PWA的基础数据
 - IndexedDB
 - Web SQL
 - 本地和会话存储
 - Cookie
 - 应用程序缓存
 - 图像
 - 字体和样式表
 
 
 - 查看Web应用的数据存储情况
 - Security面板
- 显示当前页面的一些基础安全信息
 
 
JavaScript是如何影响DOM树创建的
从网络传给渲染引擎的 HTML 文件字节流是无法直接被渲染引擎理解的,
所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是 DOM。
DOM 提供了对 HTML 文档结构化的表述。
- 在渲染引擎中,DOM 有三个层面的作用。
- 从页面的视角来看,DOM 是生成页面的基础数据结构。
 - 从 JavaScript 脚本视角来看,DOM 提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容。
 - 从安全视角来看,DOM 是一道安全防护线,一些不安全的内容在 DOM 解析阶段就被拒之门外了。
 
 - DOM 树如何生成
- 首先确认一个观念,HTML 解析器并不是等整个文档加载完成之后再解析的,而是网络进程加载了多少数据,HTML 解析器便解析多少数据。
 - 网络进程接受到响应头之后,会根据响应头中的 
content-type字段判断文件类型,如果值为text/html,浏览器会判断这是一个HTML类型,然后为该请求选择或创建一个渲染进程。 - 渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,解析器就将收到的字节流解析为DOM。
 
 - DOM形成的详细过程
- 第一个阶段,通过分词器将字节流转换为 Token。
- 如果压入到栈中的是 StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
 - 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
 - 如果分词器解析出来的是 EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。
 
 - 至于后续的第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。
 
 - 第一个阶段,通过分词器将字节流转换为 Token。
 - JavaScript 是如何影响 DOM 生成的
- 内联 
<script>console.log(1)</script>会阻塞DOM树的形成。 - 标签内插入下载的JS文件 
<script type="text/javascript" src='foo.js'></script> 
 - 内联 
 
CSS如何影响首次加载时的白屏时间
当渲染进程接收 HTML 文件字节流时,会先开启一个预解析线程,
如果遇到 JavaScript 文件或者 CSS 文件,那么预解析线程会提前下载这些数据。
- CSSOM 的作用
- 提供给 JavaScript 操作样式表的能力。
 - 为布局树的合成提供基础的样式信息。
 
 - 影响页面展示的因素
- 主要原因就是渲染流水线影响到了首次页面展示的速度。
 - 视觉上经历的3个阶段
- 等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容。
 - 提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,并等待 CSS 文件和 JavaScript 文件的加载完成,生成 CSSOM 和 DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染。
 - 等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。
 
 - 优化策略
- 通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了。
 - 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件。
 - 还可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 sync 或者 defer。
 - 对于大的 CSS 文件,可以通过媒体查询属性,将其拆分为多个不同用途的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。
 
 
 
为什么CSS动画比JavaScript动高效?
因为
will-change属性的存在,
使得浏览器在解析 CSS 文件时会具有该属性的元素提前分层,
后续仅仅只在渲染主进程内的合成线程进行合成,该方法是效率最高的,
JavaScript修改元素样式,可能会引起 重排或重绘 皆是效率最低的。
- 显示器如何显示图像
- 前缓冲区 : 每秒固定读取 60 次前缓冲区中的图像,并将读取的图像显示到显示器上。
 - 后缓冲区 : 显卡的职责就是合成新的图像,并将图像保存到后缓冲区中。
 
 - 帧和帧率
- 帧 : 一张图片理解为帧。
 - 帧率 : 1s 内更新帧数的频率,如,1s内更新60帧,帧率为60Hz(60FPS)。
 
 - 如何生成一帧图像,通常渲染路径越长,生成图像花费的时间就越多。
- 重排 : 需要重新根据 CSSOM 和 DOM 来计算布局树,这样生成一幅图片时,会让整个渲染流水线的每个阶段都执行一遍,如果布局复杂的话,就很难保证渲染的效率了。
 - 重绘 : 没有了重新布局的阶段,操作效率稍微高点,但是依然需要重新计算绘制信息,并触发绘制操作之后的一系列操作。
 - 合成 : 操作的路径就显得非常短了,并不需要触发布局和绘制两个阶段,如果采用了 GPU,那么合成的效率会非常高。
- 分层和合成 : 为了提升每帧的渲染效率,Chrome 引入了分层和合成的机制。
- 分层 : 将素材分解为多个图层称为分层,分层体现在生成布局树之后。
 - 合成 : 将这些图层合并到一起称为合成,合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。
 
 - 分块 : 合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块,这样就可以大大加速页面的显示速度。
 
 - 分层和合成 : 为了提升每帧的渲染效率,Chrome 引入了分层和合成的机制。
 - 如何利用分层技术优化代码
will-change属性,通过例子可以观察到,在chrome devtools的 performance面板、layers面板中可以观察到 具有该属性的元素会被提前分层成为单独的一帧图片 从而使得元素不需要重排或重绘,提高了动画合成的效率,但是该操作会占用相对大的内存,根据不同情况下考量何时使用该属性。
 
 
如何系统的优化页面
页面优化即让页面更快的显示和响应。
一个页面有三个阶段:加载阶段、交互阶段、关闭阶段。
加载阶段:请求发出到渲染完整页面,影响因素有网络和JavaScript脚本。
交互阶段:页面加载完成到用户交互,影响因素主要是JavaScript脚本。
关闭阶段:主要是用户发出关闭指令后页面的一些清理操作。
- 加载阶段
- 造成阻塞:JS文件、首次加载的HTML文件、CSS文件等。
 - 不会造成阻塞:图片、视频、音频等资源。
 - 造成阻塞的原因
- 关键资源个数
 - 关键资源大小
 - 请求关键资源需要多少个RTT(round trip time)
 
 - 优化策略
- 减少关键资源个数,使用如内联css和内联javascript
 - 减小关键资源大小,去除css重复代码,javascript文件中注释和重复代码
 - 减少rtt次数,一次rtt最多14kb的数据包传输,减小css和html和js文件体积大小。
 
 
 - 交互阶段:主要考虑重排、重绘
- 减少JS执行时间
- 一次执行的函数分解为多步操作
 - 使用 
web worker进行非dom操作的逻辑处理 
 - 避免强制同步布局
- css计算属性是在单独的任务中进行的
 - 如mwui-vue中处理nav组件的动画使用到了js使css强制计算当前元素的css属性.
 
 - 避免布局抖动
 - 合理利用css合成动画(will-change属性)
 - 避免频繁垃圾回收(尽可能减少闭包的使用)
 
 - 减少JS执行时间
 
虚拟DOM
如果通过JS脚本直接修改DOM的话,会引起如,重排、重绘、合成,甚至因为不当操作,还会引起如,布局抖动和强制同步布局等一系列的操作。所以虚拟DOM,改进了上述的问题。
- 虚拟DOM
- 虚拟dom做了什么
- 将页面变更内容应用到虚拟DOM上,而不是直接修改DOM。
 - 数据变化时,生成新的虚拟DOM,对比新旧虚拟DOM,找出变化的节点。
 - 数据停止变化后,渲染到DOM上,更新页面
 
 
 - 虚拟dom做了什么
 - 几种设计模式
- 双缓存
- 虚拟DOM,类似双缓存中的Buffer(缓存)
 
 - MVC
- Model(数据层)、View(视图层)、Controller(逻辑处理层)
 
 
 - 双缓存
 - react中的具体流程
- Controller 监控 DOM 变化,DOM变化,Controller 通知 Model 更新数据。
 - Model 更新数据后,Controller通知 View ,告知数据发生改变。
 - View 接受更新消息后,生成新的虚拟DOM。
 - 新虚拟DOM生成好后,与旧的虚拟DOM比较(diff),找出变化节点。
 - react此时将变化节点应用到DOM上,触发DOM节点的更新。
 - DOM 节点的变化又会触发后续一系列渲染流水线的变化,从而实现页面的更新。
 
 
浏览器中的网络
HTTP/1
HTTP 是浏览器中最重要且使用最多的协议,是浏览器和服务器之间的通信语言,也是互联网的基石。
- HTTP/0.9
- 请求过程
- 客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。
 - 建立好连接之后,会发送一个 GET 请求行的信息。
 - 服务器接收请求信息之后,读取对应文件,并将数据以ASCII字符流返回给客户端
 - 传输完成后,断开连接。
 
 - 3个特点
- 只有一个请求行,并没有 HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
 - 服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
 - 返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。
 
 
 - 请求过程
 - HTTP/1.0 解决HTTP/0.9不支持多种不同类型的数据处理的问题.
- 首先,浏览器需要知道服务器返回的数据是什么类型,然后浏览器才能根据不同的数据类型做针对性的处理。
 - 其次,服务器会对数据进行压缩后再传输,所以浏览器需要知道服务器压缩的方法。
 - 再次,浏览器告诉服务器它想要什么语言版本的页面。
 - 最后,浏览器需要知道文件的编码类型。
 - 以上问题皆需要通过设置请求头去设置。
 
 - HTTP/1.1 相比较HTTP/1.0的改进。
- 改进持久连接,HTTP/1.1 中增加了持久连接的方法,它的特点是在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。
 - 不成熟的 HTTP 管线化,TCP内只要有一段数据包传输出错,就会造成队头阻塞。
 - 提供虚拟主机的支持
 - 对动态生成的内容提供了完美支持
 - 客户端 Cookie、安全机制
 
 
HTTP/2
__END__