2021-11-19 肝完《浏览器工作原理与实践》,我总结了这些

2021-11-19  本文已影响0人  alanwhy

肝完《浏览器工作原理与实践》,我总结了这些

Chrome 架构:仅仅打开了 1 个页面,为什么有 4 个进程

线程和进程区别:多线程可以并行处理任务,线程不能单独存在,它是由进程来启动和管理的。一个进程是一个程序的运行实例。

线程和进程的关系

  1. 进程中任意一线程执行出错,都会导致整个进程的崩溃。
  2. 线程之间共享进程中的数据。
  3. 当一个进程关闭后,操作系统会回收进程所占用的内存。
  4. 进程之间的内容相互隔离。

单进程浏览器

多进程浏览器

多进程架构:分为 浏览器进程、渲染进程、GPU 进程、网络进程、插件进程。

缺点

面向服务架构:把原来的各种模块重构成独立的服务,每个服务都可以在独立的进程中运行,访问服务必须使用定义好的接口,通过 IPC 通讯,使得系统更内聚、松耦合、易维护和拓展。

TCP 协议:如何保证页面文件能被完整送达浏览器

HTTP 请求流程:为什么很多站点第二次打开速度会很快

浏览器中的 HTTP 请求从发起到结束一共经历如下八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接;

  1. 构建请求。浏览器构建请求行,构建好后,准备发起网络请求;
  2. 查找缓存。在真正发起请求前浏览器会查询缓存中是否有请求资源副本,有则拦截请求,返回资源副本,否则进入网络请求;
  3. 准备 IP 地址和端口。HTTP 网络请求需要和服务器建立 TCP 连接,而建立 TCP 连接需要准备 IP 地址和端口号,浏览器需要请求 DNS 返回域名对应的 IP,同时会缓存域名解析结果,供下次查询使用;
  4. 等待 TCP 队列。Chrome 机制,同一个域名同时最多只能建立 6 个 TCP 连接;
  5. 建立 TCP 连接。TCP 通过“三次握手”建立连接,传输数据,“四次挥手”断开连接;
  6. 发送 HTTP 请求。建立 TCP 连接后,浏览器就可以和服务器进行 HTTP 数据传输了,首先会向服务器发送请求行,然后以请求头形式发送一些其他信息,如果是 POST 请求还会发送请求体;
  7. 服务器处理请求。首先服务器会返回响应行,随后,服务器向浏览器发送响应头和响应体。通常服务器返回数据,就要关闭 TCP 连接,如果请求头或者响应头有 Connection:keep-alive TCP 保持打开状态;

导航流程:从输入 URL 到页面展示这中间发生了什么

  1. 用户输入 URL 并回车;
  2. 浏览器进程检查 URL,组装协议,构成完整 URL;
  3. 浏览器进程通过进程通信(IPC)把 URL 请求发送给网络进程;
  4. 网络进程接收到 URL 请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程;

如果没有,网络进程向 web 服务器发起 http 请求(网络请求),请求流程如下:

  1. 进行 DNS 解析,获取服务器 IP 地址,端口
  2. 利用 IP 地址和服务器建立 tcp 连接构建请求头信息
  3. 发送请求头信息
  4. 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容

网络进程解析响应流程:

  1. 检查状态码,如果是 301/302,则需要重定向,从 Location 自动读取地址,重新进行第 4 步,如果是 200,则继续处理请求
  2. 200 响应处理:检查响应类型 Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续渲染。如果是 html 则通知浏览器进程准备渲染进程进行渲染

准备渲染进程:

浏览器进程检查当前 URL 是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程

传输数据、更新状态:

  1. 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道”
  2. 渲染进程接收完数据后,向浏览器发送“确认提交”
  3. 浏览器进程接收到确认消息后 engine 浏览器界面状态:安全、地址 URL、前进后退的历史状态、更新 web 页面

渲染流程(上):HTML、CSS 和 JavaScript 是如何变成页面的

渲染流程(下):HTML、CSS 和 JavaScript 是如何变成页面的

变量提升:javascript 代码是按顺序执行的吗

调用栈:为什么 JavaScript 代码会出现栈溢出

块级作用域:var 缺陷以及为什么要引入 let 和 const

作用域链和闭包:代码中出现相同的变量,JavaScript 引擎如何选择

this:从 JavaScript 执行上下文视角讲 this

当执行 new CreateObj 的时候,JavaScript 引擎做了四件事:

  1. 首先创建一个控对象 tempObj;
  2. 接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 createObj 的执行上下文创建时,它的 this 就指向 tempObj 对象;
  3. 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向 tempObj 对象;
  4. 最后返回 tempObj 对象。

this 的使用分为:

  1. 当函数最为对象的方法调用时,函数中的 this 就是该对象;
  2. 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
  3. 嵌套函数中的 this 不会继承外层函数的 this 值;
  4. 箭头函数没有自己的执行上下文,this 是外层函数的 this。

栈空间和堆空间:数据是如何存储的

动态语言:在使用时需要检查数据类型的语言。

弱类型语言:支持隐式转换的语言。

JavaScript 中的 8 种数据类型,它们可以分为两大类—— 原始类型 和 引用类型。

原始类型数据存放在栈中,引用类型数据存放在堆中。堆中的数据是通过引用与变量关系联系起来的。

从内存视角了解闭包:词法扫描内部函数,引用了外部函数变量,堆空间创建一个“closure”对象,保存变量。

垃圾回收:垃圾数据如何自动回收

编译器和解析器:V8 如何执行一段 JavaScript 代码的

消息队列和事件循环:页面是怎么活起来的

webapi:setTimeout 是怎么实现的

JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程会创建一个回调任务,延时执行队列存放定时器任务;

当定时器任务到期,就会从延时队列中取出并执行;

如果当前任务执行时间过久,会影响延时到期定时器任务的执行;

如果 setTimeout 存在嵌套调用(5 次以上),判断该函数方法被阻塞,那么系统会设置最短时间间隔为 4 秒;

未激活的页面,setTimeout 执行最小间隔是 1000 毫秒,目的是为了降低加载损耗;

延时执行时间最大值是 24.8 天,因为延时值是以 32 个 bit 存储的;

setTimeout 设置的回调函数中的 this 指向全局 window

webpai:XMLHttpRequest 是怎么实现的

XMLHttpRequest onreadystatechange 处理流程:未初始化 -> OPENED -> HEADERS_RECEIVED -> LOADING -> DONE;

渲染进程会将请求发送给网络进程,然后网络进程负责资源下载,等网络进程接收到数据后,利用 IPC 通知渲染进程;

渲染进程接收到消息之后,会将 xhr 回调函数封装成任务并添加到消息队列中,等主线程循环系统执行到该任务的时候,会根据相关状态来调用回调函数。

宏任务和微任务:不是所有的任务都是一个待遇

消息队列中的任务为宏任务。渲染进程内部会维护多个消息队列,比如延时执行队列和普通消息队列,主线程采用 for 循环,不断地从这些任务队列中取出任务并执行;

微任务是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前;

V8 在执行 javascript 脚本时,会为其创建一个全局执行上下文,同时会创建一个微任务队列;

执行微任务过程中产生的微任务不会推迟到下个宏任务中执行,而是在当前宏任务中继续执行;

使用 Promise 告别回调函数

使用 Promise 解决了回调地狱问题,消灭嵌套和多次处理;

// 模拟实现 Promise
function Bromise(executor) {
  var _onResolve = null;
  this.then = function (onResolve) {
    _onResolve = onResolve;
  };
  function resolve(value) {
    setTimeout(() => {
      _onResolve(value);
    }, 0);
  }
  executor(resolve, null);
}

async await 使用同步方式写异步代码

生成器函数是一个带星号函数,而且是可以暂停执行和回复执行的;

生成器函数内部执行一段代码,遇到 yield 关键字,javascript 引擎返回关键字后面的内容给外部,并且暂停该函数的执行;

外部函数可以同步 next 方法恢复函数的执行;

协程是一种比线程更加轻量级的存在,协程可以看成是跑在线程上的任务,一个线程可以存在多个协程,但是同时只能执行一个协程,如果 A 协程启动 B 协程,A 为 B 的父协程;

协程不被操作协同内核所管理,而完全由程序所控制,这样性能提升;

await xxx 会创建一个 Promise 对象,将 xxx 任务提交给微任务队列;

暂停当前协程的执行,将主线程的控制权力转交给父协程执行,同时将 Promise 对象返回给父协程,继续执行父协程;

父协程执行结束之前会检查微任务队列,微任务队列中有 resolve(xxx) 等待执行,触发 then 的回调函数;

回调函数被激活后,会将主线程的控制权交给协程,继续执行后续语句,完成后将控制权还给父协程。

页面性能分析:利用 chrome 做 web 性能分析

Chrome 开发者工具(简称 DevTools)是一组网页制作和调试的工具,内嵌于 Google Chrome 浏览器中。它一共包含了 10 个功能面板,包括了 Elements、Console、Sources、NetWork、Performance、Memory、Application、Security、Audits 和 Layers。

DOM 树:JavaScript 是如何影响 DOM 树构建的

HTML 解析器(HTMLParse)负责将 HTML 字节流转换为 DOM 结构;

HTML 解析器并不是等整个文档加载完成之后再解析,而是网络进程加载流多少数据,便解析多少数据;

字节流转换成 DOM 三个阶段:

  1. 字节流转换为 Token;
  2. 维护一个 Token 栈,遇到 StartTag Token 入栈,遇到 EndTag Token 出栈;
  3. 为每个 Token 创建一个 DOM 节点;

JavaScript 文件和 CSS 样式表文件都会阻塞 DOM 解析;

渲染流水线:CSS 如何影响首次加载时的白屏时间?

DOM 构建结束之后,css 文件还未下载完成,渲染流水线空闲,因为下一步是合成布局树,合成布局树需要 CSSOM 和 DOM,这里需要等待 CSS 加载结束并解析成 CSSOM;

CSSOM 两个作用:提供给 JavaScript 操作样式表能力,为布局树的合成提供基础样式信息;

在执行 JavaScript 脚本之前,如果页面中包含了外部 CSS 文件的引用,或者通过 style 标签内置了 CSS 内容,那么渲染引擎还需要将这些内容转化为 CSSOM,因为 JavaScript 有修改 CSSOM 的能力,所以在执行 JavaScript 之前,还需要依赖 CSSOM。也就是说 CSS 在部分情况下也会阻塞 DOM 的生成。

分层和合成机制:为什么 CSS 动画比 JavaScript 高效

显示器固定刷新频率是 60HZ,即每秒更新 60 张图片,图片来自显卡的前缓冲区;

显卡的职责是合成新的图像,保存在后缓冲区,然后后缓冲区和前缓冲区互换,显卡更新频率和显示前刷新频率不一致,就会造成视觉上的卡顿;

渲染流水线生成的每一副图片称为一帧,生成一帧的方式有重排、重绘和合成三种;

重排会根据 CSSOM 和 DOM 计算布局树,重绘没有重新布局阶段;

生成布局树之后,渲染引擎根据布局树特点转化为层树,每一层解析出绘制列表;

栅格线程根据绘制列表中的指令生成图片,每一层对应一张图片,合成线程将这些图片合成一张图片,发送到后缓存区;

合成线程会将每个图层分割成大小固定的图块,优先绘制靠近视口的图块;

页面性能:如何系统优化页面

加载阶段:减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数;

交互阶段:减少 JavaScript 脚本执行时间,避免强制同步布局:操作 DOM 的同时获取布局样式会引发,避免布局抖动:多次执行强制布局和抖动,合理利用 CSS 合成动画:标记 will-change,避免频繁的垃圾回收;

CSS 实现一些变形、渐变、动画等特效,这是由 CSS 触发的,并且是在合成线程中执行,这个过程称为合成,它不会触发重排或者重绘;

虚拟 DOM:虚拟 DOM 和真实 DOM 有何不同

当有数据更新时, React 会生产一个新的虚拟 DOM,然会拿新的虚拟 DOM 和之前的虚拟 DOM 进行比较,这个过程找出变化的节点,然后将变化的节点应用到 DOM 上;

最开始的时候,比较两个 DOM 的过程是在一个递归函数里执行的,其核心算法是 reconciliation。通常情况,这个比较过程执行很快,不过虚拟 DOM 比较复杂时,执行比较函数可能占据主线程比较久的时间,这样会导致其他任务的等待,造成页面卡顿。React 团队重写了 reconciliation 算法,称为 Fiber reconciler,之前老的算法称为 Stack reconciler

PWA:解决 web 应用哪些问题

PWA(Progressive Web App),渐进式 Web 应用。一个渐进式过渡方案,让普通站点过渡到 Web 应用,降低站点改造代价,逐渐支持新技术,而不是一步到位;

PWA 引入 ServiceWorker 来试着解决离线存储和消息推送问题,引入 mainfest.json 来解决一级入口问题;

暗转了 ServiceWorker 模块之后,WebApp 请求资源时,会先通过 ServiceWorker,让它判断是返回 Serviceworker 缓存的资源还是重新去网络请求资源,一切的控制权交给 ServiceWorker 来处理;

在目前的 Chrome 架构中,Service Worker 是运行在浏览器进程中的,因为浏览器进程生命周期是最长的,所以在浏览器的生命周期内,能够为所有的页面提供服务;

WebComponent:像搭积木一样构建 web 应用

CSS 的全局属性会阻碍组件化,DOM 也是阻碍组件化的一个因素,因为页面中只有一个 DOM,任何地方都可以直接读取和修改 DOM;

WebComponent 提供了对局部试图封装能力,可以让 DOM、CSSOM 和 JavaScript 运行在局部环境中;

template 创建模版,查找模版内容,创建影子 DOM,模版添加到影子 DOM 上;

影子 DOM 可以隔离全局 CSS 和 DOM,但是 JavaScript 是不会被隔离的;

HTTP1:HTTP1 性能优化

HTTP/0.9 基于 TCP 协议,三次握手建立连接,发送一个 GET 请求行(没有请求头和请求体),服务器接收请求之后,读取对应 HTML 文件,数据以 ASCII 字符流返回,传输完成断开连接;

HTTP/1.0 增加请求头和响应头来进行协商,在发起请求时通过请求头告诉服务器它期待返回什么类型问题、什么形式压缩、什么语言以及文件编码。引入来状态吗,Cache 机制等;

HTTP/1.1 改进持久化连接,解决建立 TCP 连接、传输数据和断开连接带来的大量开销,支持在一个 TCP 连接上可以传输多个 HTTP 请求,目前浏览器对于一个域名同时允许建立 6 个 TCP 持久连接;

HTTP/1.1 引入 Chunk transfer 支持动态生成内容:服务器将数据分割成若干任意大小的数据块,每个数据块发送时附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。在 HTTP/1.1 需要在响应头中设置完整的数据大小,如 Content-Length。

HTTP2:如何提升网络速度

HTTP/1.1 主要问题:TCP 慢启动;同时开启多条 TCP 连接,会竞争固定宽带;对头阻塞问题;

HTTP/2 在一个域名下只使用一个 TCP 长连接和消除对头阻塞问题;

多路复用的实现:HTTP/2 添加了二进制分帧层,将发送或响应数据经过二进制分帧处理,转化为一个个带有请求 ID 编号的帧,服务器或者浏览器接收到响应帧后,根据相同 ID 帧合并为一条完整信息;

设置请求优先级:发送请求可以设置请求优先级,服务器可以优先处理;

服务器推送:请求一个 HTML 页面,服务器可以知道引用了哪些 JavaScript 和 CSS 文件,附带一起发送给浏览器;

头部压缩:对请求头和响应头进行压缩;

HTTP3:甩掉 TCP、TCL 包袱,构建高效网络

虽然 HTTP/2 解决了应用层面的对头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议,而 TCP 最初是为了单连接而设计;

TCP 可以看成是计算机之间的一个虚拟管道,数据从一端发送到另一端会被拆分为一个个按照顺序排列的数据包,如果在传输过程中,有一个数据因为网络故障或者其他原因丢失,那么整个连接会处于暂停状态,只有等到该数据重新传输;

由于 TCP 协议僵化,也不可能使用新的协议,HTTP/3 选择了一个折衷的方法,基于现有的 UDP 协议,实现类似 TC 片多路复用,传输可靠等功能,称为 QULC 协议;

QULC 实现类似 TCP 流量控制,传输可靠功能;集成 TLS 加密功能;实现多路复用功能;

同源策略:为什么 XMLHttpRequst 不能跨域请求

协议、域名和端口号相同的 URL 是同源的;

同源策略会隔离不同源的 DOM、页面数据和网络通信;

页面可以引用第三方资源,不过暴露出诸如 XSS 问题,引入内容安全策略 CSP 限制;

默认 XMLHttpRequest 和 Fetch 不能跨站请求资源,引入跨域资源共享(CORS)进行跨域访问控制;

跨站脚本攻击 XSS:为什么 cookie 中有 httpOnly 属性

XSS 跨站脚本,往 HTML 文件中注入恶意代码,对用户实施攻击;

XSS 攻击主要有存储型 XSS 攻击、反射型 XSS 攻击和 DOM 的 XSS 攻击;

阻止 XSS 攻击:服务器对脚本进行过滤或转码,利用 CSP 策略,使用 HttpOnly;

CSRF 攻击:陌生连接不要随便点

CSRF 跨站请求伪造,利用用户的登录状态,通过第三方站点攻击;

避免 CSRF 攻击:利用 SameSite(三种模式:Strict、Lax、None) 让浏览器禁止第三方站点发起请求携带关键 Cookie;验证请求的来源站点,请求头中的 Referer 和 Origin 属性;利用 CSRF Token;

沙盒:页面和系统之间的隔离墙

浏览器被划分为浏览器内核和渲染内核两个核心模块,其中浏览器内核石油网络进程、浏览器主进程和 GPU 进程组成的,渲染内核就是渲染进程;

浏览器中的安全沙箱是利用操作系统提供的安全技术,让渲染进程在执行过程中无法访问或者修改操作系统中的数据,在渲染进程需要访问系统资源的时候,需要通过浏览器内核来实现,然后将访问的结果通过 IPC 转发给渲染进程;

站点隔离(Site Isolation)将同一站点(包含相同根域名和相同协议的地址)中相互关联的页面放到同一个渲染进程中执行;

实现站点隔离,就可以将恶意的 iframe 隔离在恶意进程内部,使得它无法继续访问其他 iframe 进程的内容,因此无法攻击其他站点;

HTTPS:让数据传输更安全

在 TCP 和 HTTP 之间插入一个安全层,所有经过安全层的数据都会被加密或者解密;

对称加密:浏览器发送加密套件列表和一个随机数 client-random,服务器会从加密套件中选取一个加密套件,然后生成一个随机数 service-random,返回给浏览器。这样浏览器和服务器都有相同 client-random 和 service-random,再用相同的方法将两者混合生成一个密钥 master secret,双方就可以进行数据加密传输了;

对称加密缺点:client-random 和 service-random 的过程都是明文,黑客可以拿到协商的加密套件和双方随机数,生成密钥,数据可以被破解;

非对称加密:浏览器发送加密套件列表给服务器,服务器选择一个加密套件,返回加密套件和公钥,浏览器用公钥加密数据,服务器用私钥解密;

非对称加密缺点:加密效率太低,不能保证服务器发送给浏览器的数据安全,黑客可以获取公钥;

对称加密结合非对称加密:浏览器发送对称加密套件列表、非对称加密列表和随机数 client-random 给服务器,服务器生成随机数 service-random,选择加密套件和公钥返回给浏览器,浏览器利用 client-random 和 service-random 计算出 pre-master,然后利用公钥给 pre-master 加密,向服务器发送加密后的数据,服务器用私钥解密出 pre-master 数据,结合 client-random 和 service-random 生成对称密钥,使用对称密钥传输加密数据;

引入数字证书是为了证明“我就是我”,防止 DNS 被劫持,伪造服务器;

证书的作用:一个是向浏览器证明服务器的身份,另一个是包含服务器公钥;

数字签名过程:CA 使用 Hash 函数技术明文信息,得出信息摘要,然后 CA 使用私钥对信息摘要进行加密,加密后的秘文就是数字签名;

验证数字签名:读取证书明文信息,使用相同 Hash 函数计算得到信息摘要 A,再利用 CA 的公钥解密得到 B,对比 A 和 B,如果一致,则确认证书合法;

原文链接:肝完《浏览器工作原理与实践》,我总结了这些

上一篇下一篇

猜你喜欢

热点阅读