chrome 架构
单进程(单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里)
缺点
不稳定(插件或者渲染进程的崩溃 导致进程崩溃)
- 进程相互隔离的,一个崩溃 不影响两一个
不流畅 脚本插件让单进程卞卡,复杂的页面不完全回收内存也会造成 内存越来越高,运行变慢
- 多个渲染进程,独立的
不安全 插件可以使用 C/C++ 等代码编写,通过插件可以获取到操作系统的任意资源 ,页面脚本,它可以通过浏览器的漏洞来获取系统权限,这些脚本获取系统权限之后也可以对你的电脑做一些恶意的事情
- 进程 放在安全沙箱
多进程架构
GPU
浏览器主进程
- 负责用户交互、子进程管理和文件储存等功能
多个插件进程
多个渲染进行
- 把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面
网络进程
- 面向渲染进程和浏览器进程等提供网络下载功能。
缺点
更高的资源占用 每个进程都会包含公共基础结构的副本
更复杂的体系架构 浏览器各模块之间耦合性高、扩展性差等问题
面向服务架构(SOA)形式
- 原来的各种模块会被重构成独立的服务(Service),每个服务(Service)都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而构建一个更内聚、松耦合、易于维护和扩展的系统,更好实现 Chrome 简单、稳定、高速、安全的目标
线程
线程是不能单独存在的,它是由进程来启动和管理的
进程
概念:进程就是一个程序的运行实例(创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程)
特点
1进程中的任意一线程执行出错,都会导致整个进程的崩溃。
线程之间共享进程中的数据。
当一个进程关闭之后,操作系统会回收进程所占用的内存。
进程之间的内容相互隔离。
浏览器输入url 到页面展示
导航流程
浏览器进程 接受用户输入的url 然后转发给网络进行处理
判断是不是url
如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
如果判断输入内容符合 URL 规则,比如输入的是 time.geekbang.org,那么地址栏会根据规则,把这段内容加上协议,合成为完整的 URL
让当前页面执行beforeunload 操作
- 当前页面一次执行 beforeunload 事件的机会,beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面,比如当前页面可能有未提交完成的表单等情况,因此用户可以通过 beforeunload 事件来取消导航,让浏览器不再执行任何后续工作
网络进程 通过URL发起请求,接着接到响应数据,解析然后传给浏览器进程
查看缓存
如果有缓存资源,那么直接返回资源给浏览器进程
如果在缓存中没有查找到资源,那么直接进入网络请求流程
DNS解析获取ip地址,如果https 还需要tsl链接
利用 IP 地址和服务器建立 TCP 连接,添加请求头等,发送请求
接受数据(服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程)
重定向
- 果发现返回的状态码是 301 或者 302,那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了。
正常的话响应数据处理
contentTpe
下载类型
- 那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束
其他类型 html类型
- 如果是 HTML,那么浏览器则会继续进行导航流程
浏览器进程接到网络数据,发起提交导航消息到渲染进程
渲染进程(因为网络获取数据不安全,所以需要安全沙箱)接到导航消息然后便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道;
通常情况下,打开新的页面都会使用单独的渲染进程;
如果是同一个站点(同一域名,同一协议 不同端口 )会公用一个渲染进程
最后渲染进程会向浏览器进程“确认提交”
- 会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
浏览器进程接收到渲染进程“提交文档”的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。
- 渲染阶段
渲染流程
主线程
构建DOM树
- HTML 转换为浏览器能够理解的结构——DOM 树
样式计算
CSS 转换为浏览器能够理解的结构styleSheets。
通过 link 引用的外部 CSS 文件
style>标记内的 CSS
元素的 style 属性内嵌的 CSS
转换样式表中的属性值,使其标准化
计算出 DOM 树中每个节点的具体样式
css层叠原则
- 层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。
css继承原则
- CSS 继承就是每个 DOM 节点都包含有父节点的样式
布局(DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局)
创建布局树
遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
而不可见的节点会被布局树忽略掉
图层绘制
- 会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,提交到合成线程
分层—图层树
为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree) 这些图层叠加后合成了最终的页面。
第一点,拥有层叠上下文属性的元素会被提升为单独的一层(,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素)
第二点,需要剪裁(clip)的地方也会被创建为图层。
合成线程
合成线程会将图层划分为图块—并且光栅化线程池中将图块转换成位图
删格化
栅格化,是指将图块转换为位图(块是栅格化执行的最小单位)
渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的
GPU 加速 删格化并把位图保存在GPU中
合成–一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览进程
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
性能优化
更新了元素的几何属性(重排) 重排需要更新完整的渲染流水线。 也会引起重绘
更新元素的绘制属性(重绘)布局和分层阶段
直接合成阶段
- 我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段
执行机制
JavaScript 代码的执行流程
编译阶段(变量和函数的位置在编译阶段,放在内存中)将代码生成 上下文和可执行代码
- 同样的变量和函数 第二个会覆盖第一个
JavaScrip 的执行上下文(JavaScript 执行一段代码时的运行环境)
全局代码 全局上下文
调用函数 函数上下文
eval 函数 执行上下文
执行上下文栈(调用栈)管理执行上下文的,在执行上下文创建好压入栈中
在开发中,如何利用好调用栈
- 如何利用浏览器查看调用栈的信息(1.在分析复杂结构代码,或者检查 Bug 时,调用栈都是非常有用的。2. 除了通过断点来查看调用栈,你还可以使用 console.trace() 来输出当前的函数调用关系,比如在示例代码中的 add 函数里面加上了 console.trace(),你就可以看到控制台输出的结果,如下图:)
- 栈溢出(Stack Overflow)当入栈的执行上下文超过一定数目,JavaScript 引擎就会报错,我们把这种错误叫做栈溢出
- 比如把递归调用的形式改造成其他形式,或者使用加入定时器的方法来把当前任务拆分为其他很多小任务。
可执行代码
执行阶段
v8工作原理
语言类型
动态语言
- ,我们把在运行过程中需要检查数据类型的语言称为动态语言
静态语言
- 我们把这种在使用之前就需要确认其变量数据类型的称为静态语言。
弱类型
- 而支持隐式类型转换的语言称为弱类型语言,
强类型
- 不支持隐式类型转换的语言称为强类型语言
数据类型
原始类型
Boolean
null
undedined
biginit
symbol
string
number
引用类型
- object
判断数据类型方法
typeof
判断引用类型,除了function 全返回object类型
判断基础类型 除了null返回的是object,其他都可返回正确的类型
null 为空原因
null被认为是一个空对象,因此返回了object
因为任何对象都会被转化为二进制,null转为二进制则表示全为0,如果前三个均为0,js就会把它当作是对象,这是js早期遗留下来的bug
instanceof
只能用来判断变量的原型链上是否有构造函数的prototype属性(两个对象是否属于原型链的关系),不一定能获取对象的具体类型
Instanceof 不适用判断原始类型的值,只能用于判断对象是否从属关系
- 为什么 [] instanceof Array 为true。
- 基于原型链的原理:从实例对象的构造函数的原型开始向上寻找,构造函数的原型又有其原型,一直向上找,直到找到原型链的顶端Object.prototype为止。如果没有,则返回null
- constructor
- 原理:每一个实例对象都可通过constructor来访问它的构造函数,其实也是根据原型链的原理来的。
- 由于undefined和null是无效的对象,因此是没有constructor属性的,这两个值不能用这种方法判断.
- Object.prototype.toString
- 因为实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,所以在使用时,最好加上call
Object.prototype.toString.call
内存空间
栈空间 管所有的执行上下文
原始类型的数据值 比较小都是直接保存在“栈”中的
如果都放在栈里把数据 会影响到上下文切换的效率,进而又影响到整个程序的执行效率
堆空间
引用类型的值比较大是存放在“堆”中的
不过缺点是分配内存和回收内存都会占用一定的时间。
原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。
闭包
第一步是需要预扫描内部函数
第二步是把内部函数引用的外部变量保存到堆中(创建换一个“closure(foo)”的对象(这是一个内部对象,JavaScript 是无法访问的))。
代码空间
垃圾回收机制
自动回收
JavaScript、Java、Python 等语言,产生的垃圾数据是由垃圾回收器来释放的,并不需要手动通过代码来释放
调用栈中的数据是如何回收的
一个记录当前执行状态的指针(称为 ESP,当前函数执行完后让ESP指针下移就是销毁
当全部执行完后 ESP 应该是指向全局执行上下文的
堆中的数据是如何回收的
JavaScript 中的垃圾回收器(垃圾回收算法很多,不能完全胜任)
代际假说
第一个是大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得死了
第二个是不死的对象,会活得更久
堆分为
新生代区域(生存时间短的对象)1~8M
副垃圾回收器(新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。)
Scavenge 算法 实现
分为对象区域和空闲区域
1.首先要对对象区域中的垃圾做标记
2,将对象区域内存货对象复制到空闲区域,同时有序排列,这就是内存整理了
3.,对象区域与空闲区域进行角色翻转,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。因为每次回收都需要复制,需要时间成本所以不能太大
因为新生区的空间不大,JavaScript 引擎采用了对象晋升策略,也就是经过两次垃圾回收依然还存活的对象,会被移动到老生区中。
老生代区域(放的生存时间久的对象)
主垃圾回收器
标记 - 清除(Mark-Sweep)的算法
1.首先是标记过程阶段。标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
标记 - 整理(Mark-Compact)的算法
增量标记(Incremental Marking)算法
为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法。如下图所示:
新老生代回收器执行流程
1.标记空间中活动对象和非活动对象
2.回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
3.做内存整理,频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片
全停顿
- 待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)。
手动回收
- C/C++ 就是使用手动回收策略,何时分配内存、何时销毁内存都是由代码控制的
v8执行代码语言分类
编译型语言(c C++ go
编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了
源代码
AST
中间代码
二进制文件
执行
解释型语言 js,phyon
解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行
源代码
babel
- Babel 的工作原理就是先将 ES6 源码转换为 AST,然后再将 ES6 语法的 AST 转换为 ES5 语法的 AST,最后利用 ES5 的 AST 生成 JavaScript 源代码。
eslint
- ESLint 是一个用来检查 JavaScript 编写规范的插件,其检测流程也是需要将源码转换为 AST,然后再利用 AST 来检查代码规范化的问题。
词法分析(分词)
- 将一行行的源码拆解成一个个 token。所谓 token,最小单个字符
语法分析(解析)
- 是将上一步生成的 token 数据,根据语法规则转为 AST
AST
字节码
- 介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行
执行
即时编译(JIT)
- 它发现某一部分代码变热了之后,即时 编译器便闪亮登场,把热点的字节码转换为机器码,并把转换后的机器码保存起来,以备下次使用。
页面循环系统(渲染进程 ㊗️线程)
实现原理
事件循环机制
作用 :要想在当前线程运行过程中,能接收并执行新的任务
引入了循环机制,具体实现方式是在线程语句最后添加了一个 for 循环语句,线程会一直循环执行。
引入了事件,可以在线程运行过程中,等待用户输入的数字,等待过程中线程处于暂停状态,一旦接收到用户输入的信息,那么线程会被激活,然后执行相加运算,最后输出结果。
消息队列(可以存放要执行的任务)
作用:处理其他线程发送过来的任务
步骤
添加一个消息队列;
IO 线程中产生的新任务添加进消息队列尾部
渲染主线程会循环地从消息队列头部中读取任务,执行任务。
同步锁
- 多个线程操作同一个消息队列,所以在添加任务和取出任务时还会加上一个同步锁
任务分类(何处理高优先级的任务。)
宏任务(都包含一个微任务队列)
渲染事件(如解析 DOM、计算布局、绘制)
用户交互事件(如鼠标点击、滚动页面、放大缩小等)
JavaScript 脚本执行事件;
网络请求完成、文件读写完成事件。
settimeout 触发的回调函数
微任务队列(主函数执行结束之后、当前宏任务结束之前执行回调函数)
无法通过 JavaScript 直接访问的,微任务队列
分类
Promise
当调用 Promise.resolve() 或者 Promise.reject() 的时候
解决的问题
封装异步代码,让处理流程变得线性
新的问题:回调地狱(Promise:消灭嵌套调用和多次错误处理)
首先,Promise 实现了回调函数的延时绑定
其次,需要将回调函数 onResolve 的返回值穿透到最外层
promise相关问题
Promise 中为什么要引入微任务?
- promise采用.then延时绑定回调机制,而new Promise时又需要直接执行promise中的方法,即发生了先执行方法后添加回调的过程,此时需等待then方法绑定两个回调后才能继续执行方法回调,便可将回调添加到当前js调用栈中执行结束后的任务队列中,由于宏任务较多容易堵塞,则采用了微任务
2、Promise 中是如何实现回调函数返回值穿透的?
- 首先Promise的执行结果保存在promise的data变量中,然后是.then方法返回值为使用resolved或rejected回调方法新建的一个promise对象,通过 new promise 创建新对象然后把data 给它
- Promise 出错后,是怎么通过“冒泡”传递给最后那个捕获
- promise内部有resolved_和rejected_变量保存成功和失败的回调,进入.then(resolved,rejected)时会判断rejected参数是否为函数,若是函数,错误时使用rejected处理错误;若不是,则错误时直接throw错误,如果都没有捕获到可以用监听unhandledrejection事件捕获未处理的promise错误
- MutationObserver、
- 当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务。
- 原理
- 异步调用,可以不用在每次 DOM 变化都触发异步调用,而是等多次 DOM 变化后,一次触发异步调用(通过异步操作解决了同步操作的性能问题;)
- 通过微任务解决了实时性的问题。
- gennerator
- 协程(一个线程中多个协程,只能同时执行一个协程)
- 协程是一种比线程更加轻量级的存在
- 优点
- 程序所控制,不会像线程切换那样消耗资源
- 生成器
- 生成器函数是一个带星号函数,而且是可以暂停执行和恢复执行的
- 如果遇到 yield 关键字,返回关键字后边内容给外边,然后暂停。会保存当前栈信息,然后恢复父协程的信息
- 通过 next 方法恢复函数的执行
- return 停止
- async/await
- 概念
- 提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力
- 生成器配合执行器实现的aysnc/await
- async
- 异步执行和隐式返回 Promise
- await
- await 100时,会默认创建一个 Promise 对象
- 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。
- 回调功能
- 也就是让要执行的 JavaScript 任务滞后执行,解决单个任务执行时长过久的问题。
IO进程
作用 :处理其他进程发送过来的任务
渲染进程专门有一个 IO 线程用来接收其他进程传进来的消息,接收到消息之后,会将这些消息组装成任务发送给渲染主线程,
如何安全退出
- 确定要退出当前页面时,页面主线程会设置一个退出标志的变量,在每次执行完一个任务时,判断是否有设置退出标志
延迟执行任务列表(定时器和 Chromium 内部一些需要延迟执行的任务)
settimeout
通过定时器设置回调函数有点特别,它们需要在指定的时间间隔内被调用,不能影响消息队列函数执行,所以不放在消息队列中
注意实现
如果当前任务执行时间过久,会影响定时器任务的执行
如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫
- 是因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒
未激活的页面,setTimeout 执行最小间隔是 1000 毫秒
- 目的是为了优化后台页面的加载损耗以及降低耗电量
延时执行时间有最大值
- 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,大约 24.8 天)时就会溢出,那么相当于延时值被设置为 0 了
使用 setTimeout 设置的回调函数中的 this 不符合直觉
如果被 setTimeout 推迟执行的回调函数是某个对象的方法,那么该方法中的 this 关键字将指向全局环境,而不是定义时所在的那个对象
var name= 1;var MyObj = { name: 2, showName: function(){ console.log(this.name); }}setTimeout(MyObj.showName,1000)
第一种是将MyObj.showName放在匿名函数中执行,如下所示:
第二种是使用 bind 方法,将 showName 绑定在 MyObj 上面,代码如下所示:
执行时间
- 一个消息队列任务执行后
—XMLHttpRequest
在 XMLHttpRequest 出现之前,如果服务器数据有更新,依然需要重新刷新整个页面。而 XMLHttpRequest 提供了从 Web 服务器获取数据的能力
同步回调
- 回调函数 callback 是在主函数 doWork 返回之前执行的
异步回调
我们使用了 setTimeout 函数让 callback 在 doWork 函数执行结束后,又延时了 1 秒再执行,这次 callback 并没有在主函数 doWork 内部被调用
第一种是把异步函数做成一个任务,添加到信息队列尾部;
第二种是把异步函数添加到微任务队列中,这样就可以在当前任务的末尾处执行微任务了。
存在的问题
跨域问题
- HTTPS 混合内容的问题
- TTPS 页面中包含了不符合 HTTPS 安全要求的内容,比如包含了 HTTP 资源,通过 HTTP 加载的图像、视频、样式表、脚本等,都属于混合内容。通常,如果 HTTPS 请求页面中使用混合内容,浏览器会针对 HTTPS 混合内容显示警告,用来向用户表明此 HTTPS 页面包含不安全的资源。比如打开站点 https://www.iteye.com/groups ,可以通过控制台看到混合内容的警告,参考下图
网络
数据传输
TCP 传输层
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP 头除了包含了目标端口和本机端口号外,还提供了用于排序的序列号,以便接收端通过序号来重排数据包。
TCP的传输过程
一个完整的 TCP 连接的生命周期包括了“建立连接”“传输数据”和“断开连接”三个阶段。
首先,建立连接阶段。这个阶段是通过“三次握手”来建立客户端和服务器之间的连接。TCP 提供面向连接的通信传输
其次,传输数据阶段。在该阶段,接收端需要对每个数据包进行确认操作 接收方接到数据需要发确认数据包给发送方,如果没有收到,需要触发重发机制
最后,断开连接阶段。数据传输完毕之后,就要终止连接了,涉及到最后一个阶段“四次挥手”来保证双方都能断开连接。
三次握手
客户端发送syn 包给服务端,进入SYN_SENT状态 等待服务器确认
服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包+确认包ack,此时服务器进入SYN_RECV状态
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手
四次挥手
客户端进程发出连接释放报文,并且停止发送数据,客户端进入FIN-WAIT-1(终止等待1)状态
服务器收到连接释放报文,发出确认报文,服务端就进入了CLOSE-WAIT(关闭等待)状态,TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
- 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
- 为什么连接的时候是三次握手,关闭的时候却是四次握手?
- 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。 但是断开的时候 只回复一个确认,但是有数据还没发完,所以又一个二次确认的过程
- 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态
- 必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文
- 为什么不能用两次握手进行连接?
- 现在把三次握手改成仅需要两次握手,死锁是可能发生的
- 如果已经建立了连接,但是客户端突然出现故障了怎么办
- TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接
UDP 传输层
基于ip上开发的用户数据包协议
IP 通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序。
udp 传输过程
上层将含有“极客时间”的数据包交给传输层
传输层会在数据包前面附加上 UDP 头,组成新的 UDP 数据包,再将其交给网络层;
网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层
数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层
在传输层,数据包中的 UDP 头会被拆开,并根据 UDP 中所提供的端口号,把数据部分交给上层的应用程序
udp优缺点
传输速度非常快
对于错误的数据没有重发机制,所以会丢包
大文件传输,不知道怎么组装
IP
计算机的地址就称为 IP 地址
ip 数据包传输过程
上层将数据包交给网络层
网络层把将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层;
底层通过物理网络将数据包传输给主机 B;
数据包被传输到主机 B 的网络层,在这里主机 B 拆开数据包的 IP 头信息,并将拆开来的数据部分交给上层;iP非常底层的协议,只负责把数据包传送,但是对方电脑并不知道把数据包交给谁
http 应用层(正是建立在 TCP 连接基础之上)
tcp 与http 的关系
- HTTP协议和TCP协议都是TCP/IP协议簇的子集。
HTTP协议属于应用层,TCP协议属于传输层,HTTP协议位于TCP协议的上层。
请求方要发送的数据包,在应用层加上HTTP头以后会交给传输层的TCP协议处理,应答方接收到的数据包,在传输层拆掉TCP头以后交给应用层的HTTP协议处理。建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规范构建和解析HTTP报文
- 浏览器端发起 HTTP 请求流程
1.1. 构建请求首先,浏览器构建请求行信息(如下所示),构建好后,浏览器准备发起网络请求。GET /index.html HTTP1.1
2.查找缓存
3/ 准备 IP 地址和端口 ,你会发现在第一步浏览器会请求 DNS 返回域名对应的 IP。当然浏览器还提供了 DNS 数据缓存服务
4. 等待 TCP 队列
5. 建立 TCP 连接
66. 发送 HTTP 请求
- 服务器端处理 HTTP 请求流程
1. 返回请求
2. 断开连接
3. 重定向
- 1. 为什么很多站点第二次打开速度会很快?
- DNS 缓存
- 页面资源缓存
- 浏览器根据http响应头 cache-control和 Max-age
- 服务器通过 ETag & If-None-Match
的 如果没有更新, 文件修改时间Last-Modified & if-modified-since:
就返回 304 状态码
- 304的缓存原理
- 1.服务器首先产生Etag,服务器可在稍后使用它来判断页面是否被修改。本质上客户端通过该记号传回服务器要求服务器验证(客户端)缓存。
- 2.304是HTTP的状态码,服务器用来标识这个文件没有被修改,不返回内容,浏览器接受到这个状态码会去去找浏览器缓存的文件。
- 3.流程:
- 客户端请求一个页面A。服务器返回页面A,并在A上加一个Tage客服端渲染该页面,并把Tage也存储在缓存中。客户端再次请求页面A。
- 并将上次请求的资源和ETage一起传递给服务器。服务器检查Tage.并且判断出该页面自上次客户端请求之后未被修改。直接返回304。
- last-modified: 客服端请求资源,同时有一个last-modified的属性标记此文件在服务器最后修改的时间
- 客服端第二次请求此url时,根据http协议。浏览器会向服务器发送一个If-Modified-Since报头,
- 询问该事件之后文件是否被修改,没修改返回304。
- 有了Last-Modified,为什么还要用ETag?
- 1、因为如果在一秒钟之内对一个文件进行两次更改,Last-Modified就会不正确(Last—Modified不能识别秒单位的修改)
- 2、某些服务器不能精确的得到文件的最后修改时间
- 3、一些文件也行会周期新的更改,但是他的内容并不改变(仅仅改变修改的事件),这个时候我们并不希望客户端认为文件被修改,而重新Get。
- http发展历史
- HTTP/0.9
- 网络之间传递 HTML 超文本的内容,所以被称为超文本传输协议(似GET /index.html的简单请求命令)
- 特点
- 一个是只有一个请求行,并没有 HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
- 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了
- 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的
- HTTP/1.0
- 通过请求头和响应头来支持多种不同类型的数据
- 根据服务器数据类型,浏览器进行处理
- 压缩类型
- 供国际化的支持
- 编码形式
- 状态码
- 提供了 Cache 机制
- 要统计客户端的基础信息,还加入了用户代理的字段
- 缝缝补补的 HTTP/1.1
- HTTP/1.1 中增加了持久连接的方法,
- 在一个 TCP 连接上可以传输多个 HTTP 请求,只要浏览器或者服务器没有明确断开连接,那么该 TCP 连接会一直保持。(Connection字段,最多6个TCP持久链接)
- 解决队头阻塞(
- 管线化的技术来解决队头阻塞的问题
- HTTP/1.1 中的管线化是指将多个 HTTP 请求整批提交给服务器的技术,虽然可以整批发送请求,不过服务器依然需要根据请求顺序来回复浏览器的请求。
- 果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,)
- HTTP/1.1 的请求头中增加了 Host 字段
- 用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。
- 动态生成的内容提供了完美支持
- Chunk transfer 机制(服务器会将数据分割成若干个任意大小的数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。)
- 客户端 Cookie、安全机制
- 缺点
- HTTP/1.1对带宽的利用率却并不理想
- 第一个原因,TCP 的慢启动。
- 一旦一个 TCP 连接建立之后,就进入了发送数据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。
- 第二个原因,同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽。
- 第三个原因,HTTP/1.1 队头阻塞的问题。
- HTTP/2
- HTTP/2 的多路复用
- 一个域名只使用一个 TCP 长连接,避免多个tcp竞争
- 要实现资源的并行请求消除队头阻塞问题
- HTTP/2 添加了一个二进制分帧层(转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器)
- 1. 可以设置请求的优先级
- 服务器推送
- 头部压缩
- HTTP/3
- 继续解决TCP队头阻塞
- TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。
- 随着丢包率的增加,HTTP/2 的传输效率也会越来越差,当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好
- 解决TCP 建立连接的延时
- 连接迁移
- QUIC基于UDP协议,所以一条UDP协议不再由四元组标识,而是以客户端随机产生的一个64位数字作为ID标识。只要ID不变,那么这条UDP就会存在,维持连接,上层业务逻辑就感受不到变化。
- 解决TCP 协议僵化
- 除了中间设备僵化外,操作系统也是导致 TCP 协议僵化的另外一个原因
- 挑战
- 服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持
- 部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度
- 中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。
安全
Web 页面安全
同源策略
同一协议 同一域名 同一端口
三个层面
DOM 层面
- 同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
数据层面
- 同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据
网络层面。
- 同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点
不过安全性和便利性是相互对立的,让不同的源之间绝对隔离,无疑是最安全的措施,但这也会使得 Web 项目难以开发和使用。 出让了同源策略的哪些安全性。
页面中可以嵌入第三方资源
XSS
为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。
跨域资源共享和跨文档消息机制
我们引入了跨域资源共享(CORS),使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。
浏览器中又引入了跨文档消息机制,可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信。
攻击
XSS 攻击
黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段
攻击类型
存储型 XSS 攻击(将恶意代码放在漏洞的服务器,访问页面的时候,用户信息被上传)
反射型 XSS 攻击
Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方。
我们会发现用户将一段含有恶意代码的请求提交给 Web 服务器,Web 服务器接收到请求时,又将恶意代码反射给了浏览器端,这就是反射型 XSS 攻击。
- 基于 DOM 的 XSS 攻击
- 黑客通过各种手段将恶意脚本注入用户的页面中(有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据)
危害
可以窃取 Cookie 信息。(恶意 JavaScript 可以通过“document.cookie”获取 Cookie 信息,然后通过 XMLHttpRequest 或者 Fetch 加上 CORS 功能将数据发送给恶意服务器;恶意服务器拿到用户的 Cookie 信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作)
可以监听用户行为。可以使用“addEventListener”接口
可以通过修改 DOM 伪造假的登录窗口,用来欺骗用户输入用户名和密码等信
还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。
如何阻止 XSS 攻击
- 服务器对输入脚本进行过滤或转码
- 充分利用 CSP
- 使用 HttpOnly 属性
CSRF 攻击
CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事
类型
- 自动发起 Get 请求
- 自动发起 POST 请求
- 引诱用户点击链接
如何防止 CSRF 攻击
- 充分利用好 Cookie 的 SameSite 属性
- 验证请求的来源站点
- CSRF Token认证
浏览器系统安全
单进程架构的浏览器是不稳定的,
黑客就有可能通过恶意的页面向浏览器中注入恶意程序,其中最常见的攻击方式是利用缓冲区溢出,不过需要注意这种类型的攻击和 XSS 注入的脚本是不一样的。
XSS 攻击只是将恶意的 JavaScript 脚本注入到页面中,虽然能窃取一些 Cookie 相关的数据,但是 XSS 无法对操作系统进行攻击
而通过浏览器漏洞进行的攻击是可以入侵到浏览器进程内部的,可以读取和修改浏览器进程内部的任意内容,还可以穿透浏览器,在用户的操作系统上悄悄地安装恶意软件、监听用户键盘输入信息以及读取用户硬盘上的文件内容。
安全视角下的多进程架构
为什么一定要通过浏览器内核去请求资源,再将数据转发给渲染进程,而不直接从进程内部去请求网络资源?
为什么渲染进程只负责生成页面图片,生成图片还要经过 IPC 通知浏览器内核模块,然后让浏览器内核去负责展示图片
站点隔离
- 指 Chrome 将同一站点(包含了相同根域名和相同协议的地址)中相互关联的页面放到同一个渲染进程中执行。
浏览器网络安全
https
加密方案
对称加密
浏览器发送它所支持的加密套件列表和一个随机数 client-random,这里的加密套件是指加密的方法,加密套件列表就是指浏览器能支持多少种加密方法列表。
服务器会从加密套件列表中选取一个加密套件,然后还会生成一个随机数 service-random,并将 service-random 和加密套件列表返回给浏览器
最后浏览器和服务器分别返回确认消息
使用非对称加密
首先浏览器还是发送加密套件列表给服务器。
然后服务器会选择一个加密套件,不过和对称加密不同的是,使用非对称加密时服务器上需要有用于浏览器加密的公钥和服务器解密 HTTP 数据的私钥,由于公钥是给浏览器加密使用的,因此服务器会将加密套件和公钥一道发送给浏览器。
最后就是浏览器和服务器返回确认消息。
缺点
第一个是非对称加密的效率太低
第二个是无法保证服务器发送给浏览器的数据安全。
- 但是服务器端只能采用私钥来加密,私钥加密只有公钥能解密,但黑客也是可以获取得到公钥的,这样就不能保证服务器端数据的安全了
第三版:对称加密和非对称加密搭配使用(pre-master 是经过公钥加密之后传输的,所以黑客无法获取到 pre-master,这样黑客就无法生成密钥,也就保证了黑客无法破解传输过程中的数据了)
首先浏览器向服务器发送对称加密套件列表、非对称加密套件列表和随机数 client-random;
服务器保存随机数 client-random,选择对称加密和非对称加密的套件,然后生成随机数 service-random,向浏览器发送选择的加密套件、service-random 和公钥
浏览器保存公钥,并生成随机数 pre-master,然后利用公钥对 pre-master 加密,并向服务器发送加密后的数据;
最后服务器拿出自己的私钥,解密出 pre-master 数据,并返回确认消息
添加数字证书
一个是通过数字证书向浏览器证明服务器的身份
另一个是数字证书里面包含了服务器公钥
相较于第三版的 HTTPS 协议,这里主要有两点改变:
服务器没有直接返回公钥给浏览器,而是返回了数字证书,而公钥正是包含在数字证书中的;
在浏览器端多了一个证书验证的操作,验证了证书之后,才继续后续流程
页面性能优化
网络
优化时间线上耗时项
- 排队(Queuing)时间过久(大概率是由浏览器为每个域名最多维护 6 个连接导致的)
那么基于这个原因,你就可以让 1 个站点下面的资源放在多个域名下面,比如放到 3 个域名下面,这样就可以同时支持 18 个连接了,这种方案称为域名分片技术。
把站点升级到 HTTP2,因为 HTTP2 已经没有每个域名最多维护 6 个 TCP 连接的限制了。
第一字节时间(TTFB(反映服务端响应速度的重要指标))时间过久
服务器生成页面数据的时间过久
- 可以想办法去提高服务器的处理速度,比如通过增加各种缓存的技术
网络的原因。
- 使用 CDN 来缓存一些静态文件
发送请求头时带上了多余的用户信息。
- 你在发送请求时就去尽可能地减少一些不必要的 Cookie 数据信息。
- Content Download 时间过久
- 需要减少文件大小,比如压缩、去掉源码中不必要的注释等方法
js 执行优化
提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程,这样可以使得页面快速响应交互;
避免大的内联脚本,因为在解析 HTML 的过程中,解析和编译也会占用主线程
减少 JavaScript 文件的容量,因为更小的文件会提升下载速度,并且占用更低的内存
dom
生程dom的步骤
先默认创建了一个根为 document 的空 DOM 结构
第一个阶段,通过分词器将字节流转换为 Token。
至于后续的第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中
token 栈处理规则(HTML 解析器维护了一个 Token 栈结构)
如果是startTag 那就会创建一个dom节点加入dom树,父节点是栈中相邻节点
如果是endTag,就会把startTag 弹出,表示解析完成
如果是文本节点,那么加入dom树,文本不需要入栈,
解析时机
- 网络进程加载了多少数据,HTML 解析器便解析多少数据。
当碰到js事
- 因为 JavaScript 文件的下载过程会阻塞 DOM 解析
优化
预解析操作
- 优化预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件
压缩js
CDN 来加速 JavaScript 文件的加载
JavaScript 文件中没有操作 DOM 相关代码 使用异步加载,
async
- 标志的脚本文件一旦加载完成,会立即执行
defer
- 需要在 DOMContentLoaded 事件之前执行。
css
CSSOM(体现在DOM 中就是document.styleSheets)
第一个是提供给 JavaScript 操作样式表的能力
二个是为布局树的合成提供基础的样式信息。
渲染阶段
第一个阶段,等请求发出去之后,到提交数据阶段
- 网络
第二个阶段,提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,
通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了
但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件
还可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 async 或者 defer。
对于大的 CSS 文件,可以通过媒体查询属性,将其拆分为多个不同用途的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。
第三个阶段,等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。
分层和合成机制
显示器显示图像
显示器所做的任务很简单,就是每秒固定读取 60 次前缓冲区中的图像,并将读取的图像显示到显示器上 (前缓存区更新图像)
显卡的职责就是合成新的图像,并将图像保存到后缓冲区中 (后缓存区保存图像)
系统就会让后缓冲区和前缓冲区互换(这样就能保证显示器能读取到最新显卡合成的图像。)
帧
- 把渲染流水线生成的每一副图片称为一帧
帧率
- ,把渲染流水线每秒更新了多少帧称为帧率
重绘
重排
合成
分层
为什么有分层
- 没有采用分层机制,从布局树直接生成目标图片的话,那么每次页面有很小的变化时,都会触发重排或者重绘机制
步骤
渲染引擎会根据布局树的特点将其转换为层树(Layer Tree)
层树中的每个节点都对应着一个图层,下一步的绘制阶段就依赖于层树中的节点
有了绘制列表之后,就需要进入光栅化阶段了,
光栅化就是按照绘制列表中的指令生成图片。每一个图层都对应一张图片
合成线程有了这些图片之后,会将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区
分块(合成之前的中间步骤,提高渲染效率)
合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块
在首次合成图块的时候使用一个低分辨率的图片。提高图块渲染速度
合成
- 合成线程中进行,不影响主线程渲染流程
性能优化
will change处理 CSS 特效或者动画
好处让它为该元素准备独立的层
坏处 它占用的内存也会大大增加
页面有三个阶段
加载阶段(阻塞网页首次渲染的资源称为关键资源)
第一个是关键资源个数
内联JavaScript 和 CSS
async 或者 defer js
css 添加媒体取消阻止显现的标志
关键资源大小。
压缩
移除不必要的代码
求关键资源需要多少个 RTT(Round Trip Time)表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。
- CDN 来减少每次 RTT 时长
交互阶段
减少 JavaScript 脚本执行时间
一种是将一次执行的函数分解为多个任务,使得每次的执行时间不要过久
另一种是采用 Web Workers。
避免强制同步布局(JavaScript 强制将计算样式和布局操作提前到当前的任务中。)
- 在修改dom之前查询相关值
避免布局抖动
- ,在 foo 函数内部重复执行计算样式和布局,这会大大影响当前函数的执行效率。这种情况的避免方式和强制同步布局一样,都是尽量不要在修改 DOM 结构时再去查询一些相关值。
- 合理利用 CSS 合成动画
关闭阶段
避免频繁的垃圾回收
- 如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。
虚拟dom
react 递归算法当虚拟dom 很复杂的时候会阻碍主线程,造成页面等待,卡顿,所以重构fiber架构
双缓存机制
和图形显示一样,它会在完成一次完整的操作之后,再把结果应用到 DOM 上,这样就能减少一些不必要的更新,同时还能保证 DOM 的稳定输出。
图像会在将计算的中间结果存放在另一个缓冲区中,等全部计算完成再将完整的图形一次性显示
MVC 模式 视图和数据不通讯
model
view
controller
PWA,全称是 Progressive Web App,渐进式网页应用
PWA 提供了一个渐进式的过渡方案,让 Web 应用能逐步具有本地应用的能力。采取渐进式可以降低站点改造的代价,使得站点逐步支持各项新技术,而不是一步到位。
PWA 技术也是一个渐进式的演化过程,在技术层面会一点点演进,比如逐渐提供更好的设备特性支持,不断优化更加流畅的动画效果,不断让页面的加载速度变得更快,不断实现本地应用的特性。
web应用与本地应用的对比
Web 应用缺少离线使用能力
Web 应用还缺少了消息推送的能力
最后,Web 应用缺少一级入口,
WebComponent 组件化
对内高内聚,对外低耦合
自定义元素
影子 DOM
HTML 模板三种技术,