浏览器相关原理
Posted Silam Lin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器相关原理相关的知识,希望对你有一定的参考价值。
浏览器相关原理
- Chaper One 浏览器的安全
- XSS攻击 (跨站脚本攻击)
- CSRF攻击(跨站请求伪造)
- 中间人攻击 ***
- 前端方面的安全问题
- 网络劫持 (DNS劫持和HTTP劫持)
- -----------------------------------
- Chaper Two 浏览器的组成
- 对浏览器的理解
- 对浏览器内核的理解
- 常见浏览器所用的内核
- 浏览器的组成部分
- -----------------------------------
- Chaper Three 浏览器的渲染
- 浏览器的渲染过程
- 浏览器的渲染优化
- CSS阻塞文档的解析
- JS文件阻塞文档的解析
- 渲染页面时的常见不良现象(FOUC、白屏)
- 文档的预解析
- 优化关键渲染路径
- -----------------------------------
- Chaper Four 浏览器的存储
- Cookie
- Cookie有哪些字段
- JS如何操作Cookie、WebStorage ***
- Cookie和WebStorage的区别
- Cookie和Session的区别
- IndexedDB ***
- 单点登录 ***
- -----------------------------------
- Chaper Five 进程与线程
- 进程与线程的概念
- 进程与线程的区别
- 进程之间的通信方式
- 产生死锁的原因
- 实现浏览器内多个标签页之间的通信
- 浏览器渲染进程的线程有哪些
- 孤儿进程和僵尸进程
- -----------------------------------
- Chaper Six 浏览器的缓存
- 浏览器的缓存机制
- 强缓存与协商缓存
- 浏览器资源缓存的位置
- 使用缓存的好处
- -----------------------------------
- Chaper Seven 浏览器的同源策略
- 什么是浏览器的同源策略
- 如何解决跨域问题
- -----------------------------------
- Chaper Eight 其它
- 事件循环
Chaper One 浏览器的安全
XSS攻击 (跨站脚本攻击)
基本概念:
- XSS即跨站脚本攻击,是一种代码注入攻击。
- 攻击者将恶意代码注入到目标网站,使其在用户的浏览器上执行。
- 从而盗取用户的cookie或者其它识别客户端身份的敏感信息
攻击类型:
- 存储型
- 反射型
- DOM型
存储型XSS
存储型XSS:恶意脚本存储在目标服务器上,当浏览器请求数据时,恶意脚本会从服务器端返回并执行。
攻击步骤:
- 攻击者将恶意代码提交到目标网站的数据库
- 用户打开目标网站时,服务器端将恶意代码从数据库取出,拼接到HTML返回给浏览器
- 用户浏览器接收到响应,对其解析并执行。混在其中的恶意代码也被执行。
- 恶意代码窃取用户数据并发送到攻击者网站;或冒充用户,与目标网站执行攻击者指定的操作
反射型XSS
反射型XSS:攻击者诱导用户去访问一个带有恶意代码的URL,服务器端接收请求后,返回带有恶意代码的数据到浏览器端,浏览器端解析这段带有XSS代码的数据并执行。
攻击步骤:
- 攻击者构造一个带有恶意代码的URL
- 用户在诱导下,访问这个URL。服务器端会将恶意代码从URL取出,拼接到html返回给浏览器
- 用户浏览器接收到响应,对其解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者网站;或冒充用户,与目标网站执行攻击者指定的操作
注意,存储型XSS与反射型XSS的区别在于:存储型XSS的恶意代码是提交到目标网站的数据库;反射型XSS的恶意代码存储在构造好的URL当中
DOM型XSS
DOM型XSS:修改页面DOM节点形成的XSS
攻击步骤:
- 攻击者构造一个带有恶意代码的URL
- 用户打开这个带有恶意代码的URL;
- 用户浏览器接收到响应并执行,前端JS取出URL的恶意代码并执行;
- 恶意代码窃取用户数据并发送到攻击者网站;或冒充用户,与目标网站执行攻击者指定的操作
注意:取出和执行恶意代码都是浏览器端完成,属于前端JS的漏洞;存储型和反射型则属于服务器端的漏洞
防范XSS
① cookie设置httpOnly ,使得脚本无法获取cookie
cookie.setHttpOnly(true);
② 使用验证码,使攻击者无法伪装成用户身份
③ 使用CSP(Content-Security-Policy),建立白名单 ,列出合法可出现的字符,对输入进行验证过滤,则可避免可能导致XSS攻击的字符。
1.可以在http首部设置Content-Security-Policy字段
2.meta设置 如:<meta http-equiv="Content-Security-Policy">
④ 转义HTML ,XSS攻击能实现是因为 输入的内容变成可执行代码,对HTML进行转义,譬如 < 变成 &alt
⑤ 避免使用innerHTML
CSRF攻击(跨站请求伪造)
CSRF的攻击流程:
- WebA是User信任的网站,User登录WebA
- 登录验证后,WebA返回Cookie等信息返回给User,并保存在浏览器本地
- User在没有退出WebA的情况下,又去访问危险网站WebB
- WebB接收到User的请求,服务器返回了恶意代码。恶意代码执行,发出要访问WebA的请求
- 浏览器带着还保存在浏览器本地的cookie,去访问了WebA
- WebA无法访问该请求是否为用户本人操作。因为Cookie已经被盗用了,WebB有了User的身份。
我们无法保证:
- 登录一个网站后,不会再访问另外一个网站
- 本地的cookie能在访问另外一个网站时就过期失效
- 危险网站WebB可能是我们信任但却出现了安全漏洞的网站
常见的CSRF攻击类型有:
- GET类型
- POST类型
- 链接类型
GET类型
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
- 通常是,危险网站中含有img标签;
- 当用户访问页面时,浏览器就会自动向img的src地址发送一次HTTP请求
POST类型
<form action="http://bank.example/withdraw" method=POST>
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>
- 通常利用的是自动提交的表单,这个表单在页面中被隐藏
- 当用户访问页面时,表单自动提交,模拟用户发送一次POST请求
链接类型
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
重磅消息!!
<a/>
- 通常是利用一些图片、夸张性广告诱导用户点击链接
- 用户点击后,则会发起请求。
防范CSRF
CSRF的本质是:攻击者利用User尚保存在浏览器本地的Cookie信息,冒充User身份,向网站发送请求。
- 进行同源检测:服务器根据http请求头中的refer和origin字段来判断,该请求是否来自允许访问的站点。
- 使用Token:服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题。这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。
中间人攻击 ***
前端方面的安全问题
- XSS
- CSRF
- Iframe滥用
- 恶意的第三方库:⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤多数时候都是在借助开发框架和各种类库进⾏快速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起安全问题。
网络劫持 (DNS劫持和HTTP劫持)
网络劫持分 DNS劫持 和 HTTP劫持
DNS劫持 (访问A却强制访问到A+(可能带有广告))
- 用户在浏览器输入网站的域名,需要靠DNS(域名系统)去解析域名,返回计算机能够识别的IP地址。通过访问IP地址,从而访问到目标网站
- 那么DNS劫持则是,返回的并不是目标网站的IP地址,而是一个中间服务器的IP地址。
- 访问中间服务器的IP地址,会一致性返回302,让用户跳转到带有广告的网页。该网页再通过iframe引入原来目标网页的内容
HTTP劫持 (访问A,而A一直有附加广告)
- 在TCP连接中,找出并标记应用层采用的是HTTP协议的连接
- 篡改HTTP响应体,对数据包内容进行篡改
- 抢先回包,将篡改后的数据包抢先正常站点返回的数据包先到达用户侧,正常的数据包则会被丢弃。
HTTP劫持的防范:
- 采用HTTPS (性能降低)
- 拆分HTTP请求,躲过标记
-----------------------------------
Chaper Two 浏览器的组成
对浏览器的理解
浏览器的主要功能:
- 用户通过URI,即统一资源标识符,指定所请求资源的位置。
- 浏览器则向服务器发送请求,并将用户指定的web资源呈现到浏览器窗口。
- web资源的格式通常是HTML,也包括pdf,image等其它格式
浏览器的组成分为两部分:shell和内核。其中shell的种类比较多,内核则比较少。
- shell:指的是浏览器的外壳,例如菜单、工具栏等;shell通过调用内核,为用户提供各种界面操作和参数设置。
- 内核:是浏览器的核心。
对浏览器内核的理解
浏览器的内核分作两部分:
- 渲染引擎
- javascript引擎
渲染引擎 主要用于渲染,有三个职责,最终将内容输出显示到显示器或打印机
- 获取页面的内容(HTML、XML、Image)
- 处理讯息
- 计算页面的显示方式
JavaScript引擎
- 解析、执行JavaScript,从而实现页面的动态效果
常见浏览器所用的内核
- IE浏览器:Trident内核
- Chrome浏览器:以前是webkit内核,现在是Blink内核
- Firefox浏览器:Gecko内核
- safari浏览器:webkit内核
- Opera浏览器:Presto内核 -> webkit内核 -> Blink内核
浏览器的组成部分
- 用户界面:地址栏、前进/后退按钮、书签菜单等
- 浏览器引擎:在用户界面与呈现引擎之间传输指令
- 呈现引擎:显示请求的内容
- 用户界面后端
- JavaScript解释器
- 数据存储:如cookie
- 网络
-----------------------------------
Chaper Three 浏览器的渲染
浏览器的渲染过程
- 解析文档(HTML、XHTML、SVG),生成 DOM Tree
- 解析CSS,生成 CSSOM Rule Tree
- 根据DOM Tree和CSSOM Rule Tree,生成 Rendering Tree 渲染树。渲染树的节点称作渲染对象,渲染对象与DOM对象不是一一对应的。
- 布局:生成Rendering Tree渲染树后,弄清楚渲染树各个节点的大小和确切位置,进行布局(自动重排)
- 绘制:遍历渲染树,调用paint方法,将内容绘制显示到屏幕上。
浏览器的渲染优化
① JavaScript会阻塞HTML和CSS的解析,从而阻缓页面的渲染
- 将JS文件尽量放在body的后面,不要放在开头或穿插在中间
- 尽量使用async或defer属性,使得script能够被异步加载,从而不会停止文档的解析
② CSS方面的优化
- 内联首屏关键CSS,使得GUI直接渲染,减少渲染时间
- 减少重排重绘
- 如果需要引入外部CSS文件,尽量使用link而不是@import:link会产生一个新线程去加载CSS资源,不阻塞GUI渲染线程继续渲染代码;@import则会暂停GUI渲染线程,先去加载CSS资源,从而阻塞了页面渲染
- CSS选择器应当精简,避免冗杂无用的遍历,避免重复书写CSS代码
③ HTML —— DOM Tree的构建
- HTML代码层级不要太深
- 使用语义化标签
CSS阻塞文档的解析
浏览器的解析,分为三方面
- 解析文档(HTML、XHTML、SVG),生成 DOM Tree
- 解析CSS,生成 CSSOM Rule Tree
- 解析JavaScript,通过 DOM API 和 CSSOM API 操作 DOM Tree 和 CSSOM Rule Tree
CSS阻塞文档的解析的原因分析:
- JavaScript可能在文档解析过程中,请求样式信息。
- 若CSS还未解析完成,即CSSOM Rule Tree还未构建完成,那JS则会得到错误的值,显然不合理。
- 所以,如果在CSS未解析完毕,而JS又想操作CSSOM Rule Tree的情况下,会先加载解析CSS,完成对CSSOM Rule Tree 的构建,再执行JavaScript,最后恢复对DOM Tree的构建。
JS文件阻塞文档的解析
JS文件会阻塞文档的解析:
- 停止对文档的解析,停止构建DOM Tree
- 将控制权交给JavaScript引擎
- 待JS加载、解析、执行完毕后,再恢复对文档的解析
渲染页面时的常见不良现象(FOUC、白屏)
- FOUC:主要指的是样式闪烁的问题。即CSS加载之前,先加载了HTML,则出现了“先展示无样式内容,然后再突然呈现样式”的现象。原因是:CSS加载时间过长、CSS放在文档底部
- 白屏:浏览器迟迟未渲染页面。有的浏览器需要先构建DOM树和CSSOM树,构建完成再渲染。有可能因为CSS放在HTML尾部,CSS未加载完成,则就不能构建完成,从而不能渲染而白屏;也有可能是JS放在了头部,阻塞了DOM的解析。
文档的预解析
预解析:
- 文档的预解析是浏览器内核对解析过程的优化
- 当执行JavaScript脚本时,另外一个线程解析剩下的文档,并加载后面需要通过网络请求的资源,从而加快了解析速度
- 文档的预解析不会改变DOM Tree,DOM Tree的构建是主线程的任务。
优化关键渲染路径
尽快完成首次渲染,需要最大限度减小以下三个因素:
- 关键资源的数量
- 关键路径的长度
- 关键字节的大小
关键资源:指的是可以阻塞页面首次渲染的资源,如JavaScript,CSS等。关键资源数量越少,浏览器的渲染工作量越少,CPU的占用也就越小。
关键路径:关键路径的长度指的是关键渲染路径的总耗时。
关键字节:关键字节的大小指的是关键资源的字节大小。关键资源的字节越小,那么下载速度就越快。
有如下几个方面可以优化:
一、优化DOM
- 缩小文档的尺寸、使用gzip压缩、使用缓存
- 优化DOM则是尽可能减小关键路径的长度和关键字节的大小
二、优化CSSOM
同优化CSS
三、异步加载JavaScript
- 减少关键资源
-----------------------------------
Chaper Four 浏览器的存储
Cookie
Cookie的原理:
- 用户首次访问网页时,服务器并不清楚用户信息。服务器返回一些数据即Cookie给客户端浏览器,并保存在浏览器/本地。
- 当用户发送第二次HTTP请求时,直接把上次HTTP请求返回且保存到浏览器/本地的Cookie交给服务器端。那么服务器端通过Cookie就能判断用户的身份。
Cookie的特点:
- 只能存储简单的字符串类型;
- 保存在浏览器/本地,在每次同源请求下都会携带Cookie访问服务器端
- 单个Cookie不能超过4K,多数浏览器限制一个站点不能超过20个Cookie,一个浏览器最多创建300个Cookie
- Cookie无法跨域
- Cookie的安全性并不高
Cookie的缺陷:
- Cookie安全性并不高
- Cookie的大小受限
- 浏览器可以禁用Cookie
Cookie有哪些字段
- Name : Cookie的名称
- Value:Cookie的值
- Size:Cookie的大小
Expires/Max-Age
- Expires:指定一个具体的到期时间,过了这个时间,Cookie则失效。Expires是UTC格式,可以使用Date.prototype.toUTCString()转换
- Max-Age:指定从现在开始Cookie 可以存在的秒数。
注意:
- Max-Age的优先级比Expires要高
- 如果没有设置Expires和Max-Age,那么就是Session-Cookie(会话Cookie)。也就是生命周期为会话周期,一旦关闭浏览器Cookie则失效。
Secure/HttpOnly
- Secure:指定浏览器只有在HTTPS下,才能传送这个Cookie
- HttpOnly:指定不能通过JavaScript脚本读取Cookie,只有浏览器发出Http请求时,才会带上Cookie
Domain/path
- Domain 设置了可以访问该Cookie的域名,path则设置可以访问该Cookie的页面路径;
- Domain和path的设置,决定了只有访问什么域名/路径才会带上Cookie
JS如何操作Cookie、WebStorage ***
Cookie和WebStorage的区别
- 存储类型:都是存储简单的字符串类型
- 存储地方:都保存在客户端浏览器;但Cookie在每次同源请求会与服务器端通信,sessionStorage和localStorage不会与服务器端通信
- 存储大小:单个Cookie不能超过4KB;sessionStorage和localStorage不能超过5MB
- 生命周期:Cookie有一个Expire值,超过则失效;sessionStorage是在当前会话下有效;localStorage除非手动清除,否则永久存在
- 作用域:Cookie和localStorage是在同源页面下有效;sessionStorage只能被同一个窗口的同源页面所共享。
Cookie和Session的区别
- 存储类型:Cookie只能存储字符串类型;Session可以存储任意类型对象;
- 存储地方:Cookie存储在浏览器/本地;Session存储在服务器端
- 存储大小:单个Cookie不能超过4K,多数浏览器限制一个站点不能超过20个Cookie;Session没有大小限制,可存储的数据大小与服务器的内存有关
- 安全性:session的安全性更高
- 性能:session保存在服务器端,当访问增多时,增加服务器的压力
IndexedDB ***
单点登录 ***
-----------------------------------
Chaper Five 进程与线程
进程与线程的概念
进程:
- 是资源分配的最小单位,能拥有资源和独立运行的最小单位
- 描述了CPU在运行指令及加载保存上下文所需的时间
- 是一个程序的运行实例:当启动一个程序时,操作系统会为该程序分配一块内存,用于存放代码、运行中的数据和一个执行任务的主线程
- 运行在虚拟内存上(虚拟内存用于解决用户对硬件资源的无限需求和有限的硬件资源之间的矛盾)
- 一个进程中可以有多个线程
线程:
- 是CPU调度的最小单位
- 描述了执行一段指令所需的时间
进程与线程的区别
- 进程是资源分配的最小单位(能拥有资源和独立运行的最小单位);线程是CPU调度的最小单位(一个进程中有多个线程)
- 进程之间通信需要借助进程通信;线程之间可以直接共享同一进程中的资源
- 进程切换比线程切换的开销要大(线程是CPU调度的最小单位)
- 进程的创建、撤销要比线程的开销要大(系统都要为其分配或回收资源)
进程之间的通信方式
进程间的通信方式有:
- 管道通信
- 消息队列通信
- 共享内存通信
- 信号量通信
- 信号通信
- Socket通信
管道通信
-
管道的基本定义是,操作系统在内核中开辟的一段缓冲区。
-
管道通信则是,进程A将需要交互的数据写入该缓冲区,进程B则可以从缓冲区读取。
-
管道可以分为匿名管道和命名管道,分别用 | 竖线操作符 或pipe函数和mkfifo命令创建。
-
匿名管道只能在有父子/兄弟关系下的进程之间进行通信,且不存在于文件系统;命名管道可以在不相关的进程之间通信,依赖文件系统。
-
特点① :管道只能单向传递,若要双向传递则需要创建多一个管道,举例如:父进程通过fork创建子进程,并且关闭父进程的读取端和子进程的写入端(或关闭父进程的写入端和子进程的读取端)从而实现父进程写入、子进程读取(或子进程写入、父进程读取)的通信方式。
-
特点② :管道传输的数据是 无格式字节流,且 大小受限
-
特点③ :管道的 生命周期跟随着进程,通信规则遵循FIFO先进先出原则。
优点:
- 操作简单
缺点:
- 通信范围受限:匿名管道只可以在父子/兄弟关系下的进程通信
- 通信方向受限:管道只能单向通信,想要双向通信则需要创建多一个管道
- 传输数据大小受限:管道传输的数据是无格式字节流,大小受限
- 效率低,不适合频繁交换数据
消息队列通信
- 消息队列是 保存在内核中的消息链表
- 消息队列通信,进程A把需要交互的数据放到消息队列中即可返回,进程B在需要用时再去消息队列中读取,就像写信一样你来我往。
- 特点① : 消息队列可以 双向传输数据
- 特点② : 消息队列传输的数据是 由用户约定好数据类型,且大小固定的消息体
- 特点③ : 消息队列的生命周期跟随内核,若不释放消息队列或关闭操作系统,则将一直存在
优点:
- 可以双向传输数据
- 适合频繁交换数据
缺点:
- 通信不及时
- 传输的消息体大小受限
- 通信时存在用户态与内核态之间的数据拷贝开销
共享内存通信
- 每个进程都有自己的进程控制块、地址空间和页表;
- 其中的页表负责将进程的虚拟地址与物理地址进行映射
- 两个进程不同的虚拟地址通过页表映射到同一区域,这个区域就叫共享内存
- 进程A写入的东西,进程B马上就可以看到,提高了通信的速率
信号量通信
有了共享内存通信方式,则有可能出现新的问题:多个进程竞争共享资源,从而造成数据混乱
- 信号量:是一个整型的计数器,表示资源的数量,能够保证共享的资源在任意时刻只能被一个进程访问,从而实现进程间的互斥与同步。
- 信号量操作:有P操作和V操作;P操作在访问共享资源之前执行,V操作是访问完共享资源后执行。
- P操作:进程执行P操作,信号量-1 : 若信号量<0,表明无资源可用,进程被阻塞;若信号量>=0,资源可用,进程可继续执行;
- V操作:进程执行V操作,信号量+1 :若信号量<=0,表明有进程被阻塞,则唤醒该进程;若信号量>0,表明没有进程被阻塞
互斥与同步:
- 若信号量初始化为1,则为互斥信号量
- 若信号量初始化为0,则为同步信号量
信号通信
- 信号通信是进程间通信机制中唯一的异步通信机制,是异常情况下的工作模式。
- 它可以在任意时刻发送信号给某一进程
- 一旦进程收到信号,可以执行默认操作、捕捉信号并执行相应的处理函数或者忽略信号
Socket通信
管道通信、消息队列通信、共享内存通信、信号量通信与信号通信都是在同一台主机进行进程间通信。
Socket通信可以跨网络与不同主机上的进程间进行通信,也可以在同主机上的进程间通信。
产生死锁的原因
死锁:
- 两个或两个以上进程在执行过程中,因为资源竞争而造成的一种阻塞现象。
- 若不施以外力处理,它们将永远处于阻塞状态。
产生死锁的原因通常是:
- 资源数量不足
- 资源分配不合理
- 进程推进的顺序不合理
产生死锁的四个必要条件:
- 互斥使用:当某个资源被一个进程占用时,别的进程不可使用
- 不可抢占:资源只能由占有者主动释放,而不能被强制抢占
- 占有且等待:请求资源者在请求其它资源的同时,保持对原有资源的占有
- 循环等待:若干进程形成头尾相接的资源等待关系,A占有B的资源,B占有C的资源,C占有A的资源。
死锁预防:
- 破坏不可抢占:当一个占有一些资源的进程,请求其它资源而未得到,则应主动释放已有的资源
- 破坏占有且等待:所有的进程,在一开始则一次性申请所需的所有资源
- 破坏循环等待:实现资源的有序分配策略
死锁解决:
- 释放资源:从其它进程中释放一定数量的资源去解决死锁状态下的进程
- 撤销进程:可以直接撤销死锁进程
实现浏览器内多个标签页之间的通信
多个标签页之间无法直接通信,得通过中介者模式来实现
- websocket协议:websocket可以实现服务器的推送:标签页向服务器发送消息,再有服务器向其它标签页推送转发
- shareWorker:shareWorker会在页面的生命周期内创建一个能被多个标签页共享的线程。故标签页之间可以通过共享线程进行数据交换
- postMessage:获得对标签页的引用,使用postMessage方法对引用的标签页推送消息
浏览器渲染进程的线程有哪些
浏览器有以下进程:
- 浏览器进程
- CPU进程
- 第三方插件进程
- Renderer进程(渲染进程)
Renderer进程即渲染进程内有五个线程:
- GUI渲染线程 (解析HTML构建DOM Tree、解析CSS构建CSSOM Rule Tree、构建Rendering Tree、布局与绘制)
- JS引擎线程
- 事件触发线程
- 定时器触发线程
- 异步http请求线程
孤儿进程和僵尸进程
孤儿进程:一个父进程退出,而其子进程还在运行。这些没有父进程的子进程,成为了孤儿进程。孤儿进程将被init进程所收养;
僵尸进程:子进程比父进程早结束,但是父进程并没有释放子进程占用的资源,子进程的进程描述符仍然保存在系统中,则成为僵尸进程。(是已经结束的进程,但其占用的资源却没有被释放)
-----------------------------------
Chaper Six 浏览器的缓存
浏览器的缓存机制
浏览器第一次发起请求:
- 因为本地没有资源缓存
- 故,向服务器发起请求
- 服务器返回资源,浏览器将资源缓存下来
- 服务器还会返回两个关键标识 E-tag 和 Last-Modified 给浏览器
浏览器第二次发起请求:
- 强缓存的优先级较高,先判断能否命中强缓存。先根据header信息中的expires和cache-control去判断资源是否过期(cache-control的优先级高):若未过期,则命中强缓存,直接从本地缓存读取资源。
- 如果过期,未命中强缓存,则进一步查看协商缓存。
- 浏览器将第一次请求获得的E-tag和Last-Modified,分别赋给If-None-Match和If-Modified-Since。
- 浏览器发送带有If-None-Match和If-Modified-Since的请求,分别与服务器的E-tag和Last-Modified比较(E-tag的优先级高)。综合考虑:若文件是最新的,返回304,命中协商缓存;否则,返回200,请求并返回新的资源。
强缓存与协商缓存
强缓存(Expires/Cache-Control)
Expires
- Expires属于Http1.0的方式,指定的是资源的失效时间:在这个时间之前,资源都可被缓存使用。
- Expires是一个绝对时间(服务器的时间),可能会与客户端时间不一致,从而影响强缓存的命中效果。
Cache-Control (优先级高于Expires)
- Cache-Control则是Http1.1的方式,可以设置以下值:
- max-age
- no-cache:不使用强缓存,但可以有协商缓存
- no-store:不使用任何缓存,每次都向服务器发起请求以拉取新的资源
协商缓存(E-tag/If-None-Match和Last-Modified/If-Modified-Since)
- E-tag的优先级高于Last-Modified
E-tag的必要性(有时候Last-Modified可能不靠谱)
- 有一些文件会发生周期性,只是改变的只是修改时间但并不改变内容。我们并不希望这种情况会被客户端认为,资源被修改了。
- 一些文件的修改非常频繁。频繁到Last-Modified与If-Modified-Since都不能检测到它发生了修改。
- 某些服务器不能精确地得到文件的最后修改时间
浏览器资源缓存的位置
资源缓存的位置有以下四种,优先级从高到低:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求(若前四种缓存都无命中,则发起网络请求)
Service Worker
- 它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
- 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
Memory Cache
- 即内存中的缓存,读取起来要比磁盘缓存快。
- 虽然读取高效,内存缓存的持续性短,会随着进程的释放而释放。
- 也就是说一旦关闭Tab标签页,Memory Cache也就被释放了
使用缓存的好处
- 浏览器的缓存主要针对的是前端的静态资源。
- 浏览器将用户请求过的静态资源缓存下来,当再次访问时,若资源无更新,则可以直接从缓存中获取。
使用缓存的好处:
- 减少了多余的请求,减少了多余的网络传输数据
- 减少了服务器的负担
- 提高了浏览器页面的加载速度,提升使用体验
-----------------------------------
Chaper Seven 浏览器的同源策略
什么是浏览器的同源策略
- 同源指的是:协议,域名和端口必须一致
- 同源策略 限制了从一个源加载的文档或脚本如何与另外一个源的资源进行交互。
- 同源策略 是浏览器的一种隔离潜在危险文件的重要安全机制
同源策略限制了三个事情,是对JS脚本的限制:
- 当前域下的JS脚本不能访问其他域下的cookie、localStorage和indexedDB
- 当前域下的JS脚本不能访问操作其它域下的DOM
- 当前域下Ajax无法发送跨域请求
如何解决跨域问题
① CORS 跨域资源共享
概述:
- CORS,Cross-Origin Resource Sharing,即跨域资源共享。
- CORS需要浏览器和服务器同时支持
- 整个CORS过程都是浏览器自动完成,而不需要用户参与;
- 关键是在于服务器需要实现CORS接口
- 浏览器会将CORS请求分作 简单请求 和 非简单请求
简单请求
只要满足两大条件,就是简单请求。否则是非简单请求:
① 请求方法为以下三种:
- HEAD
- GET
- POST
② 请求头部信息不超过以下字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-Id
- Content-Type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求的过程:
- 浏览器若发现这次AJAX跨域请求是简单请求,则自动在头部信息添加Origin字段(协议+域名+端口),以说明本次请求的来源。
- 服务器根据Origin的值,决定是否同意此次请求:
- 若Origin指定的源不在服务器许可范围内,服务器返回正常的HTTP响应(状态码是200,不可以用状态码来判断是否出错)。但是,浏览器会发现这个响应头信息 没有 Access-Control-Allow-Origin,因而抛出错误,被XMLHttpRequest的onerror函数捕获。
- 若Origin指定的源在服务器许可范围内,返回的响应头会多几个字段(Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Expose-Headers)
浏览器自动添加Origin字段
GET /cors HTTP/1.1
Origin: http://api.bob.com // 浏览器自动添加的
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果Origin指定的源在服务器许可范围内,返回的响应头多几个字段:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
- Access-Control-Allow-Origin 表示可以接受的源 (必须)
- Access-Control-Allow-Credentials 表示是否允许发送Cookie(可选)
- Access-Control-Expose-Headers 返回其它的字段 (可选)
非简单请求 (预检请求)
若不满足以上两大条件,则会是非简单请求,通常可能是:
- 请求方法为PUT或DELETE
- Content-Type字段值为application/json
若浏览器发现该请求是非简单请求,在正式通信之前,会自动发送一个预检请求,询问服务器:当前域名是否在许可范围内、可以使用哪些HTTP动词和头部字段
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
预检请求:
- 请求方法是OPTIONS
- 关键字段是Origin 表示该请求来于哪个源
- Access-Control-Request-Method 列出使用的HTTP方法
- Access-Control-Request-Headers 列出额外发送的头信息字段
服务器接收到预检请求,则会检查Origin、Access-Control-Reques-Method、Access-Control-Request-Headers,决定是否允许跨域请求:
- 如果服务器否定预检请求,会返回正常的HTTP响应。但是,浏览器会发现没有任何CORS相关字段,则认定出错。由XMLHttpRequest对象的onerror函数捕获。
- 如果通过预检请求,服务器可能返回以下CORS相关字段:Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Credentials、Access-Control-Max-Age
回应:
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
- Access-Control-Allow-Methods 服务器列出所有它支持的请求方法(必须)
- Access-Control-Allow-Headers 如果预检请求有Access-Control-Request-Headers,那么就是必须的。
- Access-Control-Allow-Credentials 可选
- Access-Control-Max-Age 可选
一旦服务器通过了预检请求,那么浏览器再正式发送请求
② JSONP
- JS动态创建一个script标签,script标签没有跨域限制
- 利用script标签的src发送一个跨域GET请求,其中包括请求的数据、callback回调函数等参数
- 后端接收到后,将所请求的数据从数据库取出,以JSON格式存储
- 后端将JSON格式的数据,当作参数放到所传入的回调函数中。从而形成一段JS脚本代码。
- 后端将这段JS脚本代码返回到前端(src是将资源引入并替换当前元素)
- 前端会执行这段JS脚本代码:执行回调函数中的操作逻辑,并且拥有了后端返回的数据
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res)
// 对跨域请求数据的逻辑操作
alert(JSON.stringify(res));
</script>
③ nginx代理 ***
其它
- document.domain + iframe
- location.hash + iframe
- window.name + iframe
-----------------------------------
Chaper Eight 其它
事件循环
事件循环所涉及的基本概念:
- Call Stack 调用栈:后进先出。所有的代码都要放到调用栈中等待主线程执行。函数被调用时,将其添加到调用栈的栈顶,执行完则退出调用栈。
- Event Table 事件表格:事件表格中存储着 异步事件与异步事件对应的回调函数 的映射关系,即 异步事件 -> 对应的回调函数
- Event Queue 事件队列:先进先出。当事件表格中某一异步事件触发时,则将其对应的回调函数添加到事件队列中。
- Macro Task 宏任务:常见有script、setTimeout 和 setInterval
- Micro Task 微任务:常见有promise.then
事件循环的原理:
- 任务进入 Call Stack 调用栈
- 若是同步任务,则在调用栈中等待主线程执行。
- 若是异步任务,则从调用栈移出,到事件表格中注册,形成异步事件与回调函数的映射关系。
- 若事件表格中某一异步事件被触发,则将其对应的回调函数添加到事件队列之中(又分微任务队列和宏任务队列)
- 当主线程执行完调用栈中的任务后,主线程处于闲置状态。主线程先去查看 微任务队列 中是否有微任务可执行:若有,则逐个取出到栈中执行。微任务队列为空后,再去查看宏任务队列。
以上是关于浏览器相关原理的主要内容,如果未能解决你的问题,请参考以下文章