「2022」寒冬下我的面试知识点复盘【浏览器原理&安全】篇

13,149 阅读34分钟

前言

笔者今年2022寒冬下成功跳槽了阿里,这篇文章就是将自己面试的一些准备知识总结分享出来~

如果这篇文章对你有用,请一键三连(点赞评论+收藏)让更多的同学看到

如果需要转载,请评论区留言,未经允许请不要私自转载

防杠声明

这篇文章不是纯堆砌面试题,而是以知识总结为主,主观观点和主观总结居多里面总结的知识点在我这次的面试中也不全都有用到~如果有写错的地方欢迎评论区提出,如果只是要那请右上角X掉慢走;

传送门

这个专栏预计要做以下这些内容,可以根据自己的需要跳转查看

面经「2022面经」:2年前端拿下字节阿里offer总结

专栏2022寒冬下我的面试知识点复盘:

「2022」寒冬下我的面试知识点复盘【浏览器原理】篇

「2022」寒冬下我的面试知识点复盘【计算机网络】篇

「2022」寒冬下我的面试知识点复盘【JS】篇(加紧编写中)

「2022」寒冬下我的面试知识点复盘【CSS】篇

「2022」寒冬下我的面试知识点复盘【Vue3、Vue2、Vite】篇

「2022」寒冬下我的面试知识点复盘【工程化】篇(加紧编写中)

「2022」寒冬下我的面试知识点复盘【Nodejs】篇(加紧编写中)

「2022」寒冬下我的面试知识点复盘【TypeScript】篇(加紧编写中)

本文标题思维导图

浏览器原理.png

浏览器原理 篇

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-MatchIf-Modified-Since的请求
  • 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200
  • 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的 last-modified 和文件并返回200

强缓存

缓存标识

强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-Control 。强缓存表示在缓存期间不需要发送请求

  • ExpiresHTTP/1.0 的产物。值代表的是服务端的时间,并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
  • Cache-Control 出现于 HTTP/1.1,优先级高于 Expires 。该属性值表示资源会在 多少秒后过期,需要再次请求。
字段协议版本缓存类型响应头请求头
ExpiresHTTP1.0强缓存X
Cache-ControlHTTP1.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 指令后,公共缓存服务器将直接忽略 Expiresmax-age指令的值。
  • public 指令表示该资源可以被任何节点缓存(包括客户端和代理服务器)
  • private 指令表示该资源只提供给客户端缓存,代理服务器不会进行缓存。同时当设置了 private 指令后 s-maxage 指令将被忽略。
no-cache、no-store 的区别
  • no-cacheno-store 这两个指令在请求响应中都可以使用
  • no-store 是真正的不进行任何缓存,告知服务器和缓存服务器,我请求、响应的内容里有机密信息;
  • no-cache请求头中被使用时,表示强制使用协商缓存
  • no-cache响应头中被返回时,表示缓存服务器不能对资源进行缓存,客户端可以缓存资源,但每次使用缓存资源前都必须先向服务器确认其有效性

协商缓存

缓存过程

如果首次请求时没有Cache-ControlExpires;或者Cache-Control 的属性设置为 no-cache 时,又或者如果缓存过期了。就需要发起请求验证资源是否有更新。向服务器发送请求时,服务器会根据这个请求的请求头里的If-Modified-SinceIf-None-Match 来判断是否命中协商缓存,如果命中,则返回304状态码并且更新浏览器缓存有效期

缓存标识
  • Last-Modified 表示本地文件最后修改时间,发送请求时,会将当前的Last-Modified值作为If-Modified-Since 这个字段的内容,放在请求头中发送给服务器,去询问服务器在该时间后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。
  • ETag 类似于文件指纹,请求时会将当前 ETag 作为 If-None-Match 这个字段的内容,并放到请求头中发送给服务器,服务器接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行比对:,有变动的话就将新的资源发送回来。否则返回304状态码
字段Header类型协议版本缓存类型
Last-ModifiedResponse(响应头)HTTP1.0协商缓存
If-Modified-SinceRequest(请求头)HTTP1.0协商缓存
ETagResponse(响应头)HTTP1.1协商缓存
If-None-MatchRequest(请求头)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 和最新内容

启发式缓存

在资源请求的响应头中没有出现Expirescache-control:max-age 字段值, 并且没有限制no-store,并且设置了Last-Modified, 那么浏览器默认会采用一个启发式的强缓存算法

通常会根据响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

2.浏览器渲染机制

详细渲染过程

  • 构建DOM树:浏览器从上到下解析 HTML 文档生成DOM节点树;
  • 构建CSSOM树:浏览器解析遇到样式时,会进行异步下载,下载完成后构建 CSSOM树;
  • 值得一提的是,浏览器解析过程中遇到 图片时,会进行异步下载;当遇到不带 asyncdeferscript 时,会阻止解析HTML并进行下载和执行;
  • 并且CSSDOM渲染,JSDOM解析之间是有阻塞关系的;
  • 构建渲染树:根据DOM节点树和CSSOM树构建渲染树(Render);
  • 布局(Layout):根据渲染树将DOM节点树每一个节点布局在屏幕上的正确位置;
  • 绘制(Paint):绘制所有节点,为每一个节点适用对应的样式,绘制到屏幕上;
    • 绘制的过程中还有很多细节,包括说:
    • 构建图层树:需要对布局树进行分层,生成图层树(比如说Z轴排序)
    • 生成绘制列表:将图层的绘制拆分为很多的绘制指令,并按顺序组成绘制列表,并提交到合成线程中;
    • 光栅化(栅格化)生成位图:合成线程图层划分成图块,并在光栅化线程池中将图块转换成位图
      • 同时因为用户只能看到视口的这一部分,所以合成线程就会按照视口附近的图块来优先生成位图
    • 显示:一旦所有的图块都被光栅化,合成线程就会提交绘图指令给浏览器进程;浏览器进程生成页面并显示到屏幕上;

3.浏览器资源解析机制

整体流程

  • 浏览器开始解析HTML,此时document.readystateloading
  • 解析中遇到不带asyncdeferscript脚本时,需要等待 script脚本 下载完成并执行后,才会继续解析 HTML
  • 当文档完成解析,document.readyState变成 interactive,触发 DOMContentLoaded事件
  • 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readyState变为 completewindow 触发 load 事件

浏览器解析 不同资源 时的行为

  • 浏览器解析遇到 CSS样式资源 时,CSS会异步下载,不会阻塞浏览器构建DOM树,但是会阻塞渲染,在构建渲染树时,会等css下载解析完毕后才进行(防止css规则不断变化)
  • 浏览器解析遇到 JS脚本资源 时,需要等待JS脚本下载完成并执行后才会继续解析HTML;但是当脚本加上deferasync时又不一样,defer是延迟执行,async是异步执行;
  • CSS加载会阻塞后面的的JS语句的执行,因为HTML5标准中有一项规定,浏览器在执行Script脚本前,必须保证当前的的外联CSS已经解析完成,因为JS可能会去获取或者变更DOMCSS样式,如果此时外联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 的区别
  • asyncdefer 都仅对外部脚本有效
  • async 标志的脚本文件一旦加载完成就立即执行;而 defer 标志的脚本文件会在 HTML解析完成且DOM构建完毕后再执行;(也就是说defer是延迟执行,async是异步执行)
  • 如果有多个js脚本async标记的脚本哪个先下载结束,就先执行那个脚本。而defer标记则会按照js脚本书写顺序执行。
  • 如果同时使用asyncdefer属性,defer不起作用,浏览器行为由async属性决定。
  • DOMContentLoaded 事件会等待 defer 的脚本执行完后才触发;

DOM树 和 CSSOM树 的构建顺序关系

  • 实际上,构建 DOM树 和 构建 CSSOM树 是并行的;这也正解释了为什么CSS加载不会阻塞DOM解析,但是因为渲染树需要依赖DOM树CSSOM树,所以会阻塞DOM渲染;

CSS 解析规则

  • 浏览器解析 CSS 选择器的方式是从上到下、从右到左,因为从右往左只需要最右边的一个不匹配,就可以直接舍弃,避免了许多无效匹配。
  • 一句话总结: 浏览器的这种查找规则是为了 尽早过滤掉一些无关的样式规则和元素。

Load 和 DOMContentLoaded 区别

推荐阅读:DOMContentLoaded event MDN

  • Load 事件触发代表页面中的 DOMCSSJS,图片已经全部加载完毕。
  • 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标签给删除;
  • 利用 HttpOnlycookie 设置 httponly 后,会禁止 javascript 脚本来访问 cookie,这样,XSS攻击之后也无法获取用户的cookie
  • 其次就是使用 CSPCSP 也就是浏览器内容安全策略;只允许加载指定域的脚本及样式;

CSRF

基本概念
  • CSRF(跨站请求伪造) 就是黑客诱导用户跳转恶意网站,然乎利用用户的登录态发起恶意请求;
  • 原理就是http请求会自动携带 Cookie,而且是 HTTP 目标请求域名Cookie
防范措施
  • SameSite Cookies: 该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击;它有Strict(浏览器将只发送相同站点(完全一致)请求的 Cookie)、Lax(第三方get方法可以携带Cookie) 和 None (任何情况下都会发送 Cookie)三个值。
  • OriginReferer:验证Referer是否是从第三方网站发出来的,阻止第三方网站请求接口,但是这两者可以通过ajax自定义请求头的方式被伪造;
  • CSRF Token:客户端向服务端请求token,然后在所有的请求中带上;
Chrome80 版本的 CSRF 例子

讲到 CSRFcookie,不得不提一下 Chrome 80 版本的的一个默认设置;Chrome 80 版本将 SameSite 的值设置为了 Lax;这导致之前公司有一些业务产生了跨域;

XSS 和 CSRF 的区别

  • 两者的原理区别:CSRF 是利用 网站A 本身的漏洞,去请求 网站Aapi。而 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

prefetchpreload都是告知浏览器提前加载文件(图片视频jscss等),但执行上是有区别的。

  • prefetch :其利用浏览器空闲时间来下载用户在不久的将来可能访问的资源(比如下一个页面)。<link href="/js/xx.js" rel="prefetch">;加载完成后,浏览器在使用资源时自动从prefetch cache读取该资源;
  • preload : 可以指明哪些资源是在页面加载完成后就需要的,这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。(MDN就这么写的) <link href="/js/xxx.js" rel="preload" as="script"> 需要 as 指定资源类型,比如font字体文件、style样式表;
  • preloadprefetch仅仅是加载资源,并不会执行;
  • preloadprefetch均能设置、命中缓存;

preconnect

  • 在我们下载资源时,要先建立链接,然后才能下载资源;建立链接时会涉及 DNS 寻址、TLS 握手、TCP 握手、重定向等步骤;
  • 使用了这个参数后,浏览器就会提前做好连接工作,但是只保留10秒,之后就会被关闭;
  • 不必要的预连接会延迟其他重要资源,因此要限制 preconnect 预连接的数量;
  • 使用场景包括
    • 资源后缀是动态的,还不确定资源具体的地址时
    • 页面上有媒体,但没那么快播放,又希望点击时尽快播放;

dns-prefetch 和 preconnect 的区别

dns-prefetch可以预先解析DNS,它只对跨域的DNS查找有效,

  • dns-prefetchpreconnect的作用类似,都可以用来预链接
  • 区别在于preconnect的浏览器兼容性稍微比dns-prefetch差;
  • dns-prefetch只能预先进行dns查询这一步;
  • 所以可以让dns-prefetch作为不兼容时的后备选择,两个都配置;也可以是只设置关键的preconnect,其余用dns-prefetch

6.跨域

同源策略

  • 只有协议域名端口都相同才算同源
  • 同源策略是一种最基本的安全策略,他限制了客户端js代码的部分行为;

哪些行为受同源策略的限制

同源策略限制了客户端javascript代码的部分行为

  • CookieLocalStorageIndexDB 无法读取。(访问存储在浏览器中的数据,如 localStorage IndexedDB,是以源进行分割。每个源都拥有自己单独的存储空间)
  • DOM 无法获得。
  • AJAX 请求不能发送。

跨域的手段

  • 跨域资源共享(CORS
  • 通过jsonp跨域(只支持get
  • postMessage跨域
  • nginx代理跨域
  • nodejs中间件代理跨域

CORS 跨域

跨域资源共享(CORS)是一种机制,是W3C标准。它允许浏览器向跨域服务器,发出XMLHttpRequestFetch请求。

并且整个CORS通信过程都是浏览器自动完成的,不需要用户参与。

CORS 请求步骤
  • 当我们发起跨域请求时,如果是复杂请求,浏览器会帮我们自动触发预检请求,也就是 OPTIONS 请求,用于确认目标资源是否支持跨域。如果是简单请求,则不会触发预检,直接发出正常请求。
  • 浏览器会根据服务端响应的 headerAccess-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 跨域

MDN文档

window.postMessage() 可以安全的实现跨域通信;我们只需要拥有另一个窗口的引用,就可以传递消息给另一个窗口;

通过onmessage监听 传递过来的数据

可用于解决以下方面的问题:

  • 页面和页面上打开的新窗口的数据传递(使用window.open打开的)
  • 页面与嵌套的 iframe 消息传递

跨域请求如何携带 cookie

例如我们想要在跨域请求中带上cookie,需要满足以下条件:

  • Request 请求设置withCredentialstrue
  • samesite值要设置为none
  • 服务器设置首部字段Access-Control-Allow-Credentialstrue
  • 服务器的Access-Control-Allow-Origin 设置为* 或者对应的域名;

7.存储

cookie

Cookie 一开始设计的时候并不是做本地存储的,而是为了让HTTP具有状态;比如将登录的标识存在cookie里,请求时就会自动携带cookie,这让无状态的HTTP请求变得能够标识请求的状态(身份);

cookie 和 session 的区别?

两个都可以用来存私密的东西比如用户身份,但是cookie数据保存在客户端,session数据保存在服务器端。

cookie 要注意什么安全性?
  • cookievalue如果用于保存用户登录态,应该将该值加密
  • http-only 属性设置了不能通过 JS 访问 Cookie,减少 XSS 攻击
  • secure 属性设置只能在https请求中携带
  • SameSite 属性规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击
cookie 有效期

Cookie的有效期可以通过ExpiresMax-Age两个属性来设置。

  • Expires过期时间

  • Max-Age用的是一段时间间隔,单位是秒,从浏览器收到报文开始计算

  • 过期时间如果设置为负数0,则浏览器关闭直接被销毁

domain

domain标识指定了哪些主机可以访问该Cookie的域名。如果设置为.google.com,则所有以google.com结尾的域名都可以访问该Cookie。注意第一个字符必须为.

cookies、sessionStorage、localStorage 几者的区别

  • cookie数据始终在同源http请求中携带(浏览器默认的SameSite规定),而sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存
  • cookie数据大小不能超过4k,其它两个比cookie大的多,对单域名普遍支持 2.5 ~ 10MB 之间,容量虽大但仍有上限,超出容量后会报错 QuotaExceededError
  • localStorage 存储持久数据,浏览器关闭后数据不丢失,除非主动删除数据,sessionStorage 数据在当前浏览器窗口关闭后自动删除,cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

IndexedDB

IndexDB的使用,可以查看 MDN文档

  • IndexedDB 是一个事务型数据库系统
  • 存储数据量不超过可用磁盘空间的 50%,具体可看 IndexedDB 浏览器存储限制和清理标准
  • 支持存储和检索用索引的对象;可以存储结构化克隆算法支持的任何对象
  • 使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。
  • 支持事务
  • 遵从同原协议

8.回流(重排)、重绘

概念

  • 重绘:当渲染树中的元素外观(如:颜色背景visibility)发生改变,不影响布局时,产生重绘
  • 回流:当渲染树中的元素的布局(如:尺寸位置)发生改变时,重新生成布局,重新排列元素。
  • 回流必将引起重绘,而重绘不一定会引起回流

回流(重排)的触发条件

  • JS获取Layout属性值(如:offsetLeftscrollTopgetComputedStyle等)
  • 页面初始渲染,这是开销最大的一次重排(从没有DOM元素开始渲染)
  • 添加/删除可见的DOM元素
  • 改变元素位置
  • 改变元素尺寸,比如边距、填充、边框、宽度和高度等
  • 改变元素内容,比如文字数量等

重绘的触发条件

  • color
  • visibility
  • background
  • box-shadow
  • 等等......

如何避免触发回流和重绘

  • 避免频繁使用 style,而是采用修改class的方式。
  • 将动画效果应用到position属性为absolutefixed的元素上。
  • 使用 display: noneDOM离线处理,减少回流重绘次数。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
  • 对于 resizescroll等进行防抖/节流处理。
  • 利用 CSS3transformopacityfilter这些属性可以实现合成的效果,也就是GPU加速。

硬件加速、渲染合成层

  • 硬件加速是指通过创建独立的复合图层,让GPU来渲染这个图层,从而提高性能,
  • 更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局绘制,只执行后续的合成操作,我们把这个过程叫做合成
  • 一般触发硬件加速的CSS属性有transformopacityfilter

9.进程和线程

概念

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
  • 线程被包含在进程之中,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

JS 为什么是单线程的

  • JavaScript作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

浏览器是多进程的优点

  • 默认新开 一个 tab 页面 新建 一个进程,所以单个 tab 页面崩溃不会影响到整个浏览器。
  • 第三方插件崩溃也不会影响到整个浏览器。
  • 多进程可以充分利用现代 CPU 多核的优势。

进程间通讯的方式

  • 信号:信号是进程间通信唯一的异步通信机制,因为可以在任何时候发送信号给某一个进程
  • 匿名管道:是一个内核缓冲区,进程以先进先出的方式从缓冲区中存取数据,管道一端的进程在缓冲区的末尾写数据,管道另一端的进程在缓冲区的头部读数据;只能用于父子关系的进程
  • 命名管道:正常的匿名管道需要父子关系,而命名管道提供了一个路径名与之相连,从而以文件的形式存在于文件系统中;就不受父子关系的限制;
  • 消息队列:消息队列本质上是保存在内核当中的消息链表
  • 共享内存:共享内存可以使得多个进程可以直接读写在同一块内存空间中,这是效率最高的进程间通信方式。
  • SocketSocket可以让跨网络的不同主机之间进行通讯,还可以在同主机上进程间通讯;

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 的生命周期包括 installactiveworking 三个阶段。
  • 一旦 Service Workerinstall,它将始终存在,只会在 activeworking 之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。

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

监听这两个时间,我们需要对 replaceStatepushState,去创建新的全局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.currentTargete.target ===this