一、浏览器进程
浏览器的进程
主要有:
- Broswer进程:负责主控和协调,创建和销毁其它进程;控制下载;绘制位图等。
- GPU进程:用于3D绘制;同一时刻最多拥有一个GPU进程
- Render进程:处理页面渲染、脚本执行、事件处理等;一般来说一个标签页对于一个Render进程,当然也看具体浏览器的实现
- 插件进程:处理插件相关工作。
- 网络进程:处理网络请求相关工作。
Render进程的线程
Render进程是和前端密切相关的进程了,它拥有多个线程
- GUI渲染线程(主线程):负责页面的构建,如解析HTML、CSS,构建布局树、图层树,生成绘制列表等
- 合成线程:将图层分为图块,并把视口附近图块提交给光栅化线程
- 光栅化线程(光栅线程池)::生成位图,即页面需要的每个像素点的颜色值
- JS引擎线程:负责解析和执行js脚本,一个Render进程只有一个JS引擎线程,故常JS被称为单线程,而且该线程和GUI渲染线程是互斥的,这也是为什么JS中执行大量复杂计算容易导致页面渲染不连贯的原因
- EventLoop轮询处理线程:控制事件循环;当JS引擎线程执行遇到像setTimeout这种异步执行代码块,会通知EventLoop轮询处理线程,之后由它进一步交付给对应异步线程并且等待触发条件成立时,又由它将回调函数加入相应处理队列,等待处理。
- 定时器触发线程:负责定时器事件的计时,时间到了便会通知轮询处理线程将回调函数加入处理队列
- 异步HTTP请求线程:XMLHttpReque在连接之后新启动的一个线程,该线程如果检测到请求的状态发生变更,如果设置有回调函数,会将回调函数加入事件队列。
- 浏览器事件线程:浏览器事件线程会先监听dom,待到事件发生后通知轮询处理线程将回调函数加入处理队列
将进程结合页面渲染过程来看
前戏:
- 发送网络请求之后得到的响应数据由网络进程接收;并且解析响应头,成功则继续处理请求
- broswer进程根据响应头中的Content-type字段判断,如果为application/octet-stream,则启动下载进程;为text/html则启动渲染进程,broswer进程会发送一个“接收文档”消息给渲染进程;
- 渲染进程接收到“接收文档”的消息后,会和网络进程建立一个通道,接收数据。
- 渲染进程接收到数据后,向broswer进程发送“确认接收”消息
- 浏览器主进程接收到“确认接收”的消息后,开始更新浏览器页面,包括:地址栏的url,前进后退按钮。之后开始执行Render进程
正戏:
Render进程履行职责
- 渲染进程开始接受到数据的时候,会先预扫描接收到的数据,如果如果发现有需要加载资源的标签(img,link,外部script等),就先告诉浏览器主进程,先去下载,这个过程叫
预解析
- 然后开始解析HTML
- 遇到HTML标签则生成节点,构建DOM树
- 遇到CSS代码则根据css的样式选择器构建CSSOM
- 遇到CSS链接会继续下载
- 遇到JS代码,会阻塞DOM构建,等待css代码下载完并解析完毕,然后再执行js代码
- 遇到JS链接,如果是普通链接,则和上面一样;如果有async则等待JS下载完成,然后上面的流程;如果有defer,则会等到JS下载完毕且HTML解析完成再执行JS代码
- 之后根据DOM树和CSSOM构建布局树
- 构建完布局树,GUI线程还会对节点进行分层,构建图层树
- 构建好图层树后,GUI线程会为每一个图层
创建绘制指令
列表之后交由合成线程来处理 - 合成线程将图层分块,把浏览器用户
视口附近
的图块优先交给栅格化线程来生成位图;栅格化的过程通常会用GPU执行,就是说栅格化线程会把绘制图块的指令发送给GPU,然后GPU生成图块的位图(像素点的颜色值),存在GPU内存。 - 完成栅格化后,合成线程发出DrawQuad指令给broswer进程的
VIZ组件
VIZ组件
将页面绘制到内存中,此时页面数据已经完成绘制- 之后发给显卡处理;显卡分为
前缓冲区
和后缓冲区
,数据首先被送至后缓冲区
,显卡将其合成为图片,之后前、后缓冲区对调,图片显示在屏幕上。
启发
-
CSS尽量放在前面,JS文件尽量放在后面,如果能够使用async和defer则尽量使用
-
重绘会重新计算样式,跳过生成布局树和图层树,然后生成绘制列表及之后的步骤,而GUI线程和JS引擎线程互斥,因此很大程度上避免了两者的冲突;回流则重新构建布局树,对性能影响极大。
-
位于合成层的元素如果发生改变,直接由合成线程处理,不占用GUI线程资源,即使GUI线程线程卡住,像动画效果这种仍然流畅显示
因此,考虑到上面的这些情况:
- 使用createDocumentFragment进行批量的DOM操作
- 对于resize,监听滚动这种进行防抖/节流处理
- 避免频繁修改样式,尽量使用改变class的方式一次性修改
- 利用合成,使用CSS来实现动画效果,最好的方式是使用CSS的will-change属性
- 避免使用过于复杂的属性选择器
WebWorker
-
创建Webworker时,JS引擎向浏览器申请开一个子线程用于处理一个命名文件中的JS代码,它不能操作DOM,因为运行在另一个全局上下文中;可以通过postMessage和JS引擎线程通信,传递数据。所以耗时的计算操作可以通过Worker线程来执行,然后将结果返回给JS引擎线程;
-
SharedWorker是浏览器中的一个进程,所有页面共享;WebWorker是线程,属于单个页面
二、浏览器缓存
1、HTTP缓存
强缓存
在发送HTTP请求之前,先检查强缓存,如果命中则直接返回。
如何检查强缓存?
- HTTP/1.0通过
Expires
字段检查,该字段存在于服务器返回的响应头中
Expires: Wed, 22 Jun 2021 08:41:00 GMT
表示资源在2021年6月22号8点41分过期
存在的问题:服务器时间于客户端时间可能不一致,这导致过期时间不准确
- HTTP/1.1采用
Cache-Control
字段,该字段使用过期时长来控制
Cache-Control: max-age = 3600
表示资源在接收到响应后3600s内可以直接使用缓存
它还有private
(禁止代理缓存)、s-maxage
(代理缓存过期时长)、no-cache
(不使用强缓存,使用协商缓存)、no-store
(禁止缓存)等字段
强缓存的使用
- 对于频繁变动的资源,显然使用强缓存不能保证每次获得最新的数据,因此建议设置Cache-Control字段为no-cache;在使用时会判断是否过期(即协商缓存)
- 对于不常变化的数据,可以将Cache-Control设置为一个大的max-age;如果后续有更新数据的需求可以在url之后添加hash、版本号、随机数等动态字符从而更改url,使得之前的强缓存失效
协商缓存
强缓存如果没有命中,客户端发送HTTP请求,并在请求头中携带相应的缓存tag
来决定是否使用协商缓存
缓存tag
-
If-Modified-Since
和Last-Modified
首次请求时,服务器在响应体中加上
Last-Modified
字段告知客户端该资源的最后修改时间;客户端如果再次请求时,会设置If-Modified-Since
值为之前服务器发回的Last-Modified
这个值;服务器收到后对比If-Modified-Since
和服务器端该资源当前Last-Modified
,如果不一致,则资源过期,命中失败,否则说明资源在这段时间没有改变,服务器返回304告知客户端直接使用缓存。 -
ETag
和If-None-Match
ETag
是服务器根据文件内容生成的一个标识符,如果文件内容改变则该值也会改变;浏览器收到该值后,会在下次请求中将这个值设置为If-None-Match
字段的值;服务器接收到If-None-Match
后,对比当前服务器中该资源的Etag
值,如果不一致,说明资源发生改变;否则返回304; -
二者区别
Etag
是根据内容而定的,而Last-Modified
关注的是是否进行过修改操作;用过word写过东西的人知道,如果你添加一个字之后又将其删除,它也会认为你进行了修改,即使内容没有发生实质上的改变,Last-Modified
就是这样,它会记录该时间。因此
Etag
是更加精准
的;还有就是Last-Modified
的最小感知时间是1s,如果在发送给客户端Last-Modified
后的1s中之内服务器端进行了更改,Last-Modified
值不会变化;在性能上,
Last-Modified
是优于Etag
的如果两种方式都支持,服务器会优先考虑
Etag
2、本地缓存
本地缓存属于浏览器内置的存储功能了,优点是可以通过JS进行控制,比HTTP缓存灵活
Cookie
一般由服务器生成,Cookie设计初衷就是用于弥补HTTP无状态的不足,它是一个很小的文本文件,4kb;内部以键值对
的形式存储;Cookie与域名绑定(同一域名的一、二级是可以共享使用的),不可跨域;默认保存在与内存中,随着浏览器关闭消失,可以设置过期时间,设置了过期时间后会被保存在硬盘中,时间到了才消失
应用场景:
- 保存账号密码;
- 保存上次登录的时间信息;
- 保存上次查看的页面;
- 浏览计数
缺陷:1、不管该域名下的某一地址需不需要,发送请求时都会带上Cookie,造成性能浪费;2、以纯文本形式在客户端和服务器之间传递,很容易被截获和篡改;在HttpOnly为false的情况下,Cookie还能被JS直接读取
WebStorage
HTML5引入了WebStorage,分为两种,二者皆采用键值对形式存储,值为字符串类型,因此传递对象之前先使用JSON.stringify处理
LocalStorage
同样是针对一个域名,容量上限为5MB,永久存储;只存在于客户端,避免了发送导致性能问题以及可能被截获的问题;封装的整体,通过其localStorage
暴露的方法对其进行操作,方便。
应用场景
-
内存容量大,持久,因此适合用于存储一些内容稳定,较大的资源如官网的logo;
-
localStorage还可以进行页面间传值;
-
访问页面计数;
-
由于Vuex刷新后会回到初始状态,因此可以在Vuex数据改变时将其存入local Storage,刷新页面后再取出;
操作方式:
let obj = {height:180 ,age: 18};
localStorage.setItem("name", "orange");
localStorage.setItem("info", JSON.stringigy(obj));//存储字符串类型
//在同一域名下可以拿到相应值
let name = localStorage.getItem("name");
let info = JSON.stringify(localStorage.getItem("info"));
sessionStorage
容量上限5MB;只存储于客户端;封装,通过sessionStorage
暴露方法;这些性质和localStorage
几乎一样;但是sessionStorage
于它的本质差别就是当会话结束,sessionStorage
就消失了,它只临时存在于会话期间,它引入了一个"浏览器窗口概念",刷新和进入同源另一个页面,sessionStorage仍然存在,一旦关闭窗口就被删除了;两个窗口打开同一个页面它们的sessionStorage也是不同的。
应用场景
- 用于存储表单信息,即使页面刷新也不会让之前的表单信息丢失,用于存储本次的浏览记录,如果页面关闭后就不再需要的话。
- 可以单标签页同源页面之间传值
IndexDB
运行于浏览器的关系数据库
,拥有数据库本身的特性;还有一些独特的特性:1、键值对存储 2、支持异步I/O操作 3、不可跨域访问数据库
三、XSS攻击
全程是Cross Site Scripting,即跨站脚本攻击,是web应用中的计算机安全漏洞,它允许恶意web用户植入代码到提供给其他用户使用的页面当中,本质是恶意代码未经过滤,与正常网站代码混合,无法被区分,导致浏览器执行恶意脚本。
一般可以完成下面这些操作:
- 窃取cookie
- 监听用户行为,如输入账号密码后盗取
- 修改DOM伪造登录表单
- 在页面中生成浮窗广告
XSS攻击的实现的三种方式:
1、存储型
攻击步骤:
- 攻击者将恶意代码提交至目标网站数据库中
- 用户打开目标网站,网站服务器将恶意代码从数据库取出,拼接到HTML,返回给浏览器。
- 浏览器解析HTML,执行恶意脚本
- 恶意脚本窃取用户数据或者冒充用户行为
常见的像是在评论区提交一段脚本代码,如果前后端没有做好转义工作,便存储到了服务器的数据库中,在页面渲染过程直接执行。
2、反射型
攻击步骤:
- 用户构造特殊URL,包含恶意代码,诱导用户点击
- 用户点击恶意URL,服务器将恶意代码从URL中取出,拼接到HTML上返回给客户端
- 浏览器解析HTML,执行恶意脚本
- 恶意脚本窃取用户数据或者冒充用户行为
反射性攻击常见于网站搜索,跳转等,通过诱导用户点击恶意链接,指的是恶意脚本作为网络请求的一部分,比如:
http://xxxx.com?s=<script>alert("hello")</script>
服务器拿到参数s,将其拼接到HTML上,然后返回给浏览器,浏览器将这些内容作为HTML内容进行解析,发现是个脚本,于是执行;
与存储型的区别:
存储型恶意代码存放在服务器,持久;反射性恶意代码存在于URL,非持久化
当然你可能会想,用户为什么要点击这些恶意URL,或者这些恶意URL是怎么到页面上的呢?
这其实有很多实现手段,比如结合ClickJacking(鼠标劫持);通过嵌入透明的iframe页面让你主动点击
3、DOM型
原因在于前端Javascript代码不够严谨,把不可以信任的数据当作代码执行; 比如使用innerHTML、outerHTML、document.write()插入了不可信数据
防范措施
针对XSS攻击两大要素:攻击者提交恶意代码;浏览器执行恶意代码
-
输入过滤:
前端过滤
:在用户提交时,由前端过滤输入,然后提交至后端;这样做的弊端是假如攻击者绕过前端,直接构造请求,就可以提交恶意代码了。后端过滤
:后端在写入数据库前对输入进行过滤;弊端:由于后端数据可能提供给web前端和app客户端,如果后端采用escapeHTML()对输入内容进行编码过滤,返回给app客户端显示时就成了乱码。即后端无法明确内容最终输出到哪,而不同情况下采取的过滤方式还不同。 -
纯前端渲染:用于防止HTML注入。过程:浏览器先加载一个静态HTML,不包含任何业务相关数据,然后执行HTML中的Javascript,通过Ajax加载业务数据,调用DOM API更新页面。即明确的告知浏览器接下要设置的内容是文本还是样式等,浏览器不容易被欺骗去执行预期外的代码。
-
避免DOM型:谨慎使用innerHTML这些方法插入,尽量使用textContent,setAttribute这些方法。DOM的内联事件监听器,Javascript的eval,onclick,location,setTimeout等方法,a标签的href属性等都能直接执行字符串,应避免将不可信任的数据传递给这些API。
-
其它方案:利用CSP,即浏览器内容安全策略,它会1、限制其他域下的资源下载 2、禁止向其他域提交数据 3、提供上报机制,及时发现XSS攻击;利用Cookie的HttpOnly属性
四、CSRF攻击
攻击步骤如下:
- 用户访问目标网站(a.com),并且保留登录凭证
- 攻击者引诱用户点击链接,进入恶意网站(b.com)
- 用户点击后,b.com发送请求如(a.com/act=xx)(可以通过图片或者script等来实现,也可以通过自动提交POST表单实现);浏览器会默认带上a.com的Cookie
- a.com执行act=xx,攻击完成
全称Cross-site request forgery 即跨站请求伪造;指的是攻击者诱导目标点击自己的网站,然后利用用户目前的登录状态发起跨站请求。可能是下面这些情形:
1、自动发送GET请求
当你点击进入攻击者网站后,网站内可能有这样一段代码:
<img src="https://xxx.com/info?user=xxx%count=100">
一旦进入该页面,便会通过图片自动向xxx.com发送get请求,该请求会带上用户在xxx.com的Cookie信息(假定用户已经在xxx.com中登录过);如果服务器没有相应的验证机制,他可能认为发送请求的是正常用户,然后攻击者可以进行各种操作
2、自动发送POST请求
攻击者写了一个表单,并且进行提交,同样携带用户的Cookie信息,然服务器误认为是用户在操作
3、诱导点击
在攻击者的网站上可能有一些链接,诱导用户点击,这个和图片那个自动发送get请求差不多。
防范措施
-
CSRF能够实现的原因携带用户的Cookie信息,伪装成用户进行操作。因此可以从Cookie上下手,Cookie的SameSite属性可以进行控制:1、设置为Stric,浏览器严格限制第三方携带Cookie 2、Lax,只能在get方法中提交表单或者通过a标签发送get请求时允许第三方携带cookie 3、None,默认模式,请求会自动带上Cookie;
-
也可以验证来源站点:通过验证请求头中Origin和Referer两个字段,Origin只包含域名信息,Referer包含了具体路径;不过二者都可以伪造,在AJAX自定义请求头中修改即可,因此有效性较差
-
还可以通过CSRF Token,浏览器发送请求时,服务器会生成一个字符串,植入返回的页面中,之后浏览器发送请求必须带上这个字符串,通常第三方站点无法获得这个token
-
使用验证码,操作时需要用户输入验证码确认
五、标记清除算法和引用计数算法
标记清除算法:从根开始访问,标记所有可访问的对象;垃圾收集算法扫描并回收所有的未标记对象;
弊端:标记清除算法进行垃圾回收的时机是在内存耗尽时,程序被挂起,然后进行垃圾回收,显然这会造成页面在某个时间段内失去响应
好处:不会出现循环引用;对于引用对象的常规操作不会产生任何的额外开销。
引用计数:跟踪每个值被引用的次数;
弊端:循环引用
六、浏览器同源策略的作用?
同源策略限制:
- 读取非同源的cookie,localStorage和indexDB
- 获取非同源页面的DOM
- 进行非同源Ajax请求
作用:
一 一对应上面:
- 防止恶意网站通过js获取用户其他网站的cookie
- 防止恶意网站通过iframe获取其它网站页面信息,操纵其他网站DOM,执行恶意操作
- 防止恶意网站盗取用户在其它网站的信息
跨域实现
这个我就不多说了,很多博客总结的很清楚,这里推荐一篇播客吧: