Open nodisappear opened 1 year ago
【透视HTTP协议】
搭建实验环境 (1) 安装 Web 服务器 openresty (2) 安装网络抓包工具 Wireshark (3) 安装虚拟终端 Telnet (4) 以管理员身份修改 hosts (5) 过滤 “tcp.stream eq 0” (6) 用 telnet 连接 openresty:open www.chrono.com 80
浏览器 http 请求过程: (1) 浏览器从地址栏输入中获得服务器的IP地址和端口号 (2) 浏览器用TCP的三次握手与服务器建立连接 (3) 浏览器向服务器发送请求报文 (4) 服务器处理请求,向浏览器发送响应报文 (5) 浏览器解析报文,渲染页面
描述请求方法的重要属性: (1) 安全:请求方法不会破环服务器上的资源(正:GET/HEAD;反:POST/PUT/DELETE) (2) 幂等:多次执行相同的操作,得出相同的结果(正:GET/HEAD/PUT/DELETE;反:POST)
URI的完整形态:协议名(http/https...) + 特定符(://) + 身份信息(user:passwd@,不推荐使用) + 主机号(主机名:端口) + 资源位置(/1/2...) + 查询参数(?q1=1&...) + 片段标识符(#fragment,不会发送给服务器)
生成器函数是可以暂停和恢复执行的带星号函数,在生成器内部执行一段代码,如果遇到 yield 关键字,js 引擎将会返回关键字后面的内容给外部,并暂停函数执行
协程是跑在线程上的任务,一个线程可以拥有多个交替执行的协程,当切换不同协程时,js 引擎会保存当前执行协程的调用栈信息,并恢复即将执行协程的调用栈信息
执行器就是把执行生成器的代码封装成一个函数,通过生成器(协程 -> generator函数)配合执行器(微任务 -> Promise)可以实现“用同步方式书写异步代码”
async 与 await (1) async 是一个异步执行并隐式返回 Promise 作为结果的函数 (2) 执行到 await 时会默认创建一个 Promise 对象
async function foo() {
console.log('foo start;')
return 'foo end;'
}
async function bar() {
console.log('bar start;')
let res = await foo()
console.log(res)
console.log('bar end;')
}
console.log('script start;')
setTimeout(function () {
console.log('setTimeout;')
}, 0)
bar()
new Promise(function (resolve) {
console.log('promise executor;')
resolve()
}).then(function () {
console.log('promise then;')
})
console.log('script end;')
// script start; bar start; foo start; promise executor; script end; foo end; bar end; promise then; setTimeout;
- 类型系统:每种语言都定义了类型,以及类型的操作方式和交互方式
- 在 JS 中,V8会通过 ToPrimitive 函数将对象转换为原生的字符串或数字类型:
(1) 调用对象的 valueOf 方法,返回字符串类型值或数字类型值
(2) 调用对象的 toString 方法,返回字符串类型值或数字类型值
(3) 将对象转换为数字类型值
![image](https://user-images.githubusercontent.com/38271366/205014500-dc9fa3a4-72ef-49ce-9c4c-5bdaba76d2e2.png)
- RTT(Round Trip Time,网络延迟):从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间
(1) 建立连接的三次握手需要消耗 1.5 个 RRT
(2) 建立 TLS 连接需要消耗 1~2 个 RRT
(3) 综合来看,传输数据之前,需要花掉 3~4 个 RRT
- TCP 协议僵化:操作系统更新滞后、中间设备(路由器,防火墙,交换机...)更新滞后
- QUIC 协议:基于 UDP 实现一些 TCP 的功能,在同一物理连接上可以有多个独立的逻辑数据流
![image](https://user-images.githubusercontent.com/38271366/205047336-a9bb5c50-8a5a-465f-bde5-3e501f5688a7.png)
一. Web 页面安全
Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
(4) 使用 HTTP 头部(Content-Security-Policy-Report-Only)来指定报告模式,任何违规行为都会报告给一个指定的 URI 地址
Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
(5) 在 中配置策略
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
HttpOnly:仅支持在 HTTP 请求中用而不能通过 JS 读取
二. 浏览器网络安全
浏览器被划分为 浏览器内核 和 渲染内核 两个核心模块,其中浏览器内核是由网络进程、浏览器主进程和 GPU 进程组成的,渲染内核就是渲染进程
浏览器的安全沙箱: (1) 保护的最小单位是进程,限制渲染进程对操作系统资源的访问和修改 (2) 浏览器的持久存储、网络访问和用户交互功能是在浏览器内核中实现的 (3) 站点隔离:将同一站点中相互关联的页面放到同一个渲染进程中执行
三. 浏览器系统安全 对称加密算法,如:AES 非对称加密算法,如:RSA
【浏览器工作原理与实践】
浏览器中的 js 执行机制
变量提升是指提升变量和函数的声明部分并赋默认值undefined,会带来变量覆盖、变量未及时销毁等问题,可以通过 ES6 的 let 或 const 来实现块级作用域
js代码先编译后执行,编译阶段会生成执行上下文和可执行代码,执行上下文包括同为栈结构的变量环境和词法环境,其中变量提升内容保存在变量环境中,let 和 const 声明的变量保存在词法环境中(变量环境 -> 变量提升,词法环境 -> 块级作用域)
调用栈又称执行上下文栈,利用栈结构的后进先出规则来管理多个执行上下文,在每个执行上下文的变量环境中都包含一个外部引用 outer 来指向外部的执行上下文,还包含一个this
调用函数时,js引擎会先创建函数执行上下文并将其压入调用栈,然后执行函数代码时会优先从当前执行上下文中查找变量(沿着词法环境的栈顶向下查询 -> 沿着变量环境的作用域链进行查询),最后在函数执行完毕后将函数执行上下文弹出栈
作用域是变量和函数的可访问范围,控制着变量和函数的可见性和生命周期
词法作用域又称静态作用域,是指作用域由代码中函数声明的位置决定,与函数的调用关系无关,查找变量的作用域链路径是按照词法作用域实现的
在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回内部函数后,即使该外部函数执行结束,内部函数引用外部函数的变量依然保存于内存中,这些变量的合集就是闭包,调用闭包中的方法时,js引擎会沿着“当前执行上下文 -> 函数闭包 -> 全局执行上下文”的路径来查找变量
在对象的内部方法中使用对象内部的属性是一个普遍需求,由此产生了 this 机制
设置函数执行上下文的this值: (1) 调用 call/bind/apply 方法 (2) 调用一个对象的内部方法,this 指向该对象 (3) new 构造函数
this设计缺陷及解决方法: (1) 嵌套函数的 this 不会从外层函数中继承,可以修改为箭头函数,也可以将 this 保存为 self 变量再用 (2) 非严格模式下函数被正常调用时 this 指向全局对象,严格模式则指向 undefined
v8 工作原理
js 是动态语言,弱类型语言: (1) 静态语言会在使用之前确认变量的数据类型,动态语言会在运行过程中检查变量的数据类型 (2) 强类型语言不支持隐式类型转换,弱类型语言支持隐式类型转换
js 数据类型包括: (1) 原始类型;Boolean、Number、String、Null、Undefined、Symbol、BigInt (2) 引用类型;Object
js 执行过程中有三种类型的内存空间: (1) 代码空间;存储可执行代码 (2) 栈空间:存储执行上下文,包括原始类型的值和引用类型的值地址 (3) 堆空间:存储引用类型的值
js 引擎扫描到内部函数引用外部函数的变量时会判断为一个闭包,然后会在堆空间中创建一个闭包对象
ESP 是记录当前执行状态的指针,当一个函数执行结束之后,js 引擎会向下移动 ESP,该函数的执行上下文会被之后的执行上下文覆盖掉,即被销毁
代际假说: (1) 大部分对象在内存中存在的时间很短,即很多对象分配内存之后很快就变得不可访问 (2) 不死的对象会活得更久
分代收集: (1) 新生代区域:存放生存时间短的对象 只支持1~8M 的容量,副垃圾回收器主要负责新生代的垃圾回收 (2) 老生代区域:存放生存时间长的对象,容量比较大,主垃圾回收器主要负责老生代的垃圾回收
垃圾回收器的通用工作流程: (1) 标记空间中活动对象(在使用对象)和非活动对象(可回收对象) (2) 统一清理非活动对象 (3) 如果频繁回收对象产生了内存碎片,需要整理内存
副垃圾回收器: (1) Scavenge 算法:新生区划为两个区域,一半是对象区域,一半是空闲区域 (2) 新加入的对象会存放在对象区域,对象区域快被写满时,执行一次垃圾清理操作 (3) 垃圾回收时,先标记对象区域,活动对象会被复制到空闲区域中并有序排列,非活动对象会被统一清理 (4) 将对象区域与空闲区域进行角色反转,由此实现区域的无限重复使用 (5) js 引擎采用“对象晋升策略”,即经过两次垃圾回收之后依然存活的对象会被移动到老生代区域中
主垃圾回收器: (1) 占用空间大的对象会被直接分配给 老生区 (2) 标记-清除 算法:在标记阶段,遍历调用栈(不是每个对象都要调用吧?),能找到的为活动对象,找不到的为垃圾数据并被标记为红色;在清除阶段,清理红色标记数据,会产生不连续的内存碎片 (3) 标记-整理 算法:在标记阶段,保持不变;在清除阶段,让所有活动对象向一端移动,然后清理边界以外的内存
全停顿:执行垃圾回收算法时会暂停正在执行的 js 脚本,在回收完毕之后再恢复脚本执行
为减少老生代因垃圾回收造成的卡顿,v8 将标记过程分成一个个子标记过程,让垃圾回收标记和应用逻辑交替进行,直到标记完成
编译型语言:在程序执行之前需要通过编译器得到可重复执行的二进制文件,C/C++/GO
解释型语言:每次运行时需要通过解释器对程序进行动态解释和执行,Python/JavaScript
代码转换器 Babel 的工作原理: (1) 将 ES6 源码转换为 AST (2) 将 ES6 语法的 AST转换为 ES5 语法的 AST (3) 将 ES5 语法的 AST 转换为 js 源码
即时编译 JIT:解释器在执行字节码的同时收集代码信息,当发现热点代码(重复执行的代码)之后将其用编译器转换为机器码并保存起来得以重复执行
浏览器中的页面循环系统
浏览器页面是由消息队列和事件循环系统驱动的
消息队列存放执行任务,多个线程操作同一个消息队列,从尾部添加任务和从头部取出任务时会加上一个同步锁
延迟队列存放需要延迟执行的任务:当 js 调用 setTimeout 设置回调函数时,渲染进程会创建一个回调任务并将其添加到延迟执行队列中,处理完成消息队列的一个任务之后执行延迟队列中的任务,然后再进入下一个循环
使用 setTimeout 的注意事项: (1) 当前执行任务时间过长会影响定时器任务的执行 (2) 如果存在嵌套调用,超过 5 次就会被系统判断为阻塞,如果定时器的调用间隔小于 4 毫秒,浏览器就会将调用间隔设置为 4 毫秒 (3) 未激活页面中定时器的调用间隔最小为 1000 毫秒,是浏览器为了优化后台页面的加载损耗 (4) 浏览器以 32bit来存储延时值,当定时器的调用间隔大于 2147483647 (2^31-1)毫秒(约 24.8 天)时会溢出,定时器会立即执行
1个字节为8个比特,1个英文字母占1个字节,1个汉字占2个字节
渲染进程专门有一个 IO 线程来接收其他进程传进来的消息,将这些消息组装成任务添加到消息队列,渲染主线程会循环地从消息队列头部读取任务并执行,当循环系统在执行一个任务时会为当前任务维护一个系统调用栈
安全退出:确定要退出当前页面时,页面主线程会设置一个退出标志的变量,在每次执行完一个任务时会判断是否有设置退出标志,若已设置就直接中断当前所有任务并退出线程
宏任务与微任务: (1) 消息队列中的任务称为宏任务,每个宏任务中都包含一个微任务队列(宏任务队列和微任务队列的具体位置是在?),该队列是 v8 引擎在创建全局上下文(为什么是全局执行上下文,那函数执行上下文呢?)的同时创建的内部队列,用来存放当前宏任务执行过程中产生的全部微任务 (2) 需要异步执行且执行时机是在主函数执行结束之后而当前宏任务执行结束之前的任务称为微任务(当前宏任务[主函数 -> 宏微任务] -> 下一个宏任务) (3) 微任务的产生方式: 使用 MutationObserver 监控 DOM 节点,当 DOM 节点发生变化时 使用 Promise,当调用 Promise.resolve() 或 Promise.reject() 时 (4) 微任务队列的执行时间: 在当前宏任务即将完成,也就是 js 引擎退出全局执行上下文并清空调用栈时,js 引擎会检查全局执行上下文中的微任务队列并按顺序执行队列中的微任务,这个执行微任务的时间点又称为检查点 执行 ParseHTML 宏任务的过程:
回调函数:将一个函数作为参数传递给另一个函数,作为参数的函数就是回调函数 (1) 同步回调:回调函数在主函数的上下文中执行 (2) 异步回调:回调函数在主函数之外执行(异步函数作为一个任务添加到消息队列尾部/异步任务作为一个微任务添加到当前宏任务中)
Mutation Event 改进为 MutationObserver: (1) 同步回调改为异步回调 (2) 将变化记录封装为微任务
XMLHttpRequest 运作机制:
chrome://tracing 和 performance 都可以抓取系统调用栈信息(下拉的长条)
异步编程模型: 回调过多会导致代码的逻辑不连贯、不线性,应该封装这些代码,降低处理异步回调的次数 嵌套太多回调函数会陷入“回调地狱”,应该消灭嵌套调用并合并多个任务的错误处理
Promise 解决问题的思路: (1) 延迟绑定回调函数 (2) 将回调函数的返回值穿透到最外层 (3) 使用最后一个对象来捕获所有异常:Promise 对象的错误具有“冒泡性质”,会一致向后传递,直到被 onReject 函数处理或被 catch 语句捕获