浏览器工作原理学习笔记

323 阅读28分钟

在浏览器输入 url 回车之后发生什么

  1. 浏览检查输入的字符串是够符合 url 格式,不符合则使用搜索引擎拼接 keywords,去搜索.
回车动作响应之前, 页面还会发出 beforeunload 事件,给页面取消导航流程的机会
  1. 如果符合 url 格式,则通过进程间通信(ipc),有浏览器进程将 url 传递给网络进程.
  2. 网络进程拿到 url 后,会查看本地有没有缓存,如果有且没有过期则使用本地缓存.
  3. 如果没有缓存, 则需要去服务器获取资源, 在此之前需要使用 DNS 协议进行域名解析, 解析过程会依次在在浏览器/操作系统/路由器/运营商/域名服务器中查询 IP 地址.
  4. 拿到 IP地址后,则使用 TCP 协议建立网络连接(三次握手).
  5. 连接建立后,浏览器构造 http 协议的请求并携带 cookie 发送给服务器.
http 协议位于应用层,tcp 协议位于传输层,ip 协议位于网络层.三个协议层级从高到低.
  1. 服务器收到请求后构造响应,发送给客户端.
  2. 浏览器的网络进程,解析响应.根据响应行中的状态码,会有不同的行为,例如:返回 301/302 则会使用响应头中的 location 发生重定向;如果返回200 则说明请求成功,开始处理响应体中的内容.
  3. 根据响应头中 content-type,对于不同类型的响应体数据会有不同的处理,例如,如果是application/octet-stream则会让浏览器进程下载资源,如果是 text/html 则会去渲染页面.
  4. 接受数据完成后,浏览器进程会发出提交文档信号给渲染进程
  5. 渲染进程建立和网络进程的通信管道,获取网络进程中获取到的数据.渲染进程会开始渲染工作.
  6. 渲染进程数据接收完成后会发出确认文档信号给浏览器进程.
  7. 浏览器进程收到此信号后,改变导航栏 url 地址/改变前进后退历史/页面改变/安全状态改变.
  8. 一旦页面渲染完毕,浏览器页签的 loading 图标会消失.

浏览器渲染流程

渲染流程

  1. 在接收到网络进程的文档数据后,浏览器渲染进程便开始了.
  2. 首先会将 html 解析成dom 树.
  3. 将 css 解析成 stylesheet
  4. 生成布局树.位置信息
  5. 生成图层树.z 轴信息.
具有层叠上下文的元素和需要裁切的元素会生成图层
  1. 图层绘制.生成渲染指令列表
  2. 格栅化操作.渲染线程将指令列表提交给在合成线程, 完成格栅化.
  3. 格栅化指的是将图块转换成可以显示的位图.图块的概念是将图层继续划分成一个个小块,并优先格栅化视口附近的图块.
  4. 可以使用 GPU 来加速格栅化,如果使用了 GPU 那就会引入 GPU 线程.
  5. 合成和显示.合成线程处理完所有的图块后,会向浏览器进程发出DrawQuad的命令,浏览器收到后会使用合成的位图,利用viz组件在显示器上显示.
理解重排重绘合成.可以优化页面性能.

变量提升

变量提升

此处核心需要理解: js 代码需要经过编译,再到执行的过程

编译阶段:会将变量和函数的声明提前, 注意赋值不是声明,函数声明的优先级高于变量,就是说如果有同名的函数和变量那么会忽略变量的声明,如果有相同的函数名/变量名则已最后一次声明为准.

编译阶段会生成环境变量(执行上下文),和可执行代码的字节码.

文档在被解析成 dom 树的过程, 遇到 js 代码会先执行,停止 html 的解析,遇到外链的 js 则会去下载 js 文件, 阻塞 html 解析,等待 js 文件下载完成并且执行完毕后才会继续解析 html, 如果 js 代码中有对 style 的操作,那么还得等待对应的 css 文件下载完毕.

调用栈

每个函数都会经历编译+执行, 编译期间会创建执行上下文,在执行时遇到新的函数则会继续进行编译创建执行上下文.

变量提升的问题与如何实现块级作用域

  • 变量提升的问题, 与 js 没有块级作用域相关, 因为没有块级作用域,在发生变量提升后,代码会变得具有迷惑性,因为与其他具有块级作用域的语言表现形式非常不同.例如,在 for 循环中的下标变量,在 for 循环之外依然存在.
  • 为了和其他编程语言行为相似, js 引入了块级作用域,
  • 块级作用域是通过关键字,const/let 实现的.

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()

对于上述代码, 执行上下文图解:

1.当执行到 foo 函数时的执行上下文:

  • 所有函数体被 var/let 声明的变量会被提升,存储在变量环境中
  • let 变量会被提升到词法环境中,压入词法环境的栈顶
  • 但是函数内的块级作用域中的 let 不会被提升
var的创建和初始化被提升,赋值不会被提升。
let的创建被提升,初始化和赋值不会被提升。暂时性死区.
function的创建、初始化和赋值均会被提升。

2.进到函数内的块级作用域时: 将块内的 let 变量声明,继续压入词法环境栈顶
3.块级作用域内寻找变量定义的过程
4.块级作用域执行完毕后,响应的词法环境出栈

作用域链和闭包

作用域链: 变量的在不同执行上下文之间的寻找路径.

function bar() {
    console.log(myName)
}
function foo() {
    var myName = "极客邦"
    bar()
}
var myName = "极客时间"
foo()

为什么 bar 函数的 outer 指向全局作用域,而不是调用其的 foo 函数?

因为作用域链的指向不是依据调用关系的, 而是依据词法作用域, 词法作用域是一种静态作用域,是代码写好就确定的,与调用关系无关, 所以此处 foo 和 bar 定义时的上级词法作用域都是全局作用域,所以 outer 指向全局.

this

为什么引入 this?

因为有对象上的函数访问对象上的属性这样的需求, 而 js 的作用域链机制,并不能实现这样的需求.所以引入 this 机制, 注意 this 机制和作用域机制没有什么关系.(全局上下文中的 this 指向 window, 顶级作用域也是 window)

this 在执行上下文中保存, 有三种执行上下对应就有三种 this: 全局/函数/eval

谨记三点:

  1. 默认情况下函数的 this 指向全局 window(非严格模式,严格模式下为 Undefined).
    • 显示使用apply/bind/call 函数可以指定函数的 this
    • 使用 new 构造函数
    • 对象调用成员函数
  2. 嵌套函数中的 this 不会继承外层函数的 this 值。
  3. 箭头函数的执行上下文中的 this,是其调用者的 this.

栈与堆

function foo(){
    var a = "极客时间"
    var b = a
    var c = {name:"极客时间"}
    var d = c
}
foo()

基础类型保存在栈里,引用类型保存在堆里.

引用类型一般数据量会比较大,保存在栈里的话不利于执行上下文的切换, 所以保存在堆上.

闭包在内存中:

垃圾回收机制

  • 栈上的空间在执行上下文退出时, 会被销毁.esp 指针下移, 后生成的执行上下文会直接覆盖就的执行上下文.
  • 对于引用类型的变量, 会先添加在新生区.
  • 新生区负责管理那些生命比较短的变量, 新生区分为大小相同的两部分, 分别是对象区域空闲区域.
  • 对象区域中的变量会在经过遍历调用栈后,标记出哪些是不再使用的对象,哪些是还在使用的对象, 将还在使用的对象,拷贝到空闲区域, 留在对象区域的变量直接清理,并且两个区域名称互换.
  • 因为需要频繁地拷贝对象所以,新生区一般都比较小,太大会影响性能.
  • 并且有些对象生命长,会一直被拷贝,所以又引入老生区.
  • 老生区中的对象是从新生区中过来的, 那些经过几轮拷贝后依然没有释放的对象,会被放在老生区.
  • 老生区空间比较大, 不再区分空闲区域.
  • 老生区中无法内存被清理后会出现不连续的空间, 所以还会进行空间的整理,整理后使之连续.
  • 由于老生区中空间比较大, 所以在集中清理内存时,会比较耗时, 更重要的是清理内存会阻塞 js 脚本的执行(全停顿),所以浏览器会进行频繁地少量的内存清理,不停在 js 执行过程中穿插,使得看起来脚本执行流畅.
function foo(){
    var a = 1
    var b = {name:"极客邦"}
    function showName(){
      var c = "极客时间"
      var d = {name:"极客时间"}
    }
    showName()
}
foo()

编译器和解释器

即时编译(JIT): 目前许多解释性语言都使用即时编译技术.

使用机器码的好处在于一次编译后储存起来,不需要反复编译,但是比较耗内存.而字节码占用空间小,但是需要反复解释执行.所以 v8 目前是两种策略结合,把需要反复执行的代码编译为机器码.

消息队列与事件循环

页面使用单线程的缺点

  • 执行效率与实时性不好.例如,一个临时任务的出现,如果直接插入到队列头部会影响后边任务的执行,效率降低,如果排到队尾,会影响任务执行的实时性.针对此,引入宏任务微任务.
  • 单任务执行时间过长. 这种任务会造成页面卡顿, 对于这种任务可以使用回调,滞后执行.

setTimeout 是如何实现的

除了上面的任务队列,为了实现一些延时任务,浏览器会添加一个延时队列.setTimout 中的延时任务,会被添加在这个队列中.

void ProcessTimerTask(){
  //从delayed_incoming_queue中取出已经到期的定时器任务
  //依次执行这些任务
}

TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
  for(;;){
    //执行消息队列中的任务
    Task task = task_queue.takeTask();
    ProcessTask(task);
    
    //执行延迟队列中的任务
    ProcessDelayTask()

    if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
        break; 
  }
}
  1. 如果执行一个很耗时的任务,会影响延迟消息队列中任务的执行
  2. 存在嵌套带调用时候,系统会设置最短时间间隔为4s(超过5层)
  3. 未激活的页面,setTimeout最小时间间隔为1000ms
  4. 延时执行时间的最大值2147483647,溢出会导致定时器立即执行
  5. setTimeout设置回调函数this会是回调时候对应的this对象,可以使用箭头函数解决

XMLHttpRequest

宏任务与微任务

  • 任务队列中的任务都是宏任务.
  • 宏任务在执行中,经常回插入一些系统级别的宏任务,所以宏任务的执行时间,不能得到保证.

  • 微任务:异步执行的函数,执行时机在当前宏任务主函数执行结束后, 整个宏任务结束之前.
  • 宏任务会关联一个微任务队列.
  • 产生微任务的两种方式:
    • 使用 MutationObserver
    • 使用 Promise
最早的 DOM 变化监听使用的是Mutation Event, 但是这种方式性能极低,每次 DOM 变化就会同步的执行回调函数,严重影响主线程的执行.后来才提出使用 Mutation Observer 的方式:异步执行+微任务.
  • 微任务中可以产生新的微任务, 直到当前宏任务下的微任务列表中微任务执行完毕后, 才会执行下一个宏任务.
  • 执行微任务的时间点成为checkpoint 检查点.

Promise

引入 promise 的动机是为了解决异步的代码风格

如果没有 promise,对于异步的处理会出现回调地狱的情况,并且在嵌套逻辑中需要处理成功或者失败,容易是逻辑变复杂.

  • Promise 是有 v8 引擎实现的
  • Promise 的构造函数中的回调函数,会立即执行
  • resolve/reject 触发微任务, 注意发送请求时,XMLhttpRequest 是一个宏任务,当与 Promise 一起使用时,成功 resolve 触发宏任务,失败 reject 触发微任务.

为什么 Promise 可以解决回调地狱和复杂的嵌套逻辑?

  • 延迟绑定, then 方法中的回调不需要和 promise 构造函数中的方法嵌套.
  • 返回值可以穿透到最外层

async/await

  • async 和 await 搭配使用.
  • async 用于标识一个方法是异步方法, 使得放回的返回值是一个 promise
  • await 用在 async 方法内部, 用于执行一个任务, 并阻塞函数内剩余代码的执行, 知道任务返回.
  • 这两个关键字的实现其实是使用了 promiseyield.更底层其实就微任务和协程的原理.

协程和线程的关系: 协程运行在线程之上,一个线程可以有多个协程,但是同一时间只能有一个协程在执行 的执行过程.

在 js 中协程的切换可以使用关键字 yield 和 next方法, 函数名前加星号表示生成器方法,可以构造出一个生成器.

function* genDemo() {
    console.log("开始执行第一段")
    yield 'generator 2'

    console.log("开始执行第二段")
    yield 'generator 2'

    console.log("开始执行第三段")
    yield 'generator 2'

    console.log("执行结束")
    return 'generator 2'
}

console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4')

js 是如何影响 dom 树的解析

渲染进程中有专门的负责解析 html 文件的解析器, 网络进程接收数据和解析器解析 html是同步进行的,就是一边请求数据,一边解析.

如上图所示, html解析器中有个负责存放 token 的栈, 用来确定当前解析元素的层次关系.

解析 dom 和 js 执行的关系:

  • 如果遇到 script 标签, 其中的 js 代码将会阻塞 dom 树的生成.
  • 如果 js 文件是外联的,则整个文件的下载期间和执行期间都是阻塞的.
  • 浏览器为了优化 js 下载期间对 dom 解析的阻塞, 可以使用 defer 和 async 表示script 标签.
  • defer 标识, js 文件会在 DomContentLoaded 事件之前执行.
  • async 标识,会在 js 文件下载完成后立即执行.
  • 浏览器的另一个优化是,使用预解析线程,提前去加载需要的 css/js 文件.
  • js 中可能会操作样式表, 而js 引擎又不能提前知道是否操作了样式表,所以 css 也会造成 dom 树生成的阻塞.
  • 但是 css 不会直接造成 dom 解析阻塞.

css如何影响首次加载的白屏时间

从发起 URL 请求开始,到首次显示页面的内容,在视觉上经历的三个阶段。

  • 第一个阶段,等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容. 页面导航.
  • 第二个阶段,提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,并等待 CSS 文件和 JavaScript 文件的加载完成,生成 CSSOM 和 DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染。
  • 第三个阶段,等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。

针对第二个阶段的优化:

  • 通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了。
  • 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件。
  • 还可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 sync 或者 defer。
  • 对于大的 CSS 文件,可以通过媒体查询属性,将其拆分为多个不同用途的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。

分层和合成机制

为什么需要分层?

为了提高渲染效率.生成布局树后,浏览器还会进一步生成层树,使得元素能够分层渲染,当某个元素改变的时候,只需要重新渲染元素所在的层,而不需要全部重绘.

分层后,在绘制的时候会生成绘制指令列表,绘制阶段结束, 进入光栅化阶段,光栅化就是将一个个图层生成图片, 然后交给合成线程,在合成线程将各个图层图片合并,GPU 进程可以加速此过程.

由于合成是单独在合成线程中,不占用主线程,所以会出现 css 动画仍在执行,但是主线程卡死的情况.

为什么需要分块?

浏览器视口展示的区域是有限的, 所以合成时直接将所有图层绘制合并,其实还是比较耗时的,所以浏览器会将图层进一步分块,优先绘制视口附近的图块.

由于纹理上传需要从内存到显卡内存, 但是传输过程会有性能瓶颈,浏览器为了能够较快的给用户呈现页面,会降低首次渲染时图片的分辨率,以加快传输,快速显示.

css 中的will-change属性可以事先告诉浏览器元素的哪些属性会发生改变,从而使得浏览器为元素单独生成一个图层,从而使得元素属性在改变时,只改变元素所在的图层.但是这样也会增加内存占用.

页面性能优化

一个页面的三个阶段:

  • 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。
  • 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。
  • 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作

加载阶段优化

可以优化的点:

  • 减少首次加载的资源个数.首次加载的关键资源有 3 个,css/html/js.可以将必要 css/js 直接内联到 html 中,减少资源加载次数.
  • 减小文件的大小.
  • 减少rtt次数.
RTT 就是这里的往返时延。它是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。通常 1 个 HTTP 的数据包在 14KB 左右,所以 1 个 0.1M 的页面就需要拆分成 8 个包来传输了,也就是说需要 8 个 RTT

交互阶段优化点:

  • 减少js 霸占主线程的时间.js 脚本如果执行时间过长,则会严重阻塞主线程,页面交互卡顿.所以应该避免较重的任务在 js 中执行.
  • 如果非要在 js 中执行重任务,可以利用 web workers 执行.但是 web works 无法和 dom 进行交互.
  • 避免强制重新布局.js 中修改了 dom 结构后, 如果不再访问 dom 元素属性,则 dom 重绘会在其他任务中执行,反之则直接再当前任务中执行,这样的话就会使得当前任务阻塞主线程时间变长,其他任务无法及时得到执行.
  • 布局抖动.同上,但是更为严重,在循环中多次强制同步布局.
  • 一些动画效果尽可能用 css 实现.css 不会触发重排重绘,只会触发合成线程重新合成.
  • 避免频繁地垃圾回收.垃圾回收也会占用主线程.

虚拟 dom

React Fiber更新机制/reconciliation算法

  • 类似图像中的双缓冲技术.也就是 batch/commit ,避免零碎的多次去更改 dom,从而触发多次渲染.其实浏览器中也实现了这样类似的机制, js 执行过程中如果没有去强制同步布局(也就是 js 引擎没有让出主线程),那么代码修改的 dom 属性,也是在其他任务中一起去渲染这些更改.
  • diff 机制.dom 更改后,会生成新的虚拟 dom,新的 dom 后旧的虚拟 dom 比较,发现差别后,会只更新有差别的 dom,保证真实 dom 最小改动.

渐进式网页应用

渐进式网页应用

web 应用相对原生应用缺少的能力:

  • 消息推送能力
  • 离线能力
  • 一级入口

service worker

web component

为什么会有 WebComponent?

  • 为了能实现组件化的开发.js 作为编程语言可以实现组件化模块化.
  • css 无法组件化,样式会全局影响
  • dom 全局暴露,访问

一个使用 webcomponent 的实例

<!DOCTYPE html>
<html>
<body>
    <!--
            一:定义模板
            二:定义内部CSS样式
            三:定义JavaScript行为
    -->
    <template id="geekbang-t">
        <style>
            p {
                background-color: brown;
                color: cornsilk
            }


            div {
                width: 200px;
                background-color: bisque;
                border: 3px solid chocolate;
                border-radius: 10px;
            }
        </style>
        <div>
            <p>time.geekbang.org</p>
            <p>time1.geekbang.org</p>
        </div>
        <script>
            function foo() {
                console.log('inner log')
            }
        </script>
    </template>
    <script>
        class GeekBang extends HTMLElement {
            constructor() {
                super()
                //获取组件模板
                const content = document.querySelector('#geekbang-t').content
                //创建影子DOM节点
                const shadowDOM = this.attachShadow({ mode: 'open' })
                //将模板添加到影子DOM上
                shadowDOM.appendChild(content.cloneNode(true))
            }
        }
        customElements.define('geek-bang', GeekBang)
    </script>


    <geek-bang></geek-bang>
    <div>
        <p>time.geekbang.org</p>
        <p>time1.geekbang.org</p>
    </div>
    <geek-bang></geek-bang>
</body>
</html>

其中的重要概念shadow dom

dom 接口不能在全局访问影子 dom 中的元素, 影子 dom 中 css 也不在作用于全局, 起到了隔离的作用.

http 协议的变化

0.9

  • 只有请求行没有请求头,也就没有内容协商机制.
  • 只能传输 html 文件,以 ascii 码返回数据.

1.0

  • 增加了请求头和响应头,可以进行内容协商.
  • 因为有何请求头和响应头,也就可以实现多种类型文件的传输.
  • 引入了数据缓存机制
  • 引入了状态码

1.1

  • 增加长连接功能,不再每次发起 http 请求都需要去建立 tcp 连接, tcp 连接建立后可以发起多起 http 请求,除非浏览器或者服务器明确表示要关闭连接.同一域名下同时可维护 6 个 TCP 连接.
  • 不成熟的 http管线化(pipelining).
    • http 请求必须要等待早先的请求返回后,才能处理下一个,如果之前的请求不返回,就会造成队头阻塞,使后边的请求也不会被发送.
    • 1.1 中增加了管线化处理, 就是批量提交请求给服务器,不需要等待响应返回.
    • 但是服务器必须按照请求发送的顺序来处理,并且按顺序返回, 因为请求和响应没有标识,浏览器无法区分谁是谁, 但是如果服务器对某个请求处理时间过长,还是会造成阻塞的问题. 所以没有根本上解决此问题, chrome 默认是没有开启此功能.
tcp 和 http1.1 均有对头阻塞问题,上述是 http 中对头阻塞, tcp 本身的机制造成了其也有阻塞问题.一个关于 tcp 阻塞的描述:

比如说服务器可能发送3幅不同的图像供Web浏览器显示。为了营造这几幅图像在用户屏幕上并行显示的效果,服务器先发送第一幅图像的一个断片,再发送第二幅图像的一个断片,然后再发送第三幅图像的一个断片;服务器重复这个过程,直到这3幅图像全部成功地发送到浏览器为止。要是第一幅图像的某个断片内容的TCP分节丢失了,客户端将保持已到达的不按序的所有数据,直到丢失的分节重传成功。这样不仅延缓了第一幅图像数据的递送,也延缓了第二幅和第三幅图像数据的递送

  • host 字段,提供虚拟主机的支持,使得同一台物理主机上的多个虚拟主机能够公用同一个 ip 地址.
  • Chunk transfer 机制,对于大小不确定的资源提供支持,以一个空的结束,标识资源传输完成.
  • cookie 机制.

http2.0

  • 核心是使用多路复用来解决 http1.1 中队头阻塞/tcp慢启动/多路 tcp 竞争带宽.

  • 每一个请求都有一个id,用于标识请求.这样就能让浏览器区分请求和响应,为并行请求和决http对头阻塞提供基础。
  • 同一个域名下只使用一个 tcp 连接.这样就能让慢连接少发生,从而增加速度. 自然因为只有一个tcp也就不会去竞争带宽.
  • 多路复用的基础就是增加了一个二进制分帧层
  • tcp本身还是存在队头阻塞问题.

http2中的其他特性:

  • 请求可以设置优先级
  • 服务器可以向页面推送数据.这对改进浏览器首次加载性能非常重要,例如服务器在知道html中引用了css和js文件则可以不需要浏览器请求,就能提前发给浏览器,可以加快页面的加载.
  • 头部压缩.加快传输.

http3:

  • http2同一个域名下至使用一个tcp连接, 但是tcp是面向单连接的协议,如果丢包率上升,会造成队头阻塞,从而降低传输效率.
有测试数据表明,当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好.

  • http3是一个脱胎换骨的协议,去掉了tcp,基于udp造轮子,提出quic协议,实现http2的多路复用.目的在于解决:
    • HTTP2由于建立时三次握手和tls握手造成的rtt延迟.
    • tcp队头阻塞问题.

  • 为什么不基于tcp继续改进,而要造轮子呢?因为中间设备僵化,大量的基础设备都支持tcp,如果tcp进行重大改变,则基础设备都需要改变,推翻重来非常困难.
  • http3的特性:
    • 实现了类似 TCP 的流量控制、传输可靠性的功能
    • 集成了 TLS 加密功能
    • 实现了 HTTP/2 中的多路复用功能.物理上还是单连接但是逻辑上实现多个独立的数据流
    • 实现了快速握手功能

  • 然而http3,仍然有很多挑战.
    • 基础的中间设备和操作系统,对tcp的支持远好于udp
    • 浏览器和服务器都没有对http3有较好的支持

同源策略

为什么需要同源策略? 没有同源策略,web页面访问资源没有限制,容易被恶意js脚本攻击.

同源策略的限制体现在三个方面:

  • DOM.不是一个域名下的dom不能互相访问.
  • 数据.同样,cookie,indexDB等数据不能互相访问.
  • 网络请求.

同源策略是为了限制恶意攻击,同时也就限制了开发的便利性,所以有通过以下浏览器提供的手段来,解除同源策略的限制:

  • csp(Content Security Policy)可以引用第三方资源. CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码.
  • cors(cross origin resource sharing)/跨文档消息机制.可以实现dom和资源的通信共享.

csrf (cross site requset forgery)

xss (cross site script)

安全沙箱

  • 多进程的现代浏览器,为实现渲染进程在沙箱中执行提供可能.
  • 在安全沙箱中,禁止一切渲染进程与操作系统的直接通信,一切都通过浏览器内核代做,例如: 网络请求是否网络进程负责,cookie机一些缓存数据的读取由浏览器进程负责,用户的UI操作也由浏览器转发给渲染进程.
  • 站点隔离.同一站点的页面公用一个渲染进程.如果一个页面中有多个iframe,若来自不同站点,那么就在不同站点中渲染.

网络安全

  • https在http的基础上引入ssl/tls安全层,来改变http明文传输的问题.数据经过安全层时,将数据加密.

  • 对称加密.问题在于随机数还是明文传输.

  • 非对称加密.问题在于浏览器数据的安全得到保障,但是服务器端的数据无法保证安全,因为公钥明文传输,并且公钥可以用来解密服务器发来的数据.

  • 对称非对称混合加密.在传输数据阶段依然使用对称加密,但是对称加密的密钥我们采用非对称加密来传输.由于pre-master加密传输,就保证了后续对称加密传输过程的安全.

  • 添加数字证书.数字证书的目的在于,让服务器能证明自己的身份.核心在于:
    • 引进权威机构 CA.
    • 使用CA的私钥生成证明签名.

渲染进程个数

  • 在同一浏览上下文组并且是同一站点的页面会在同一渲染进程,任一条件不满足则不在同一渲染进程.
  • 如果指定rel=noopener那么新打开的页面就在新的渲染进程中,以使得页面更加独立稳定.

任务调度

  • 按优先级分类的任务队列.
  • 按任务类型划分优先级的任务队列.
  • 根据页面所处阶段的动态划分任务队列优先级.
  • 任务饿死.如果一直有高优先级的任务存在,那么低优先的任务长时间不会被执行,Chrome又引入,在超出指定之间后,必须要去执行一次低优先级的任务. 显示器和浏览器的配合:
  • 显示器以一定频率从显卡的后缓冲中读取要显示的图像, 浏览器将合成的图像提交到前缓冲中,提交后交换前后缓冲区.

  • rAF中回调会在时钟周期的最开始执行.

使用Audits分析性能指标

  • FP(first paint).指首次绘制时间,在index页面返回后,如果不发生重定向,那么渲染线程会绘制一个空白页面,这个空白页面出现的时间,就是FP.
  • FMP 首次有效绘制 (First Meaningfull Paint).计算复杂,并且不准确,现在不常用.
  • 首屏时间 (Speed Index)也是LCP(largest content paint).指全部元素绘制完成.
  • FCP (First Content Paint).指第一个像素绘制出来的时间.
  • CPU 空闲时间 (First CPU Idle),也称为 First Interactive.表示页面最小可以交互的时间.
  • TTI 完全可交互时间 (Time to Interactive).通常满足响应速度在 50 毫秒以内.
  • 最大估计输入延时 (Max Potential First Input Delay).指的是在页面最繁忙的时间,响应用户的时间.

performance 使用

浏览器数字证书校验过程

  • 数字证书申请过程
  • 校验流程:
    • 核对有效期
    • 核对是否被CA吊销
    • 核对签名
  • CA的公钥从哪里获取.在服务器部署的时候,一般会将颁发证书的CA的数字证书也放在服务器上,浏览器请求的时候回同时发送网站自己和给自己颁发证书的CA机构的证书.CA的证书里包含了CA公钥.另一种获取CA的方式是浏览器自己去下载.
  • 如何验证CA机构是合法的.
    • 证书链.CA机构分为根CA和中间CA,根CA数量少,所有中间CA的证书都由根CA颁发, 而中间CA又可以颁发证书给其他中间CA,组成一个树状证书链.
    • 根证书是由WebTrust(美国和加拿大注册会计协会制定的)认证的, 内置在各大操作系统中.