浏览器工作原理

浏览器发展

单进程 => 多进程(主进程+渲染进程[多个]+插件[多个]+网络+GPU)=> 面向服务

chrome默认策略是每创建一个Tab就创建一个进程,但是还有“同一站点(process-per-site-instance)”的概念,也就是协议+根域名一致就会复用同一个进程,这样他们就能共享同一个JS执行环境

进程和线程概念

线程共享进程的资源

  1. 线程:线程是不能单独存在的,是进行调度的单元,它是由进程启动和管理的
  2. 进程:操作系统挂载运行程序的单元,拥有独立的资源和内存,一个进程就是一个运行实例,启动一个程序,操作系统会创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程

Web页面性能指标

  1. FP(First paint): 页面从开始加载到浏览器首次绘制出像素到屏幕上的时间,也就是页面在屏幕上首次发生视觉变化的时间
  2. FCP(First Contentful Paint): 浏览器首次绘制来自DOM内容的时间,这是用户第一次看到页面内容,但内容不一定有用(比如导航栏、标题等)
  3. FMP(First Mainful Paint): 页面的主要内容绘制到了屏幕的时间,不同类型的网站对主要内容的定义不一样,比如电商网站主要内容就是图片,博客网站主要内容就是标题和摘要等
  4. LCP:页面最大内容绘制时间点
  5. FID:首次输入延迟,发生在FCP和TTI之间, 这个阶段虽然页面已经显示出部分的内容, 但尚不具备完全的可交互性. 这个阶段的用户交互往往有比较大的延迟
  6. FSP(First Screen Paint): 页面开始加载到首屏内容全部绘制完成的时间
  7. TTI(Time To Interactive): 页面第一次加载到完全可交互状态的时间
  8. TBT:页面总阻塞时长,汇总所有加载过程中阻塞用户操作的时长,在FCP和TTI之间任何long task中阻塞部分都会被汇总

LCP用于衡量加载体验<2.5s;FID用于衡量页面可交互性<100ms;CLS用于衡量页面的稳定性

通过浏览器的PerformanceObverse可以获取到部分指标,也可以通过LightHouse生成查看,也可以通过Google提供的web-vital去获取

import {getCLS, getFID, getLCP} from 'web-vitals';

getCLS(console.log, true);
getFID(console.log);
getLCP(console.log, true);

OSI模型和协议

OSI七层模型

应用层(HTTP/FTP/SMTP协议) => 表示层(SSL/TLS协议) => 会话层(RPC) => 传输层(TCP/UDP协议) => 网络层(IP/ICMP) => 数据链路层 => 物理层

IP

数据包传送到目的主机,但无法知道要给电脑哪个程序

UDP

数据包传送到应用程序,通过端口号把数据包发给分发给程序,虽然 UDP 可以校验数据是否正确,但是UDP 并不提供重发机制,只是丢弃当前错误的包,也无法知道是否能发送到目的地。但是UDP的传输速度较快,适合关注速度,但对数据完整性要求没那么高的应用,如在线视频

传输:数据 -> 数据+UDP头(UDP头包含了目标端口号和源端口号,传输层) -> 数据+UDP头+IP头(源ip地址和目标ip地址,网络层)

TCP

数据传输完整,有重排序机制,即使丢包也会补包,会对传输过程中大文件被拆分的小数据块重新按序组装成完整数据。但是缺点就是为了确保数据的完整性,通信之前都需要创建链接,就是经典的“三次握手”,因此速度相对比较慢

传输:数据 -> 数据+TCP头(TCP头包含了目标端口号和源端口号,排序的序列号,传输层) -> 数据+TCP头+IP头(源ip地址和目标ip地址,网络层)

TCP的三次握手:

  1. 第一次:客户端给服务端发送一个带有SYN标志的数据包,并指明客户端的初始化序列号ISN
  2. 第二次:服务端给客户端发送带有SYN和ACK(ISN+1)标志得数据包
  3. 第三次:客户端给服务端发送带有ACK标志的数据包

TCP的四次挥手:

  1. 第一次:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. 第二次:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. 第三次:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. 第四次:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

注:第四次挥手涉及到2MSL等待时长,因为一个数据包的最长存活时长是1MSL,所以当第四次发送包给服务端超时,也就是1MSL后,服务端会重新发送FIN包给客户端,这个过程也涉及到1MSL,所以来回涉及到2MSL

URL访问执行过程

  • 输入URL,浏览器进程通过进程间通信(IPC)将该URL转发给网络进程
  • 网络进程发起真正请求:校验是否有缓存;DNS解析域名获取IP;创建请求行、请求头、请求体、接受响应体
  • 网络进程接收到响应数据,解析响应头,把数据转发给浏览器进程:状态码检查和响应类型检查content-type,如果是html则通知浏览器进程准备渲染进程渲染
  • 准备渲染进程
    1. 检查url是否跟之前打开过的页面同站点(主域名和协议相同),是的话复用同个渲染进程,否则新开线程
    2. 浏览器进程发送“提交文档”(文档是响应体数据)消息给渲染进程
    3. 渲染进程收到“提交文档”消息,渲染进程和网络进程建立数据管道
    4. 渲染进程接收完数据,会和浏览器进程“确认提交”,告知已经准备好接受和解析数据了
    5. 浏览器接收到消息,会移除旧的文档,更新浏览器页面状态(前进后退状态、URL、安全状态)

扩展: 4.1中的复用渲染进程也不一定都这样,可以再a标签加上rel=”noopener noreferrer”这个属性,使用noopener noreferrer就是告诉浏览器,新打开的子窗口不需要访问父窗口的任何内容,这是为了防止一些钓鱼网站窃取父窗口的信息,如<a target="_blank" rel="noopener noreferrer" class="hover" href="https://linkmarket.aliyun.com/hardware_store?spm=a2c3t.11219538.iot-navBar.62.4b5a51e7u2sXtw" data-spm-anchor-id="a2c3t.11219538.iot-navBar.62">瓜皮</a>

渲染流程

流程:构建DOM树=>样式计算=>布局阶段=>分层=>绘制=>分块(图层->图块)=>光栅合(图块->位图)=>合成

分层:元素设置了定位position、透明opacity、层级设置z-index、滤镜filter等会被提升成单独的分层

绘制:一个页面的形成需要多个绘制指令实现,页面形成过程会创建绘制指令列表,主线程会对准备好的绘制列表提交给“合成线程(属于渲染进程)”,合成线程会对“图块(图层划分为多个图块,解决页面较大,用户可以优先加载视口区域的页面内容)”进行“栅格化(图块转成位图)”操作。 (开发者工具->layout标签->paint profiler 可以查看绘制列表)

合成:浏览器进程执行合成线程的指令,将页面内容绘制到内存中,再将内存显示到屏幕

JS执行机制

Javascript在执行之前会被Javascript引擎编译,编译完成才会进入执行阶段

出现同名变量和函数时的执行

showName()  // 输出1
var showName = function() {
    console.log(2)
}
showName()  // 输出2
function showName() {
    console.log(1)
}

执行结果:第一个输出1  第二个输出2
解析:变量和函数同名,那么在编译阶段,变量的声明会被忽略,函数showName会被提升,并且指向堆中的函数定义,因此showName()会打印出1,如果在变量赋值后面再执行函数,那么就会输出2

如果是两个函数名同名,没有赋值给变量,那么第二个函数会覆盖第一个函数

调用栈(后进先出)是javascript引擎最终函数执行的机制,实际应用中,可以在代码打断点查看call stack,或者通过打印指令查看consolt.trace()调用栈信息

执行上下文生命周期包括3个阶段:创建阶段->执行阶段->回收阶段,创建阶段包括了变量环境词法环境确定this的值的创建,var和function的定义会被放到变量环境,es6中引入的let和const定义的变量会被放到词法环境中

  1. var的创建和初始化被提升,赋值不会被提升。
  2. let的创建被提升(不是声明提升),初始化和赋值不会被提升。 如果在let定义变量之前使用该变量,会形成暂时性死区
let a = 1
if (true) {
    console.log(a)
    let a = 2
}
// Uncaught ReferenceError: Cannot access 'a' before initialization
  1. function的创建、初始化和赋值均会被提升。

作用域,是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

作用域链,每个执行上下文都会有一个outer外部引用指向外部的执行上下文,js引擎会通过这个链接一步一步往上查找变量,就形成了作用域链

闭包,通过外部函数返回内部函数,内部函数调用的的外部函数的变量在外部函数执行结束后还会保存在内存中,这些变量的集合就称为内部函数的闭包

浏览器垃圾回收

调用栈的垃圾回收

通过下移ESP(记录当前执行状态的指针)到下一个函数执行上下文,移动之前的执行上下文所占用的栈内存被释放,执行效率很高且不需要经过垃圾回收机制

堆中的数据回收

回收堆中的数据需要用到“垃圾回收器”,V8把堆分为“新生代”(容量较小1~8M)和“老生代”两个区域,新生代存放的生存时间短的对象,老生代则存放生存时间长的对象

“新生代”采用scanvenge算法,也就是会把新生代区划分为“对象区域”和“空闲区域”,当对象区域的内存快满的时候,引擎执行一遍清理操作,标记活动对象,对活动对象有序的复制到“空闲区域”,然后回收“对象区域”的空间,最后翻转“对象区域”为“空闲区域”,之前的“空闲区域”变成“对象区域”。也因此新生代区域容量不能设置太大,避免清理时间太长,影响性能

因为“新生代”容量相对较小,为了避免容易造成空间不足,会将经过两次回回收还存在的活动对象放到老生代区域中,一些大的对象也会直接分配到这个空间,采用“标记-清除”算法,为避免清理过程产生不连续的内存碎片,改用“标记-整理”算法

为了提升GC的性能,避免对主线程造成大的影响,通过增量标记、懒惰清理、并行回收的方式去做优化

事件循环和消息队列

事件循环和消息队列保证了页面渲染过程中不同任务类型在主线程中有序执行

渲染进程中通过IO线程接收其它线程(网络进程、浏览器进程…),将接收到的任务丢到任务队列(先进先出)里面,渲染主线程通过事件循环不断从队列头中拿到任务执行。

微任务,用以解决高优先级任务执行的问题,高优先级任务放到队列后面就会导致等待过长,马上执行又会导致当前任务执行时长拉长,因此执行完宏任务后就会执行当前的微任务,微任务就是放我们高优先级的任务

浏览器通过回调的功能规避单个任务执行时长太久影响其它任务的情况

注:

  1. 事件循环并不会一直迭代执行,实际过程中会有系统级中断机制,有事件时才会被激活,否则,线程会被挂起
  2. 定时任务(setTimeout、setInterval)这种为了在指定时间执行回调,如果放到队列中,而队列是按照顺序执行的,所以达不到目的。js执行到setTimeout设置回调函数的时候,渲染进程会创建一个回调任务,并将该回调函数放到延迟队列中去,会在每次执行完宏任务的时候执行延迟队列里到期执行的任务
  3. 每个宏任务都有一个微任务

css & js对页面加载的影响

css不阻塞DOM生成;css不阻塞js的加载,但是阻塞js执行(因为js可能存在修改dom样式的代码,所以在js加载之前需要先下载解析js之上的所有css);js阻塞DOM生成(因为js可能会修改当前状态下的dom),也就是会阻塞页面的渲染,那么css也有可能阻塞页面的渲染;

优化:对于一些没有操作DOM的脚本可以通过设置异步加载的方式加载资源(async和defer);也可以通过CDN家属资源下载;也可压缩资源;

  • async:脚本并行加载,加载完立即执行,执行时机不确定,仍可能阻塞页面渲染,执行时机在load事件派发之前
  • defer:脚本并行加载,等待HTML加载完成后执行,按照加载顺序执行,执行时机在DOMContentLoaded事件派发之前

<html>
<head>
    <link href="theme.css" rel="stylesheet">
</head>
<body>
    <div>geekbang com</div>
    <script>
        console.log('time.geekbang.org')
    </script>
    <div>geekbang com</div>
</body>
</html>

上面代码块的执行流程如下
image.png

HTTP发展

  • http/0.9
    1. 简单请求,没有请求头、请求体、响应头、响应体
  • http/1.0
    1. 支持多种文件类型、引入响应头、请求头、状态码
    2. 缺点:请求过多会重复进行TCP链接建立、传输HTTP数据、断开TCP链接,开销很大,http/1.1解决了此问题
  • http/1.1
    1. 支持持久化连接,避免重复TCP建立、请求头增加host字段、引入Cookie机制和安全机制
    2. 每个域名最多同时维护6个TCP连接(http2.0通过使用只建立一个连接多路复用解决)
    3. 使用CDN的实现域名分片机制
    4. 缺点:队头阻塞(同个TCP管道的同一时刻只能处理一个请求)、TCP慢启动(请求发送速度有个加速度过程,影响到关键资源的下载)、多个TCP互相竞争带宽
  • http/2.0
    1. 采用多路复用的方式解决了http1.1的问题,传输过程中多了二进制分帧层(将请求转换为多个带有id编号的帧,服务器收到请求会将相同id的数据合并为一个完整的请求)实现多路复用
    2. 请求头、响应头压缩
    3. 缺点:队头阻塞(Tcp丢包重传,包需要等待丢失数据的重新传输)、连接建立延时(TLS握手花费1.5RTT,TCP三次握手连接1.5RTT)
  • http/3.0
    1. UDP基础上实现QUIC协议,保证了数据传输的可靠性、快速重传、拥塞控制

页面安全

XSS攻击(跨站脚本攻击)

  • 存储型XSS攻击:将攻击代码片段存储到服务器中,用户访问页面的时候执行脚本
  • 反射型攻击:恶意脚本由用户发送到后台,后台又把脚本返回给页面(比如url上的query,页面直接渲染query字段内容,脚本不会存储到服务器)
  • 基于DOM攻击:不涉及web服务器,通过网络劫持并修改页面资源传输过程中的数据(wifi路由器劫持)

防止攻击策略

  1. 服务器对客户端的输入内容进行过滤替换
  2. 利用CSP限制第三方资源的加载
  3. 对一些关键性的Cookie设置httponly

CSRF攻击(跨站请求伪造)

原理:利用目标站点(用户站点)的登录态,通过各种渠道诱导用户打开危险网站,危险网站通过伪造目标站点发起请求

eg.
目标站点:a(http://a.com)
危险站点:b(http://b.com)

攻击过程:
用户登录a站点,浏览器已经有了a域名的登录态
当前a站点有个危险诱导链接跳转到b站点,用户点击跳转
b站点有个图片资源<img src="http://a.com/pay?user=mm&total=300" />,页面加载则会发起请求,此时因为a站点已经登录所以会直接当成一个正常请求向mm转账300

防止攻击策略

  1. 利用Cookie的SameSite属性,可以设置禁止第三方网站请求携带Cookie
  2. 服务端通过origin(来源网站域名,不带额外信息)和Reference(来源网站域名,带额外信息)验证请求的来源
  3. token校验,因为只有当前网站有token(两个标签,虽然是同个页面,但是token也是不一样的),请求时需要带上,第三方网站获取不到token

点击劫持

点击劫持(Clickjacking)是一种通过视觉欺骗的手段来达到攻击目的手段。往往是攻击者将目标网站通过 iframe 嵌入到自己的网页中,通过 opacity 等手段设置 iframe 为透明的,使得肉眼不可见,这样一来当用户在攻击者的网站中操作的时候,比如点击某个按钮(这个按钮的顶层其实是 iframe),从而实现目标网站被点击劫持。

  1. 在HTTP投中加入 X-FRAME-OPTIONS 属性,此属性控制页面是否可被嵌入 iframe 中
DENY:不能被所有网站嵌套或加载;
SAMEORIGIN:只能被同域网站嵌套或加载;
ALLOW-FROM URL:可以被指定网站嵌套或加载。
  1. 判断当前网页是否被 iframe 嵌套

中间人攻击

对于客户端来说,中间人是服务端,对于服务端来说中间人是客户端,可通过数字证书来保证服务器的可信任,通过数字摘要保证数据不被篡改

加密方式

  1. 对称加密:协商秘钥的过程容易被窃取。过程是,client向服务端发送client随机数+加密算法选择列表,服务端选择其中一种算法并生成server随机数,发送给客户端,server和client都通过这两个随机数生成秘钥,就可以开始数据加密交互了。但是,随机数是明文传输的,可以被劫持,生成秘钥,从而篡改数据。
  2. 非对称加密:服务端有秘钥,并把公钥发给客户端加密数据。确定是效率低,并且无法保证服务器给客户端的数据,因为Hack能够劫持到公钥,并解密返回给客户端的数据。而客户端发给服务端的数据则是安全的,因为客户端用公钥加密的数据只能用私钥解密,而私钥只有服务端有。
  3. 对称加密+非对称加密:数据传输用对称加密方式,对称加密公钥通过非对称加密方式加密传输。过程是,client发送对称加密列表、非对称加密列表、client随机数到服务端;服务端选择加密方式,并生成server随机数和公钥一起发给客户端,客户端生成另外一个随机数master,并通过公钥加密传输给服务端,最后,client和server都通过这三个随机数生成对称秘钥,对称加密传输数据就用该秘钥,因为最后的随机数master是通过公钥加密的,所以Hack无法生成秘钥,因此也就避免了非对称加密服务端向客户端发送数据被篡改的问题。但是,该策略还是有可能被DNS劫持,目标ip地址被指向Hack的服务器。
  4. 添加数字证书:通过权威机构(CA)颁发的数字证书证明自己是合法的,数字证书包含两个作用,证明服务器的身份和服务器公钥。最终证明CA的合法性是由系统内置的根CA决定的,所以如果操作系统的内置根证书被替换,那么HTTPS的访问也是不安全的。

求职中

6年前端开发一枚,上家公司是腾讯音乐(TME),干了3年,目前离职状态

实际项目的主要技术栈:VUE、Ts、Node以及小程序开发

?邮箱:mrjiaobin@163.com

?wechat:kchen0810

有人来捞一捞吗~~~ ?

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYPNQfsP' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片