前言
笔者今年2022寒冬
下成功跳槽了阿里
,这篇文章就是将自己面试
的一些准备
、知识总结
分享出来~
如果这篇文章对你有用,
请一键三连(点赞评论+收藏)
让更多的同学看到
如果需要
转载
,请评论区留言
,未经允许请不要私自转载
;
防杠声明
这篇文章不是纯堆砌面试题
,而是以知识总结
为主,主观观点和主观总结居多
,里面总结的知识点在我这次的面试中也不全都有用到
~如果有写错的地方欢迎评论区提出,如果只是要杠
那请右上角X
掉慢走;
传送门
这个专栏预计要做以下这些内容,可以根据自己的需要跳转查看
「2022」寒冬下我的面试知识点复盘【JS】篇(加紧编写中)
「2022」寒冬下我的面试知识点复盘【Vue3、Vue2、Vite】篇
「2022」寒冬下我的面试知识点复盘【工程化】篇(加紧编写中)
「2022」寒冬下我的面试知识点复盘【Nodejs】篇(加紧编写中)
「2022」寒冬下我的面试知识点复盘【TypeScript】篇(加紧编写中)
本文标题思维导图
浏览器原理 篇
1.浏览器缓存机制
缓存行为
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
强制缓存优先于协商缓存进行,若强缓存生效则直接使用强缓存,若强缓存不生效则进行协商缓存
协商缓存由
服务器
决定是否使用缓存;若协商缓存失效,那么就返回200
,重新返回资源和缓存标识,再存入浏览器缓存中;若协商缓存生效则返回304
,继续使用缓存。
缓存位置
Service Worker
:是运行在浏览器背后的独立线程,无法直接访问DOM
,但可以用来做离线缓存
、消息推送
和网络代理
。传输协议必须为HTTPS
。Memory Cache
:内存中的缓存Disk Cache
:存储在硬盘中的缓存Push Cache
:(推送缓存
)是HTTP/2
中的内容;
注:
HTTP2
的服务器推送功能,在Chrome106
版本后不可用;
详细的缓存过程
- 浏览器第一次加载资源,服务器返回
200
,浏览器将资源文件从服务器上请求下载下来,并把response header
及该请求的返回时间一并缓存;
- 下一次加载资源时,先比较当前时间和上一次返回
200
时的时间差,如果没有超过cache-control
设置的max-age
,则没有过期,命中强缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持HTTP1.1
,则用expires
判断是否过期);如果时间过期,则向服务器发送header
带有If-None-Match
和If-Modified-Since
的请求
- 服务器收到请求后,优先根据
Etag
的值判断被请求的文件有没有做修改,Etag
值一致则没有修改,命中协商缓存,返回304
;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200
;
- 如果服务器收到的请求没有
Etag
值,则将If-Modified-Since
和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304
;不一致则返回新的last-modified
和文件并返回200
;
强缓存
缓存标识
强缓存
可以通过设置两种 HTTP Header
实现:Expires
和 Cache-Control
。强缓存表示在缓存期间不需要发送请求
Expires
是HTTP/1.0
的产物。值代表的是服务端的时间,并且Expires
受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-Control
出现于HTTP/1.1
,优先级高于Expires
。该属性值表示资源会在多少秒
后过期,需要再次请求。
字段 | 协议版本 | 缓存类型 | 响应头 | 请求头 |
---|---|---|---|---|
Expires | HTTP1.0 | 强缓存 | √ | X |
Cache-Control | HTTP1.1 | 强缓存 | √ | √ |
Cache-Control 属性
Cache-Control
首部字段是 HTTP/1.1
中定义缓存的字段,其用于控制缓存的行为,可以组合使用多种指令,多个指令之间可以通过 “,” 分隔
// eg:
Cache-Control: max-age:3600, s-maxage=3600, public
Cache-Control: no-cache
max-age
指令 给出了缓存过期的相对时间,单位为秒数。时间是相对于请求的时间。s-maxage
指令 与max-age
不同之处在于,其只适用于公共缓存服务器,比如资源从源服务器发出后又被中间的代理服务器接收并缓存。当使用s-maxage
指令后,公共缓存服务器将直接忽略Expires
和max-age
指令的值。public
指令表示该资源可以被任何节点缓存(包括客户端和代理服务器)private
指令表示该资源只提供给客户端缓存,代理服务器不会进行缓存。同时当设置了private
指令后s-maxage
指令将被忽略。
no-cache、no-store 的区别
no-cache
和no-store
这两个指令在请求
和响应
中都可以使用no-store
是真正的不进行任何缓存,告知服务器和缓存服务器,我请求、响应的内容里有机密信息;- 当
no-cache
在请求头
中被使用时,表示强制使用协商缓存
- 当
no-cache
在响应头
中被返回时,表示缓存服务器不能对资源进行缓存,客户端可以缓存资源,但每次使用缓存资源前都必须先向服务器确认其有效性
协商缓存
缓存过程
如果首次请求时没有
Cache-Control
和Expires
;或者Cache-Control
的属性设置为no-cache
时,又或者如果缓存过期了
。就需要发起请求验证资源是否有更新。向服务器发送请求时,服务器会根据这个请求的请求头
里的If-Modified-Since
和If-None-Match
来判断是否命中协商缓存,如果命中,则返回304
状态码并且更新浏览器缓存有效期
缓存标识
Last-Modified
表示本地文件最后修改时间,发送请求时,会将当前的Last-Modified
值作为If-Modified-Since
这个字段的内容,放在请求头中发送给服务器,去询问服务器在该时间后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回304
状态码。
ETag
类似于文件指纹,请求时会将当前ETag
作为If-None-Match
这个字段的内容,并放到请求头中发送给服务器,服务器接收到If-None-Match
后,会跟服务器上该资源的ETag
进行比对:,有变动的话就将新的资源发送回来。否则返回304
状态码
字段 | Header类型 | 协议版本 | 缓存类型 |
---|---|---|---|
Last-Modified | Response(响应头) | HTTP1.0 | 协商缓存 |
If-Modified-Since | Request(请求头) | HTTP1.0 | 协商缓存 |
ETag | Response(响应头) | HTTP1.1 | 协商缓存 |
If-None-Match | Request(请求头) | HTTP1.1 | 协商缓存 |
协商缓存两属性对比
- 在
精准度
上,ETag
优于Last-Modified
。由于ETag
是按照内容给资源上标识,因此能准确感知资源的变化。而Last-Modified
就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:- 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
Last-Modified
能够感知的单位时间是秒,如果文件在1
秒内改变了多次,那么这时候的Last-Modified
并没有体现出修改了。
- 在
性能
上,Last-Modified
优于ETag
,也很简单理解,Last-Modified
仅仅只是记录一个时间点,而Etag
需要根据文件的具体内容生成哈希值。 - 另外,如果两种方式都支持的话,服务器会优先考虑
ETag
。
用户行为对浏览器缓存的影响
打开网页,地址栏输入地址
: 查找disk cache
中是否有匹配。如有则使用;如没有则发送网络请求。
普通刷新 (F5)
:不使用强缓存,会判断协商缓存;且因为TAB
并没有关闭,因此memory cache
是可用的,会被优先使用(如果匹配的话)。其次才是disk cache
。
强制刷新 (Ctrl + F5)
:浏览器不使用缓存,服务器直接返回 200 和最新内容
启发式缓存
在资源请求的响应头中没有出现Expires
和 cache-control:max-age
字段值, 并且没有限制no-store
,并且设置了Last-Modified
, 那么浏览器默认会采用一个启发式的强缓存算法。
通常会根据响应头中的 Date
减去 Last-Modified
值的 10%
作为缓存时间。
2.浏览器渲染机制
详细渲染过程
- 构建
DOM
树:浏览器从上到下
解析HTML
文档生成DOM
节点树; - 构建
CSSOM
树:浏览器解析遇到样式
时,会进行异步下载
,下载完成后构建CSSOM
树; - 值得一提的是,浏览器解析过程中遇到 图片时,会进行异步下载;当遇到不带
async
和defer
的script
时,会阻止解析HTML
并进行下载和执行; - 并且
CSS
和DOM
渲染,JS
和DOM
解析之间是有阻塞关系
的; - 构建渲染树:根据
DOM
节点树和CSSOM
树构建渲染树(Render
); - 布局(
Layout
):根据渲染树将DOM
节点树每一个节点布局在屏幕上的正确位置; - 绘制(
Paint
):绘制所有节点,为每一个节点适用对应的样式,绘制到屏幕上;- 绘制的过程中还有很多细节,包括说:
- 构建图层树:需要对
布局树
进行分层,生成图层树
(比如说Z轴排序) - 生成绘制列表:将
图层
的绘制拆分为很多的绘制指令
,并按顺序
组成绘制列表
,并提交到合成线程
中; - 光栅化(
栅格化
)生成位图:合成线程
将图层
划分成图块
,并在光栅化线程池
中将图块
转换成位图
。- 同时因为用户只能看到
视口
的这一部分,所以合成线程
就会按照视口
附近的图块
来优先生成位图
,
- 同时因为用户只能看到
- 显示:一旦所有的图块都被光栅化,合成线程就会提交绘图指令给浏览器进程;浏览器进程生成页面并显示到屏幕上;
3.浏览器资源解析机制
整体流程
- 浏览器开始
解析HTML
,此时document.readystate
为loading
- 解析中遇到不带
async
和defer
的script脚本
时,需要等待script脚本
下载完成并执行后,才会继续解析HTML
; - 当文档完成解析,
document.readyState
变成interactive
,触发DOMContentLoaded事件
- 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,
document.readyState
变为complete
,window
触发load
事件
浏览器解析 不同资源 时的行为
- 浏览器解析遇到
CSS样式资源
时,CSS
会异步下载,不会阻塞浏览器构建DOM
树,但是会阻塞渲染,在构建渲染树时,会等css
下载解析完毕后才进行(防止css
规则不断变化)
- 浏览器解析遇到
JS脚本资源
时,需要等待JS脚本
下载完成并执行后才会继续解析HTML
;但是当脚本加上defer
与async
时又不一样,defer
是延迟执行,async
是异步执行;
CSS
加载会阻塞后面的的JS
语句的执行,因为HTML5
标准中有一项规定,浏览器在执行Script
脚本前,必须保证当前的的外联CSS
已经解析完成,因为JS
可能会去获取或者变更DOM
的CSS
样式,如果此时外联CSS
还没解析好,获取到的结果就是不准确的;
- 解析遇到
Img图片
时,直接异步下载,不会阻塞解析;下载完毕后用图片替换原有src的地方;
- 总结:
CSS
会阻塞浏览器渲染;JS
会阻塞浏览器解析;CSS
会阻塞后面的JS
执行;IMG
不会阻塞;
为什么 CSS 要放在头部
- 外链
css
无论放在html
的任何位置都不会影响html
的解析,但是会影响html
的渲染;
- 如果将
css
放在尾部,html
的解析不受影响,浏览器会在css
样式加载解析完后,重新计算样式绘制,造成回流重绘
、页面闪动
等现象;
- 而如果将
css
放在头部,css
的下载解析时可以和html
的解析并行,并且会等待css
下载解析完毕后开始绘制;
为什么 Script 要放在尾部
- 因为当浏览器解析到
script
时,就会立即下载执行,中断html
的解析过程,因为js
可能会修改dom
元素;如果外部脚本加载时间长,就会造成网页长时间未响应;
async 和 defer 的解析过程
- 浏览器解析到带
async
属性的script
标签时,不会中断html
解析,而是并行下载脚本;当脚本下载完成后,中断解析并执行脚本; - 浏览器解析到带
defer
属性的script
标签时,不会中断html
解析,而是并行下载脚本;当浏览器解析完HTML
时、DOMContentLoaded
事件即将被触发时,此时再执行下载完成的脚本;
async 和 defer 的区别
async
和defer
都仅对外部脚本有效async
标志的脚本文件一旦加载完成就立即执行
;而defer
标志的脚本文件会在HTML解析完成且DOM构建完毕
后再执行;(也就是说defer
是延迟执行,async
是异步执行)- 如果有多个
js脚本
,async
标记的脚本哪个先下载结束,就先执行那个脚本。而defer
标记则会按照js
脚本书写顺序执行。 - 如果同时使用
async
和defer
属性,defer
不起作用,浏览器行为由async
属性决定。 DOMContentLoaded
事件会等待defer
的脚本执行完后才触发;
DOM树 和 CSSOM树 的构建顺序关系
- 实际上,构建
DOM树
和 构建CSSOM树
是并行的;这也正解释了为什么CSS
加载不会阻塞DOM解析
,但是因为渲染树需要依赖DOM树
和CSSOM树
,所以会阻塞DOM
渲染;
CSS 解析规则
- 浏览器解析
CSS
选择器的方式是从上到下、从右到左,因为从右往左
只需要最右边的一个不匹配,就可以直接舍弃,避免了许多无效匹配。
- 一句话总结: 浏览器的这种查找规则是为了 尽早过滤掉一些无关的样式规则和元素。
Load 和 DOMContentLoaded 区别
Load
事件触发代表页面中的DOM
,CSS
,JS
,图片已经全部加载完毕。
DOMContentLoaded
事件触发代表初始的HTML
被完全加载和解析,不需要等待CSS
,带 async 的 JS
,图片加载;此时所有的DOM
都构建完毕;
-
在应用场景下:
- 如果我们想在回调中操作
dom
:添加、删除某些元素时,使用domcontentloaded
; - 如果想知道图片宽高、
iframe
内容等资源信息,需要在load
事件里处理;
- 如果我们想在回调中操作
4.浏览器安全
XSS
基本概念
XSS
(跨站脚本攻击
)是指攻击者在返回的HTML
中嵌入javascript
脚本,从而拿到用户的信息并进行操作。XSS
分为三种:存储型
,反射型
和文档形
存储型
存储型
的XSS
将脚本存储到了服务端的数据库,然后在客户端执行这些脚本,从而达到攻击的效果。- 常见的场景就是评论区提交一段脚本代码,如果前后端没有做好转义,存储到数据库后,在客户端渲染时直接执行;
反射型
- 反射形
XSS
攻击指的是恶意脚本作为请求URL
的参数;浏览器解析后作为脚本执行, - 之所以叫它
反射型
, 是因为恶意脚本是通过作为网络请求的参数出现在url
中,经过服务器解析响应,拼接在HTML
中传回给客户端,然后浏览器解析执行恶意脚本。 - 和
存储型
不一样的是,服务器并不会存储这些恶意脚本。
文档型
文档形
的XSS
攻击其实也是恶意脚本被作为请求URL
的参数;浏览器解析后作为脚本执行,和反射形的区别在于:由前端JS
取出URL
中的恶意代码并执行
防范措施
- 最普遍的做法就是
转义
和过滤
:对引号,尖括号,斜杠进行转义,让代码在html
解析的过程中无法执行;过滤就是把script
标签给删除;
- 利用
HttpOnly
:cookie
设置httponly
后,会禁止javascript
脚本来访问cookie
,这样,XSS
攻击之后也无法获取用户的cookie
;
- 其次就是使用
CSP
:CSP
也就是浏览器内容安全策略;只允许加载指定域的脚本及样式;
CSRF
基本概念
CSRF
(跨站请求伪造) 就是黑客诱导用户跳转恶意网站,然乎利用用户的登录态发起恶意请求;- 原理就是
http
请求会自动携带Cookie
,而且是HTTP
目标请求域名
的Cookie
防范措施
SameSite Cookies
: 该属性表示Cookie
不随着跨域请求发送,可以很大程度减少CSRF
的攻击;它有Strict
(浏览器将只发送相同站点(完全一致)请求的Cookie
)、Lax
(第三方get
方法可以携带Cookie
) 和None
(任何情况下都会发送Cookie
)三个值。Origin
和Referer
:验证Referer
是否是从第三方网站发出来的,阻止第三方网站请求接口,但是这两者可以通过ajax
自定义请求头的方式被伪造;CSRF Token
:客户端向服务端请求token
,然后在所有的请求中带上;
Chrome80 版本的 CSRF 例子
讲到 CSRF
的 cookie
,不得不提一下 Chrome 80
版本的的一个默认设置;Chrome 80
版本将 SameSite
的值设置为了 Lax
;这导致之前公司有一些业务产生了跨域;
XSS 和 CSRF 的区别
- 两者的原理区别:
CSRF
是利用网站A
本身的漏洞,去请求网站A
的api
。而XSS
是向网站A
注入JS
代码,然后执行JS
里的代码,篡改网站A
的内容。 CSRF
仅仅是利用了http
携带cookie
的特性进行攻击的,但是无法得到被攻击站点的cookie
。这个和XSS
不同,XSS
一般是直接通过拿到Cookie
等信息进行攻击的
SQL 注入
概念
- 就是通过把
SQL
命令插入到Web表单
、页面请求的查询字符串
里面提交到服务器,最终达到欺骗服务器执行恶意的SQL
命令
原理
- 服务端在执行
sql
操作时,可能
会拼接前端传入的参数
,这就会将一些sql
注入的sql
拼接起来,导致一些预期之外的操作; - 就比如说登录的场景,前端输入用户名和密码,后端也许会
select * from user where username = '' AND password = ''
这样子拼接起来; - 而
sql
中会将#
以及--
之后的字符串当做注释处理,那么如果我们将password
写成or 1=1#
;那么服务端就有可能将所有的用户都查询出来;
防范方法
- 永远不要信任用户的输入,要对用户的输入进行校验,可以通过
正则表达式
,或限制长度
,对单#号
和双"-"
进行转换等 - 永远不要使用动态拼装
SQL
,可以使用参数化的SQL
或者直接使用存储过程进行数据查询存取 - 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
- 不要把机密信息明文存放,请加密或者
hash
掉密码和敏感的信息
点击劫持
概念
- 点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过
iframe
嵌套的方式嵌入自己的网页中,并将iframe
设置为透明,在页面中透出一个按钮诱导用户点击
防范方法
X-FRAME-OPTIONS
X-FRAME-OPTIONS
是一个 HTTP
响应头,在现代浏览器有一个很好的支持。这个 HTTP
响应头 就是为了防御用iframe
嵌套的点击劫持攻击。
该响应头有三个值可选,分别是
DENY
,表示页面不允许通过iframe
的方式展示SAMEORIGIN
,表示页面可以在相同域名下通过iframe
的方式展示ALLOW-FROM
,表示页面可以在指定来源的iframe
中展示
5.资源预加载 & 预连接
prefetch、preload
prefetch
、preload
都是告知浏览器提前加载文件(图片
、视频
、js
、css
等),但执行上是有区别的。
prefetch
:其利用浏览器空闲时间来下载用户在不久的将来可能访问的资源(比如下一个页面)。<link href="/js/xx.js" rel="prefetch">
;加载完成后,浏览器在使用资源时自动从prefetch cache
读取该资源;preload
: 可以指明哪些资源是在页面加载完成后
就需要的,这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。(MDN
就这么写的)<link href="/js/xxx.js" rel="preload" as="script">
需要as
指定资源类型,比如font
字体文件、style
样式表;preload
、prefetch
仅仅是加载资源,并不会执行;preload
、prefetch
均能设置、命中缓存;
preconnect
- 在我们下载资源时,要先建立链接,然后才能下载资源;建立链接时会涉及
DNS
寻址、TLS
握手、TCP
握手、重定向
等步骤; - 使用了这个参数后,浏览器就会提前做好
连接工作
,但是只保留10秒
,之后就会被关闭; - 不必要的预连接会延迟其他重要资源,因此要限制
preconnect
预连接的数量; 使用场景包括
:- 资源后缀是动态的,还不确定资源具体的地址时
- 页面上有媒体,但没那么快播放,又希望点击时尽快播放;
dns-prefetch 和 preconnect 的区别
dns-prefetch
可以预先解析DNS
,它只对跨域的DNS
查找有效,
dns-prefetch
和preconnect
的作用类似,都可以用来预链接
- 区别在于
preconnect
的浏览器兼容性稍微比dns-prefetch
差; - 且
dns-prefetch
只能预先进行dns
查询这一步; - 所以可以让
dns-prefetch
作为不兼容时的后备选择,两个都配置;也可以是只设置关键的preconnect
,其余用dns-prefetch
6.跨域
同源策略
- 只有
协议
、域名
、端口
都相同才算同源 - 同源策略是一种最基本的安全策略,他限制了客户端
js
代码的部分行为;
哪些行为受同源策略的限制
同源策略限制了客户端javascript
代码的部分行为
Cookie
、LocalStorage
和IndexDB
无法读取。(访问存储在浏览器中的数据,如localStorage
和IndexedDB
,是以源进行分割。每个源都拥有自己单独的存储空间)DOM
无法获得。AJAX
请求不能发送。
跨域的手段
- 跨域资源共享(
CORS
) - 通过
jsonp
跨域(只支持get
) postMessage
跨域nginx
代理跨域nodejs
中间件代理跨域
CORS 跨域
跨域资源共享(
CORS
)是一种机制,是W3C
标准。它允许浏览器向跨域服务器,发出XMLHttpRequest
或Fetch
请求。并且整个
CORS
通信过程都是浏览器自动完成的,不需要用户参与。
CORS 请求步骤
- 当我们发起跨域请求时,如果是复杂请求,浏览器会帮我们自动触发预检请求,也就是
OPTIONS
请求,用于确认目标资源是否支持跨域。如果是简单请求,则不会触发预检,直接发出正常请求。
- 浏览器会根据服务端响应的
header
(Access-Control-Allow-origin
) 进行判断,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。
简单请求
只要同时满足以下条件,就属于简单请求
:
使用下列方法之一:
GET
HEAD
POST
请求头只包含安全的信息
Accept
Accept-Language
Content-Language
Content-Type
Content-Type
的值仅限于以下三者之一:
text
/plain
multipart
/form-data
application
/x-www-``form-urlencoded
复杂请求
不符合以上条件的就是复杂请求。复杂请求的 CORS
请求,会在正式通信之前,增加一次 HTTP
查询请求,称为 预检请求
,该请求的方法是 Option
,通过该请求来查询服务端是否允许跨域请求。
Option
请求头中有一个Access-Control-Request-Method
字段,表示在实际发出请求时将用什么请求方法;
Access-Control-Request-Method 作用
- 这个请求头在发出
预检请求
时,让服务器
知道在发出实际请求时将使用哪种请求方式; - 此标头是必需的,因为预检请求始终是一个
OPTIONS
,与实际请求不是相同的方法。
JSONP 跨域
JSONP
的原理很简单,就是利用 <script>
标签没有跨域限制的漏洞。通过 <script>
标签指向一个需要访问的地址并提供一个回调函数来接收数据
<script src="http://xxxxxx&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
postMessage 跨域
window.postMessage()
可以安全的实现跨域通信;我们只需要拥有另一个窗口的引用
,就可以传递消息给另一个窗口;通过
onmessage
监听 传递过来的数据
可用于解决以下方面的问题:
- 页面和页面上打开的新窗口的数据传递(使用
window.open
打开的) - 页面与嵌套的
iframe
消息传递
跨域请求如何携带 cookie
例如我们想要在跨域请求中带上cookie
,需要满足以下条件:
Request
请求设置withCredentials
为true
samesite
值要设置为none
;- 服务器设置首部字段
Access-Control-Allow-Credentials
为true
- 服务器的
Access-Control-Allow-Origin
设置为*
或者对应的域名;
7.存储
cookie
Cookie
一开始设计的时候并不是做本地存储的,而是为了让HTTP
具有状态;比如将登录的标识存在cookie
里,请求时就会自动携带cookie
,这让无状态的HTTP
请求变得能够标识请求的状态(身份);
cookie 和 session 的区别?
两个都可以用来存私密的东西比如用户身份,但是cookie
数据保存在客户端,session
数据保存在服务器端。
cookie 要注意什么安全性?
cookie
的value
如果用于保存用户登录态,应该将该值加密http-only
属性设置了不能通过JS
访问Cookie
,减少XSS
攻击secure
属性设置只能在https
请求中携带SameSite
属性规定浏览器不能在跨域请求中携带Cookie
,减少CSRF
攻击
cookie 有效期
Cookie
的有效期可以通过Expires
和Max-Age
两个属性来设置。
-
Expires
即过期时间
-
Max-Age
用的是一段时间间隔,单位是秒,从浏览器收到报文开始计算 -
过期时间如果设置为
负数
与0
,则浏览器关闭直接被销毁
domain
domain
标识指定了哪些主机可以访问该Cookie
的域名。如果设置为.google.com
,则所有以google.com
结尾的域名都可以访问该Cookie
。注意第一个字符必须为.
cookies、sessionStorage、localStorage 几者的区别
cookie
数据始终在同源
的http
请求中携带(浏览器默认的SameSite
规定),而sessionStorage
和localStorage
不会自动把数据发给服务器,仅在本地保存cookie
数据大小不能超过4k
,其它两个比cookie
大的多,对单域名普遍支持2.5 ~ 10MB
之间,容量虽大但仍有上限,超出容量后会报错QuotaExceededError
localStorage
存储持久数据,浏览器关闭后数据不丢失,除非主动删除数据,sessionStorage
数据在当前浏览器窗口关闭后自动删除,cookie
设置的cookie
过期时间之前一直有效,即使窗口或浏览器关闭
IndexedDB
IndexDB
的使用,可以查看 MDN文档
IndexedDB
是一个事务型数据库系统
- 存储数据量不超过可用磁盘空间的
50%
,具体可看 IndexedDB 浏览器存储限制和清理标准 - 支持存储和检索用
键
索引的对象;可以存储结构化克隆算法支持的任何对象
。 - 使用
IndexedDB
执行的操作是异步执行的,以免阻塞应用程序。 - 支持
事务
- 遵从
同原协议
8.回流(重排)、重绘
概念
重绘
:当渲染树中的元素外观(如:颜色
、背景
、visibility
)发生改变,不影响布局时,产生重绘回流
:当渲染树中的元素的布局(如:尺寸
、位置
)发生改变时,重新生成布局,重新排列元素。回流
必将引起重绘
,而重绘
不一定会引起回流
回流(重排)的触发条件
JS
获取Layout
属性值(如:offsetLeft
、scrollTop
、getComputedStyle
等)- 页面初始渲染,这是开销最大的一次重排(从没有
DOM
元素开始渲染) - 添加/删除可见的
DOM
元素 - 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量等
重绘的触发条件
color
visibility
background
box-shadow
- 等等......
如何避免触发回流和重绘
- 避免频繁使用
style
,而是采用修改class
的方式。 - 将动画效果应用到
position
属性为absolute
或fixed
的元素上。 - 使用
display: none
做DOM离线处理
,减少回流重绘次数。因为在display
属性为none
的元素上进行的DOM
操作不会引发回流和重绘 - 对于
resize
、scroll
等进行防抖/节流
处理。 - 利用
CSS3
的transform
、opacity
、filter
这些属性可以实现合成
的效果,也就是GPU
加速。
硬件加速、渲染合成层
硬件加速
是指通过创建独立的复合图层,让GPU
来渲染这个图层,从而提高性能,
- 更改一个既不要布局也不要绘制的属性,渲染引擎将跳过
布局
和绘制
,只执行后续的合成操作,我们把这个过程叫做合成
。
- 一般触发硬件加速的
CSS
属性有transform
、opacity
、filter
9.进程和线程
概念
线程
是程序执行的最小单位,而进程
是操作系统分配资源的最小单位线程
被包含在进程之中,一个进程
中可以并发多个线程
,每条线程
并行执行不同的任务。
JS 为什么是单线程的
JavaScript
作为浏览器脚本语言,JavaScript
的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
浏览器是多进程的优点
- 默认新开 一个
tab
页面 新建 一个进程,所以单个tab
页面崩溃不会影响到整个浏览器。 - 第三方插件崩溃也不会影响到整个浏览器。
- 多进程可以充分利用现代
CPU
多核的优势。
进程间通讯的方式
信号
:信号是进程间通信唯一的异步通信机制,因为可以在任何时候发送信号给某一个进程匿名管道
:是一个内核缓冲区,进程以先进先出
的方式从缓冲区中存取数据,管道一端的进程在缓冲区的末尾写数据,管道另一端的进程在缓冲区的头部读数据;只能用于父子关系的进程
;命名管道
:正常的匿名管道
需要父子关系,而命名管道
提供了一个路径名与之相连,从而以文件的形式存在于文件系统中;就不受父子关系的限制;消息队列
:消息队列本质上是保存在内核当中的消息链表共享内存
:共享内存可以使得多个进程可以直接读写在同一块内存空间中,这是效率最高的进程间通信方式。Socket
:Socket
可以让跨网络的不同主机之间进行通讯,还可以在同主机上进程间通讯;
Web Worker
现代浏览器为JavaScript
创造的 多线程环境。可以新建并将部分任务分配到worker
线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制
相互通信。
一般使用 Web Worker
的场景是代码中有很多计算密集型或高延迟的任务,可以考虑分配给 Worker
线程。
限制
- 同源限制:分配给
worker
线程运行的脚本文件,必须与主线程的脚本文件同源。 - 文件限制:
worker
线程是运行在后台的,它所加载的脚本都必须是网络上的,不能读取本地文件 DOM
限制:worker
线程是不能直接操作dom
对象的,如果要处理dom
对象的话,应该是worker
线程将内容返回给主线程,然后主线程再去操作DOM
对象。- 脚本限制:
worker
线程不能执行alert()
和confirm
等方法,但可以使用XMLHttpRequest
发出ajax
请求。 - 通信限制:
worker
线程和主线程
不在同一个上下文环境
,它们不能直接通信,可以通过postMessage
来进行通信;
Service Worker
Service Worker
是在Web worker
的基础上实现了离线缓存
、消息推送
和网络代理
等功能。- 借助
Service worker
实现的离线缓存就称为Service Worker Cache
。 Service workers
本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器,且只能由HTTPS
承载
生命周期
Service Worker
的生命周期包括install
、active
、working
三个阶段。- 一旦
Service Worker
被install
,它将始终存在,只会在active
与working
之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。
10.前端路由
hash 模式
- 使用
window.location.hash
属性及窗口的onhashchange
事件,可以实现监听浏览器地址hash
值变化,执行相应的js
切换网页。
hash 模式的特点
hash
指的是地址中#号
以及后面的字符,hash
也称作锚点,本身是用来做页面跳转定位的。- 失去原生页面
锚点
定位能力 - 可以通过
location.hash
来获取和设置hash
值,值变化会直接反应到浏览器地址栏,但是不会重新加载页面;
触发 hashchange 事件的几种情况
- 浏览器地址栏
hash
值的变化(包括浏览器的前进、后退导致的),会触发onhashchange
事件 html
中<a>
标签的属性href
可以设置为页面的元素ID
,点击后自动跳转并设置hash
值
history 模式
window.history
属性指向History
对象,它表示当前窗口的浏览历史。当发生改变时,只会改变页面的路径,不会刷新页面。History
对象保存了当前窗口访问过的所有页面网址。通过history.length
可以得出当前窗口一共访问过几个网址。- 由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。
- 浏览器工具栏的
前进
和后退
按钮,其实就是对History
对象进行操作。
history 的 API
History.back()
:移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。History.forward()
:移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。History.go()
:接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0,相当于刷新当前页面。History.pushState()
:该方法用于在历史中添加一条记录。pushState()
方法不会触发页面刷新,只是导致History
对象发生变化,地址栏会有变化。History.replaceState()
:该方法用来修改 History 对象的当前记录,用法与pushState()
方法一样。popstate()
:调用History.back()
、History.forward()
、History.go()
方法时才会触发。
如何监听 replaceState 和 pushState
监听这两个时间,我们需要对 replaceState
和 pushState
,去创建新的全局Event
事件。然后 window.addEventListener
监听我们加的 Event
即可
简单的代码已经贴在下面,详细的可以看一文摸清前端监控自研实践(二)行为监控:【路由跳转】
// 派发出新的 Event
const wr = (type: keyof History) => {
const orig = history[type];
return function (this: unknown) {
const rv = orig.apply(this, arguments);
const e = new Event(type);
window.dispatchEvent(e);
return rv;
};
};
// 添加 pushState replaceState 事件
export const wrHistory = (): void => {
history.pushState = wr('pushState');
history.replaceState = wr('replaceState');
};
11.跨页签通信 & 多tab通信
- 设置同域下共享的
localStorage
与绑定监听window.addEventListener('storage')
- 重复写入相同的值无法触发
- 会受到浏览器隐身模式等的限制
WebSocket
配合服务端Service Worker
IndexedDB
浏览器数据库实现,共享存储+轮询
的方式;- 通过父页面
window.open()
和子页面postMessage
12.事件机制(事件模型)
事件触发三阶段(JS事件流)
window
往事件触发处传播,遇到注册的捕获事件会触发- 传播到事件触发处时触发注册的事件
- 从事件触发处往
window
传播,遇到注册的冒泡事件会触发 - 总之:
事件捕获阶段
-->处于目标阶段
-->事件冒泡阶段
(先捕获事件再冒泡事件
)
事件冒泡、事件捕获
冒泡事件
:是指子元素向父元素传递的过程(从里到外)捕获事件
:是指父元素向子元素传递的过程(从外到里)
注册、绑定事件的方式
- 在
dom
元素中直接绑定,<div class="an" onclick="aa()">aaaa</div>
js
中绑定document.getElementById("demo").οnclick=function(){}
- 添加监听事件
document.addEventListener('name',()=>{})
如何阻止事件
- 阻止
冒泡
、捕获
事件:e.stopPropagation();
或e.stopImmediatePropagation()
。- 但是前者只会阻止冒泡和捕获
- 而后者除此之外还会阻止该元素的其它事件发生;(比如元素绑定了多个捕获事件)。
- 阻止
默认
事件:e.preventDefault()
;(比如a
标签的跳转事件)
事件代理(事件委托)
事件委托的原理就是利用了浏览器事件冒泡的机制
;由于事件冒泡过程中,会由子节点冒泡到父节点,并且可以在父节点的事件里获取到
target
(实际触发的元素),这样子就可以在父节点上处理事件;
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
事件代理的方式相较于直接给目标注册事件来说,有以下优点
:
- 节省内存
- 不需要给子节点注销事件
- 事件代理时获取触发的子元素可以采用获取
target
来得知
target 和 currentTarget 的区别
target
是指获取事件的目标(实际触发的元素)。currentTarget
是指其事件处理程序当前正在处理事件的那个元素(正在冒泡、捕获的元素)
当事件处理程序直接绑定在目标元素上(绑定在父元素,点击父元素),此时
e.target===e.currentTarget
,e.target ===this