在浏览器输入 url 回车之后发生什么
- 浏览检查输入的字符串是够符合 url 格式,不符合则使用搜索引擎拼接 keywords,去搜索.
回车动作响应之前, 页面还会发出 beforeunload 事件,给页面取消导航流程的机会
- 如果符合 url 格式,则通过进程间通信(ipc),有浏览器进程将 url 传递给网络进程.
- 网络进程拿到 url 后,会查看本地有没有缓存,如果有且没有过期则使用本地缓存.
- 如果没有缓存, 则需要去服务器获取资源, 在此之前需要使用 DNS 协议进行域名解析, 解析过程会依次在在浏览器/操作系统/路由器/运营商/域名服务器中查询 IP 地址.
- 拿到 IP地址后,则使用 TCP 协议建立网络连接(三次握手).
- 连接建立后,浏览器构造 http 协议的请求并携带 cookie 发送给服务器.
http 协议位于应用层,tcp 协议位于传输层,ip 协议位于网络层.三个协议层级从高到低.
- 服务器收到请求后构造响应,发送给客户端.
- 浏览器的网络进程,解析响应.根据响应行中的状态码,会有不同的行为,例如:返回 301/302 则会使用响应头中的 location 发生重定向;如果返回200 则说明请求成功,开始处理响应体中的内容.
- 根据响应头中 content-type,对于不同类型的响应体数据会有不同的处理,例如,如果是application/octet-stream则会让浏览器进程下载资源,如果是 text/html 则会去渲染页面.
- 接受数据完成后,浏览器进程会发出
提交文档信号给渲染进程 - 渲染进程建立和网络进程的通信管道,获取网络进程中获取到的数据.渲染进程会开始渲染工作.
- 渲染进程数据接收完成后会发出
确认文档信号给浏览器进程. - 浏览器进程收到此信号后,改变导航栏 url 地址/改变前进后退历史/页面改变/安全状态改变.
- 一旦页面渲染完毕,浏览器页签的 loading 图标会消失.
浏览器渲染流程
- 在接收到网络进程的文档数据后,浏览器渲染进程便开始了.
- 首先会将 html 解析成dom 树.
- 将 css 解析成 stylesheet
- 生成布局树.位置信息
- 生成图层树.z 轴信息.
具有层叠上下文的元素和需要裁切的元素会生成图层
- 图层绘制.生成渲染指令列表
- 格栅化操作.渲染线程将指令列表提交给在合成线程, 完成格栅化.
- 格栅化指的是将图块转换成可以显示的位图.图块的概念是将图层继续划分成一个个小块,并优先格栅化视口附近的图块.
- 可以使用 GPU 来加速格栅化,如果使用了 GPU 那就会引入 GPU 线程.
- 合成和显示.合成线程处理完所有的图块后,会向浏览器进程发出
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的创建、初始化和赋值均会被提升。
作用域链和闭包
作用域链: 变量的在不同执行上下文之间的寻找路径.
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
谨记三点:
- 默认情况下函数的 this 指向全局 window(非严格模式,严格模式下为 Undefined).
- 显示使用apply/bind/call 函数可以指定函数的 this
- 使用 new 构造函数
- 对象调用成员函数
- 嵌套函数中的 this 不会继承外层函数的 this 值。
- 箭头函数的执行上下文中的 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;
}
}
- 如果执行一个很耗时的任务,会影响延迟消息队列中任务的执行
- 存在嵌套带调用时候,系统会设置最短时间间隔为4s(超过5层)
- 未激活的页面,setTimeout最小时间间隔为1000ms
- 延时执行时间的最大值2147483647,溢出会导致定时器立即执行
- 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 方法内部, 用于执行一个任务, 并阻塞函数内剩余代码的执行, 知道任务返回.
- 这两个关键字的实现其实是使用了
promise和yield.更底层其实就微任务和协程的原理.
协程和线程的关系: 协程运行在线程之上,一个线程可以有多个协程,但是同一时间只能有一个协程在执行 的执行过程.
在 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 默认是没有开启此功能.
- http 请求必须要等待早先的请求返回后,才能处理下一个,如果之前的请求不返回,就会造成
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(美国和加拿大注册会计协会制定的)认证的, 内置在各大操作系统中.