肝完《浏览器工作原理与实践》,我总结了这些
Chrome 架构:仅仅打开了 1 个页面,为什么有 4 个进程
线程和进程区别:多线程可以并行处理任务,线程不能单独存在,它是由进程来启动和管理的。一个进程是一个程序的运行实例。
线程和进程的关系:
- 进程中任意一线程执行出错,都会导致整个进程的崩溃。
- 线程之间共享进程中的数据。
- 当一个进程关闭后,操作系统会回收进程所占用的内存。
- 进程之间的内容相互隔离。
单进程浏览器:
- 不稳定。单进程中的插件、渲染线程崩溃导致整个浏览器崩溃。
- 不流畅。脚本(死循环)或插件会使浏览器卡顿。
- 不安全。插件和脚本可以获取到操作系统任意资源。
多进程浏览器:
- 解决不稳定。进程相互隔离,一个页面或者插件崩溃时,影响仅仅时当前插件或者页面,不会影响到其他页面。
- 解决不流畅。脚本阻塞当前页面渲染进程,不会影响到其他页面。
- 解决不安全。采用多进程架构使用沙箱。沙箱看成时操作系统给进程上来一把锁,沙箱的程序可以运行,但是不能在硬盘上写入任何数据,也不能在敏感位置读取任何数据。
多进程架构:分为 浏览器进程、渲染进程、GPU 进程、网络进程、插件进程。
缺点:
- 资源占用高。
- 体系架构复杂。
面向服务架构:把原来的各种模块重构成独立的服务,每个服务都可以在独立的进程中运行,访问服务必须使用定义好的接口,通过 IPC 通讯,使得系统更内聚、松耦合、易维护和拓展。
TCP 协议:如何保证页面文件能被完整送达浏览器
- IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标 IP 地址、生存时间等信息;
- UDP 头中除了目的端口,还有源端口号等信息;
- IP 负责把数据包送达目的主机;
- UDP 负责把数据包送达具体应用;
- 对于错误的数据包,UDP 不提供重发机制,只是丢弃当前的包,不能保证数据的可靠性,但是传输速度非常块;
- TCP 头除了包含了目标端口和本机端口号外,还提供了用于排序的序列号,保证了数据完整地传输,它的连接可分为三个阶段:建立连接、传输数据和断开连接;
HTTP 请求流程:为什么很多站点第二次打开速度会很快
浏览器中的 HTTP 请求从发起到结束一共经历如下八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接;
- 构建请求。浏览器构建请求行,构建好后,准备发起网络请求;
- 查找缓存。在真正发起请求前浏览器会查询缓存中是否有请求资源副本,有则拦截请求,返回资源副本,否则进入网络请求;
- 准备 IP 地址和端口。HTTP 网络请求需要和服务器建立 TCP 连接,而建立 TCP 连接需要准备 IP 地址和端口号,浏览器需要请求 DNS 返回域名对应的 IP,同时会缓存域名解析结果,供下次查询使用;
- 等待 TCP 队列。Chrome 机制,同一个域名同时最多只能建立 6 个 TCP 连接;
- 建立 TCP 连接。TCP 通过“三次握手”建立连接,传输数据,“四次挥手”断开连接;
- 发送 HTTP 请求。建立 TCP 连接后,浏览器就可以和服务器进行 HTTP 数据传输了,首先会向服务器发送请求行,然后以请求头形式发送一些其他信息,如果是 POST 请求还会发送请求体;
- 服务器处理请求。首先服务器会返回响应行,随后,服务器向浏览器发送响应头和响应体。通常服务器返回数据,就要关闭 TCP 连接,如果请求头或者响应头有 Connection:keep-alive TCP 保持打开状态;
导航流程:从输入 URL 到页面展示这中间发生了什么
- 用户输入 URL 并回车;
- 浏览器进程检查 URL,组装协议,构成完整 URL;
- 浏览器进程通过进程通信(IPC)把 URL 请求发送给网络进程;
- 网络进程接收到 URL 请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程;
如果没有,网络进程向 web 服务器发起 http 请求(网络请求),请求流程如下:
- 进行 DNS 解析,获取服务器 IP 地址,端口
- 利用 IP 地址和服务器建立 tcp 连接构建请求头信息
- 发送请求头信息
- 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
网络进程解析响应流程:
- 检查状态码,如果是 301/302,则需要重定向,从 Location 自动读取地址,重新进行第 4 步,如果是 200,则继续处理请求
- 200 响应处理:检查响应类型 Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续渲染。如果是 html 则通知浏览器进程准备渲染进程进行渲染
准备渲染进程:
浏览器进程检查当前 URL 是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程
传输数据、更新状态:
- 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道”
- 渲染进程接收完数据后,向浏览器发送“确认提交”
- 浏览器进程接收到确认消息后 engine 浏览器界面状态:安全、地址 URL、前进后退的历史状态、更新 web 页面
渲染流程(上):HTML、CSS 和 JavaScript 是如何变成页面的
- 浏览器不能直接理解 HTML 数据,需要将其转化为 DOM 树结构;
- 生成 DOM 树后,根据 CSS 样式表,计算出 DOM 树所有节点样式;
- 创建布局树:遍历 DOM 树所有可见节点,把这些节点加到布局中,不可见节点忽略,如 head 标签下所有内容,display: none 元素;
渲染流程(下):HTML、CSS 和 JavaScript 是如何变成页面的
- 分层:层叠上下文属性的元素(比如定位属性元素、透明属性元素、CSS 滤镜属性元素)提升为单独的一层,需要裁剪的地方(比如出现滚动条)也会被创建为图层;
- 图层绘制:完成图层树构建后,渲染引擎会对图层树每一层进行绘制,把一个图层拆分成小的绘制指令,再把指令按照顺序组成一个带绘制列表;
- 有些情况图层很大,一次绘制所有图层内容,开销太大,合成线程会将图层划分为图块(256x256 或者 512x512);
- 合成线程将图块提交给栅格线程进行栅格化,将图块转换为位图。栅格化过程都会使用 GPU 加速,生成的位图保存周期 GPU 内存中;
- 一旦所有图块都被栅格化,合成线程会生成一个绘制图块命令(DrawQuad),然会将命令提交给浏览器进程,viz 组件接收到该指令,将页面内容绘制到内存中,显示在屏幕上;
- 重排:通过 JavaScript 或者 CSS 修改元素几何位置属性,会触发重新布局,解析后面一系列子阶段;
- 重绘:不引起布局变换,直接进入绘制及其以后子阶段;
- 合成:跳过布局和绘制阶段,执行的后续操作,发生在合成线程,非主线程;
变量提升:javascript 代码是按顺序执行的吗
- JavaScript 代码在执行之前需要先编译,在编译阶段,变量和函数会被存放到变量环境中,变量默认值会被设置为 undefined;
- 在代码执行阶段,JavaScript 引擎会从变量环境中查找自定义的变量和函数;
- 如果在编译阶段,窜爱两个相同的函数,那么最终放在变量环境中的是最后定义的那个,后定义的覆盖先定义的;
调用栈:为什么 JavaScript 代码会出现栈溢出
- 每调用一个函数,JavaScript 引擎会为其创建执行上下文压入调用栈,然后,JavaScript 引擎开始执行函数代码;
- 如果一个函数 A 调用另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶;
- 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈;
- 当分配的调用栈空间被占满时,会引发“堆栈溢出”问题;
块级作用域:var 缺陷以及为什么要引入 let 和 const
-
let
、const
申明的变量不会被提升。在 javascript 引擎编译后,会保存在词法环境中; - 块级作用域在代码执行时,将
let
、const
变量存放在词法环境的一个单独的区域。词法环境内部维护一个小型的栈结构,作用域内部变量压入栈顶。作用域执行完,从栈顶弹出;
作用域链和闭包:代码中出现相同的变量,JavaScript 引擎如何选择
- 使用一个变量,JavaScript 引擎会在当前的执行上下文中查找变量,如果没有找到,会继续在 outer(执行环境指向外部执行上下文的引用)所指向的执行上下文中查找;
- JavaScript 执行过程,作用域链是由词法作用域决定,而词法作用域是由代码中函数声明的位置决定;
- 根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使外部函数已经执行结束了,但是内部函数引用外部函数的变量依旧保存在内存中,把这些变量的集合称为闭包;
this:从 JavaScript 执行上下文视角讲 this
当执行 new CreateObj 的时候,JavaScript 引擎做了四件事:
- 首先创建一个控对象 tempObj;
- 接着调用 CreateObj.call 方法,并将 tempObj 作为
call
方法的参数,这样当 createObj 的执行上下文创建时,它的 this 就指向 tempObj 对象; - 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向 tempObj 对象;
- 最后返回 tempObj 对象。
this 的使用分为:
- 当函数最为对象的方法调用时,函数中的 this 就是该对象;
- 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
- 嵌套函数中的 this 不会继承外层函数的 this 值;
- 箭头函数没有自己的执行上下文,this 是外层函数的 this。
栈空间和堆空间:数据是如何存储的
动态语言:在使用时需要检查数据类型的语言。
弱类型语言:支持隐式转换的语言。
JavaScript 中的 8 种数据类型,它们可以分为两大类—— 原始类型 和 引用类型。
原始类型数据存放在栈中,引用类型数据存放在堆中。堆中的数据是通过引用与变量关系联系起来的。
从内存视角了解闭包:词法扫描内部函数,引用了外部函数变量,堆空间创建一个“closure”对象,保存变量。
垃圾回收:垃圾数据如何自动回收
- 栈中数据回收:执行状态指针 ESP 在执行栈中移动,移过某执行上下文,就会被销毁;
- 堆中数据回收:V8 引擎采用标记-清除算法;
- V8 把堆分为两个区域——新生代和老生代,分别使用副、主垃圾回收器;
- 副垃圾回收器负责新生代垃圾回收,小对象(1 ~ 8M)会被分配到该区域处理;
- 新生代采用 scavenge 算法处理:将新生代空间分为两半,一半空闲,一半存对象,对对象区域做标记,存活对象复制排列到空闲区域,没有内存碎片,完成后,清理对象区域,角色反转;
- 新生代区域两次垃圾回收还存活的对象晋升至老生代区域;
- 主垃圾回收器负责老生区垃圾回收,大对象,存活时间长;
- 新生代区域采用标记-清除算法回收垃圾:从根元素开始,递归,可到达的元素活动元素,否则是垃圾数据;
- 为了不造成卡顿,标记过程被切分为一个个子标记,交替进行。
编译器和解析器:V8 如何执行一段 JavaScript 代码的
- 计算机语言可以分为两种:编译型和解释型语言。编译型语言经过编译器编译后保留机器能读懂的二进制文件,比如 C/C++,go 语言。解释型语言是在程序运行时通过解释器对程序进行动态解释和执行,比如 Python,JavaScript 语言。
- 编译型语言的编译过程:编译器首先将代码进行词法分析、语法分析,生成抽象语法树(AST),然后优化代码,最后生成处理器能够理解的机器码;
- 解释型语言解释过程:解释器会对代码进行词法分析、语法分析,并生产抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后根据字节码执行程序;
- AST 的生成:第一阶段是分词(词法分析),将一行行源码拆解成一个个 token(语法上不可再分、最小单个字符)。第二阶段是解析(语法分析),将上一步生成的 token 数据,根据语法规则转为 AST,这一阶段会检查语法错误;
- 字节码存在的意义:直接将 AST 转化为机器码,执行效率是非常高,但是消耗大量内存,从而先转化为字节码解决内存问题;
- 解释器 ignition 在解释执行字节码,同时会手机代码信息,发现某一部分代码是热点代码(HotSpot),编译器把热点的字节码转化为机器码,并保存起来,下次使用;
- 字节码配合解释器和编译器的计数实现称为即时编译(JIT)。
消息队列和事件循环:页面是怎么活起来的
- 每个渲染进程都有一个主线程,主线程会处理 DOM,计算样式,处理布局,JavaScript 任务以及各种输入事件;
- 维护一个消息队列,新任务(比如 IO 线程)添加到消息队列尾部,主线程循环地从消息队列头部读取任务,执行任务;
- 解决处理优先级高的任务:消息队列的中的任务称为宏任务,每个宏任务中都会包含一个微任务队列,在执行宏任务的过程中,如果 DOM 有变化,将该变化添加到微任务队列中;
- 解决单个任务执行时长过久: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 三个阶段:
- 字节流转换为 Token;
- 维护一个 Token 栈,遇到 StartTag Token 入栈,遇到 EndTag Token 出栈;
- 为每个 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,如果一致,则确认证书合法;
原文链接:肝完《浏览器工作原理与实践》,我总结了这些