# 浏览器
# 基础知识
# 多进程架构
- 浏览器进程:负责界面显示、用户交互、子进程管理,提供存储。
- 网络进程:网络资源加载。
- 渲染进程(沙箱):将html、css、js文件渲染成网页。
- 站点隔离:Chrome目前是iframe级隔离渲染进程。同时遵守同一站点、同一上下文组放入一个渲染进程。
- 插件进程:主要是负责插件的运行,防止插件崩溃引起浏览器崩溃。
- GPU进程:负责页面、ChromeUI界面的绘制。
- 缺点:更高的资源占用、难扩展。
# 导航流程
- 浏览器进程接收到用户输入的URL,转发给网络进程。
- 浏览器会根据输入内容判断是搜索关键字还是url。
- 网络进程查询缓存(资源、dns缓存)、发送请求;接收响应头数据,并解析。
- 假如状态码时301或302,网络进程会根据响应头Location字段读取重定向的地址,再次发起请求。
- 如果是下载类型就直接结束导航流程
- 准备渲染进程
- 提交文档(为啥要把数据给到浏览器进程)
- 浏览器进程接收到响应数据后,便向渲染进程发起
提交文档
消息。 - 渲染进程收到
提交文档
消息后,会与网络进程建立数据管道。 - 渲染进程接收完数据后,会发起
确认提交
消息给浏览器进程。 - 浏览器进程接收到
确认提交
消息后,会更新网页状态栏。
# 渲染流程
- 构建DOM
- HTML解析器解析HTML,生成DOM树
- 样式计算
- 将css转换为样式表
- 属性标准化
- 计算每个DOM节点的样式
- 布局阶段
- 额外构建一个可见元素的布局树
- 布局计算
- 分层
- 为特定节点生成图层树
- 拥有层叠上下文属性
- 需要裁剪的地方
- 图层绘制
- 将图层拆分绘制指令,再按顺序组成绘制列表
- 栅格化操作
- 合成线程将图层分成图块
- 按照视口附近的图块优先生成位图
- 会使用GPU加速
- 合成与显示
- 光栅化完成后,合成线程发送DrawQuad命令给浏览器进程,然后将页面内容放入内存,再显示到屏幕。
# cookies
- cookies由服务器端生成,在浏览器存储的数据,会随着http请求发送给服务端。
- 也可以通过document.cookies设置,但是会有安全风险,导致xss攻击,所以一般设置成httpOnly。
- 以键值对的形式存在,值的大小限制是4kb。
- 跟domain同一域名下的请求都会携带cookies。
- Expires设置过期时间
- 一般用于自动登录、记住浏览数据。
# localStorage
- 长期存储在浏览器本地的数据,通过localStorage对象setItem,getItem方法读写。
- 以键值对的形式存在,总大小限制为5M。
# sessionStorage
- 会话级别的浏览器存储,同一窗口下的同源网站可以共用。
- 以键值对的形式存在,总大小限制为5M。
- 会话结束就会清空。
# 同源策略
- 协议、域名、端口都相同为同源
- 同源之间可以互相访问资源和操作DOM
- 会限制Cookie、IndexDB、LocalStorage
- XSS攻击
- 出让的安全性
- 页面可以嵌入第三方资源
- CSP策略:让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联的js代码。
- 跨域资源共享(CORS)
- 跨文档消息机制
- window.postMessage
- 页面可以嵌入第三方资源
# 进程计算
- 标签页之间的连接
- 通过a标签和新标签页或者window.open
- 新标签页的window.opener指向上一个标签页
- 浏览上下文
- 标签页的window对象、历史记录、滚动条位置称为浏览上下文。
- 称为浏览上下文组
- 通过脚本连接起来的浏览上下文
- chrome会把同一个浏览上下文组的同一个站点的标签页分配到一个渲染进程中
- a标签的rel属性可以设置为noopener和noreferer
- 同一站点:只要求根域名、协议相同
- 通过a标签和新标签页或者window.open
# js执行机制
# 编译原理
- 先编译,编译的时候创建执行上下文、变量对象、词法环境。此阶段会变量提升,函数声明提升。再执行。
- var会变量提升包括创建和初始化,而let、const只会创建,function会创建、初始化、赋值。
- 每个执行上下文的变量对象都有一个outer指向外部的执行上下文
- 变量作用域链是根据词法作用域链来的,静态分析的时候。
- 词法作用域是编译的时候就确定好了的。
# js执行机制
- 代码被执行的时候会先编译再执行。,编译时会创建执行上下文。
- 变量提升:是发生在编译阶段,存入变量环境的。重名会覆盖。
- js引擎维护了一个调用栈,底部是全局执行上下文。
- let、const是通过在执行上下文中添加词法环境实现的。
- 词法环境是一个栈结构,每一个都是作用域。作用域块执行时压入,执行完弹出。
- 作用域链:
- 词法作用域:
# V8工作原理
# 数据的存放
- 使用前需要确定类型的语言是静态语言,运行时确定类型的是动态语言
- 支持隐式类型转换的是弱类型语言,反之强类型语言
- 7种基本类型,1种引用类型
- js运行时内存空间:代码空间、栈空间、堆空间
- 栈空间:函数执行上下文
- 堆空间:存储引用类型的值
- 为啥要区分栈、堆。因为js运行时需要用栈来维护上下文的状态,如果数据全部放到栈空间内的话,会影响上下文切换的效率。切换上下文是通过移动指针,然后对失效的上下文回收即可。
- 执行一段代码前需要先编译,并创建执行上下文。闭包也是在编译的过程中发现并创建的。闭包其中包含的是访问了外部函数的变量。
- 执行上下文
- 变量环境
- 词法环境
# 垃圾回收机制
主要采用分代式垃圾回收机制,v8引擎内存结构主要分为新生代和老生代,新生代主要采用scavenge算法,也就是将内存一分为二,一部分为激活空间,一部分为闲置空间,当进行一次垃圾回收时,将激活区存活的对象复制到闲置区,然后再将闲置区和激活区身份互换。缺点就是浪费一半的内存用于复制。当一个对象经历过一次scavenge算法后,在下一次垃圾回收时,会转移到老生代,或者转移时闲置区空间的内存占比已经超过25%,也会将后续的对象转移到老生代。老生代使用的的标记清除和标记整理算法。标记清理是从根节点开始遍历堆中所有的对象,然后把能访问到的对象标记为活的,然后把未被标记的对象进行清理。标记整理则是为了解决清理过后内存空间不连续的问题。所以在回收过后,将存活的对象往堆内存的一端进行移动,移动完成后再清理掉边界外的全部内存。为了减少垃圾回收的停顿时间,引入了延迟清理、增量标记和增量整理、并行标记、并行清理。习惯:减少闭包使用、少创建全局对象、清理计时器、清除DOM引用。
# JS代码的执行
- 词法分析:将源码拆分成token,也就是语法上最小的单元。
- 语法分析:将token转成AST。
- 编译器是先生成中间代码,再生成二进制文件,最后执行。
- babel、eslint都依赖ast来分析源码。
- 主要流程(JIT即时编译)
- 源代码通过词法分析和语法分析生成AST,然后生成执行上下文
- 解释器生成字节码
- 执行代码
- 字节码第一次执行,则解释器逐行解释执行字节码。
- 热点代码则是由编译器编译成机器码,再执行。
# 页面循环系统
# 页面渲染
# DOM
- 生成页面的基础数据结构
- 提供给js脚本操作接口
- 解析阶段可以拦截不安全内容
- web页面和js脚本的桥梁
# 页面渲染流程
- 构建DOM
- 输入HTML文件,输出DOM
- 样式计算(CssOm、标准化属性值、继承层叠)
- 输入css文件,输出styleSheets
- 标准化样式表的属性值
- 根据继承规则计算DOM节点的样式
- 布局(创建布局树、布局计算)
- 计算出DOM可见元素的几何位置
- 分层(图层树)
- 为特定节点生成图层,并生成一颗图层树
- 绘制(绘制列表)
- 将图层绘制拆分成绘制指令,再组成绘制列表
- 分块(合成线程分成图块)
- 渲染进程下的合成线程操作
- 将图层划分为图块(256256 512512)
- 光栅化(合成线程转换成位图)
- 渲染进程下的合成线程操作
- 按照视口附近的图块优先生成位图
- 渲染进程维护了一个栅格化的线程池,可通过GPU加速(快速栅格化,保存在GPU内存)。
- 合成与显示(主进程viz组件)
- 所有图块光栅化后,合成线程发送DrawQuad指令
# 分层和合成机制
- 显示器读取60次显卡前缓冲区中的图像,展示出来。
- 显卡合成新的图像后,会将图像保存到后缓冲区。一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换。
- 一般来说显卡的更新频率和显示器的刷新频率是一致的,当不一致的时候,会出现卡顿现象。
- 渲染引擎通过渲染流水线生成图片,并发送到显卡的后缓冲区。(问题:不是放入内存吗。)
- 合成技术:分层、分块、合成
- 不触发布局和绘制阶段。效率高
- 如果没有采用分层,每次页面有个很小的变化,都会触发重排和重绘机制。
- 每次更新,合成器只需要对对应的层做相应的变化即可。
- 合成操作是在合成线程上完成的,也就意味着不会影响主线程的执行。
- 分块:合成线程会将每个图层分割为大小固定的图块。然后优先绘制靠近视口的图块。
- 纹理上传,计算机内存上传到GPU内存的操作很慢。所以chrome在首次合成图块的时候使用了一个低分辨率的图片。分辨率减少了一半,纹理减少了四分之三
- will-change,提前告诉渲染引擎,这时候,渲染引擎会将该元素单独一层,然后通过合成线程直接渲染。
- 缺点:内存占用会增加
# 系统优化页面
- 页面阶段: 加载、交互、关闭
- 关键资源:js文件、html文件、css文件
- 减少关键资源个数、降低rtt次数,优化加载速度
- 1个http的数据包在14kb左右
- 减少js脚本执行时间
- 采用web workers
- 拆分成多个任务
- 避免强制同步布局
- 修改DOM后立马查询DOM值,会强制让渲染引擎执行一次布局操作。
- 虚拟dom,双缓存思想的体现。
- 布局抖动
- 再一次js执行过程中,多次执行强制布局和抖动操作。
- 虚拟dom,双缓存思想的体现。
- 避免频繁的垃圾回收
# 降低白屏时间
内联js、css
减少js、css文件大小
sync、defer
Queuing:请求排队的时间。因为tcp连接数和资源的优先级导致。
- 优化:域名分片,资源分布到多个域名。
Stalled:表示停滞的时间
Initial connection:建立连接的时间
SSL:SSL握手的时间
Request sent:建立连接后,把浏览器缓冲区的数据发送出去。
TTFB:发送数据后,接收到服务器第一字节的时间。服务端响应速度的指标。
- 优化:服务端渲染、网络问题、请求头冗余;缓存、提高服务器处理速度。
Content Download:接收第一字节后,到完整接收完数据的时间。
- 压缩代码
# HTML解析器
- 将html解析成dom结构
- 网路进程加载了多少数据,解析器便解析多少数据
- 网络进程接收到响应头后,根据content-type判断文件类型,如果是html,则会选择或者创建一个渲染进程,渲染进程和网络进程之间会建立一个数据管道,渲染进程将接收到的数据传给html解析器。
- html字节流-分词器转成token-将token解析为Node节点-同步插入DOM树
- 维持一个token栈结构,不断往里压入弹出token来生成DOM节点,并加入DOM树。
- 解析器开始工作时,会默认创建一个根为document的空DOM结构,同时将startTag document压入栈底。
- 内联代码:当遇到script标签时,解析器会暂停DOM的解析。js引擎介入执行js代码。
- 引入js文件:先下载js代码。会阻塞DOM解析。
- chrome优化:开启预解析线程,预解析html字节流,分析是否包含js、css文件,然后提前下载。(问题:提前下载也会阻塞dom解析吧,2.下载不是网络进程的事情吗,为啥会阻塞。3.预解析html字节流时机。)
- async:脚本加载完成,立即执行。
- defer:DOMContentLoaded事件之前执行。
- DOMContentLoaded:页面构建好DOM,表示HTML、JS、CSS都下载好了。
- Load:浏览器已经加载了所有的资源。
- 在执行js之前,会先解析js语句上所有的css样式。如果代码引入了外部css文件,还需要先下载css文件,并解析生成cssOM对象后,才执行js脚本。
- 渲染引擎有一个安全检查模块叫XSSAuditor,用来检测词法安全的。在分词器解析出token后,它会检测这些模块是否安全,比如是否引用了外部脚本,是否符合csp规范,是否存在跨站点请求。然后对脚本或下载任务进行拦截。
# css
- 请求html数据和构建DOM中间有一段空闲时间。
- DOM构建结束和css文件下载结束有空闲时间。
- css需要解析出cssom
- 提供js操作样式表的能力
- 为布局树的合成提供基础的样式信息
- css在存在js的情况下,会阻塞DOM的生成。
- 如果同时存在css外部文件、js外部文件,则会同时在下载,先执行css再执行js
- 不要频繁的创建临时对象。
# 网络
# 请求HTTP流程
- 构建请求
- 查找缓存:在浏览器缓存中查询文件。
- 准备IP地址和端口
- 先查DNS数据缓存,看域名是否解析过
- 请求DNS服务器将域名转换为IP地址
- 等待TCP队列:浏览器对同一域名限制最多建立6个TCP连接。
- 发送HTTP请求。
# 零散
- IP(网际协议):计算机在互联网上的地址。
- UDP(用户数据包协议)
- DNS(域名系统)
# TCP
- 定义:是一种面向连接的、可靠的、基于字节流的传输层通信协议。
- 三次握手
- 实现原理
# HTTP(超文本传输协议)
- 允许浏览器向服务器获取资源的协议。
- HTTP头
- 请求行:包括请求方法、请求URI、协议版本
- 请求头:包含信息的字段。
- 请求体:包含需要传输给服务器的数据。
- 响应行:包括协议版本、状态码、响应行
- 响应头:包含信息的子弹。
- Cache-Control:告知浏览器是否缓存该资源。
- Set-Cookie:返回给浏览器让浏览器下次请求携带进请求头Cookie。
- Content-Type:响应体数据类型。浏览器根据该数据类型决定如何显示。
- 响应体:服务器返回的数据。
# http
- http0.9
- 只有请求行
- 没有返回头
- ascii字节码传输
- http1.0
- 通过增加请求头和响应头,来支持多类型的文件下载
- 增加状态码
- 提供了Cache机制
- 支持用户代理
- http1.1
- 支持持久连接,默认开启,一个tcp连接上可以传输多个http请求。
- 每个域名最多维护6个tcp连接
- 增加Host字段,提供虚拟主机的支持,支持多个域名公用1个ip地址
- 引入Chunk transfer机制,服务器将数据分割成若干个任意大小的数据块,每个数据块附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。支持动态内容传输。
- 客户端cookies、安全机制
- 问题
- 队头阻塞:在没有接收到上一个http响应时,不会开启下一个请求。
- tcp机制的慢启动导致带宽利用率不理想
- 同时多条tcp连接会竞争固定的带宽
- http2.0
- 一个域名只使用一个tcp长连接
- 多路复用
- 通过增加二进制分帧层实现多路复用,把请求转换成一个个带请求ID的帧。
- 服务器收到优先资源的请求,可以暂停别的请求来优先处理。
- 可以设置请求的优先级
- 服务器可以提前将数据推送到浏览器
- 头部压缩
- tcp的队头阻塞:中途一个数据包丢失导致重传等待导致
- 当达到2%的丢包率时,http/1.1比http/2的表现好。
- tcp协议的僵化
- 中间设备的僵化
- 操作系统更新滞后
- http3
- QUIC协议:使用udp协议,在应用层实现流量控制、多路复用、可靠传输的功能
- 多路复用:同一物理连接上有多个独立的逻辑数据流
- 问题:
- 服务器端和浏览器端没有对http3的提供完整支持
- 中间设备的僵化,对udp的优化程度不高。
- 系统内核对udp优化不高。
# 安全
# XSS攻击
- 存储型:黑客将恶意代码存储到存在漏洞的服务器
- 例如:输入框长传
- 反射型:恶意脚本属于用户发送给网站请求的一部分(路径参数),随后网站又把恶意脚本返回给用户。
- 基于DOM的XSS攻击:黑客通过各种手段将恶意脚本注入到用户的页面中。特点是在web资源传输的过程或者用户使用页面的过程中修改Web页面的数据。
- 阻止
- 前两种属于服务器端的漏洞,服务器可以对输入脚本进行过滤或者转码。
- 实施严格的CSP
- 限制加载其他域的资源文件
- 禁止向第三方域提交数据
- 禁止执行内联脚本和未授权脚本
- 提供了上报机制
- 使用HttpOnly属性
- 使用HttpOnly属性保护Cookie的安全。
# CSRF攻击
- 利用服务器的漏洞和用户的登录状态在第三方站点实施攻击
- 引诱用户点击恶意链接,然后隐式调用漏洞接口
- 问题:怎么利用用户的登录状态
- 阻止
- 充分利用好Cookies的SameSite属性
- strict:完全禁止第三方Cookie
- Lax:跨站点的情况下,链接打开、get表单都会写到Cookie,Post、img、iframe等标签加载的URL不会携带 。
- 验证请求的来源站点
- Referer请求头:记录了HTTP请求的来源地址
- Origin:记录了HTTP请求的来源域名
- CSRF Token
- 充分利用好Cookies的SameSite属性
# 安全沙箱
- 渲染进程需要解析网络资源,可能存在恶意攻击。所以需要将渲染进程与操作系统隔离。
- 对浏览器功能模块的影响
- 持久存储
- 浏览器内核会维护一个存放了所有Cookie的Cookie数据库。
- 浏览器内存也负责缓存文件的读写。
- 网络访问
- 网络进程专门负责网络请求,并检查渲染进程是否有权限。比如跨域检查。混合内容。
- 用户交互
- 渲染引擎无法访问窗口句柄
- 用户交互事件需要浏览器内核转发。
- 渲染进程渲染好位图需要先交给浏览器内核。
- 渲染引擎无法访问窗口句柄
- 持久存储
- 站点隔离
- 目前操作系统有漏洞,黑客可以通过这个漏洞直接入侵到进程的内部。
- 目前是iframe级的渲染进程隔离。
# HTTPS
- 防止中间人攻击
- 对称加密和非对称加密搭配使用
- 对称密钥通过非对称加密传输给双方。客户端随机数和服务器端随机数外加最后一个客户端生成的随机数,这个数字会用公钥加密传给服务器端。
- 数字证书
- 防止客户劫持服务器IP地址。
- 引入权威机构CA颁发数字证书
- 数字证书里面包含了服务器公钥
- 所以服务器端是返回数字证书来代替公钥,然后浏览器端验证证书。
# 数字证书
- 服务方需要将公钥、公司、站点等信息提交给CA机构。
- CA审核通过后,会发给服务方一个证书,明文包含服务方提交的信息和一个签名。
- 签名是用CA的私钥进行了加密的。
- 浏览器端接收到证书后,用CA的公钥解密签名,再把明文的信息和解密的信息对照,一致的话,证明证书是合法。
- 如果CA比较小众,浏览器会根据CA链查找最顶级的CA的证书信息。与浏览器内置的顶级CA(自签名证书)对比。
- 问题(验证顶级CA的流程,还有CA的公钥和hash函数从哪来)
# 未来方向
# PWA
- 渐进式增强web的优势,并通过技术手段渐进式的缩短和本地应用或者小程序的距离。
- 离线存储和消息推送
- Service Worker
- 运行在浏览器进程中,为所有页面提供服务。
- 拦截请求和缓存资源
- 消息推送,进程没启动的话,怎么消息推送。
- Service Worker
# WebComponent
- 解决问题:css、dom隔离
- 实现
- Custom elements、Shadow DOM、HTML templates
- Shadow DOM原理
- 都有一个shadow root的根结点,展示的样式和元素添加到根结点上。
- 浏览器对DOM接口做了拦截,如果是shadow root,则跳过查询。
- 生成布局树的时候,渲染引擎判断是否是shadow root,内部元素直接使用内部的css属性。
# 基本知识
- 线程是由进程启动和管理的。
- 进程是一个程序的运行实例,一个程序启动后,操作系统会分配一块内存用来存放代码、运行的数据、主线程。这样的环境就是进程。
- 当一个进程关闭后,系统会回收进程所占的内存。
- 进程之间是相互隔离的,通信需要IPC机制。
- 浏览器有多个进程:
- 渲染进程(沙箱):把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面
- 网络进程:面向渲染进程和浏览器进程等提供网络下载功能
- 浏览器主进程:主要负责用户交互、子进程管理和文件储存等功能
- 插件进程(沙箱)、GPU进程
- FP(first paint):从页面加载到首次开始绘制的时长。
- UDP可以校验当前包是否正常,但是对于错误的数据包,UDP不提供重发机制,只是丢弃当前包。
- HTTP是应用层协议。
- 查找缓存-DNS解析(缓存)-等待tcp队列-tcp连接-发送http请求。
- 请求行(请求方法、请求URI、http版本)、请求头、请求体
- 响应行(http版本、状态码)、响应头、响应体
- 重定向、location、301
- 缓存: Cache-Control、If-None-Match、304
- 登陆保持:Set-Cookies
- 导航:输入URL-发送请求-接收数据-提交导航-建立管道-确认提交(数据传输完)-移除旧页面。
- Content-Type
- document、document.styleSheets
- UserAgent浏览器默认样式
- 如何形成单独的一层
- 层叠上下文属性:fixed-zIndex:2-filter-opacity
- 被裁减:overflow
- Layers、document绘制过程
- 重排需要更新完成的渲染流水线;重绘会跳过布局和分层阶段;合成阶段最快,直接合成(transform)。
宏任务: 微任务:在宏任务快要结束的时候执行。 消息队列:内部排列的是宏任务 延时队列:其实是一个hashMap的结构,内部排列的是宏任务,当执行到这个结构时,会遍历内部的任务,然后执行到期的任务,所有到期任务结束以后才进入下一个循环。
# 问题:
- 快速光栅后,不是图片已经存在GPU内存了吗,为啥要要主线程再绘制到内存。