跳到主要内容

浏览器相关

问题:http 返回码有哪些?

💊 概述

2XX,表示 Success 成功 的状态码。

3XX,表示 Redirection 重定向 的状态码。

4XX,表示 Client Error 客户端错误 的状态码。

5XX,表示 Server Error 服务器错误 的状态码。

200系列有三个,服务器:发全部资源(200),不发资源(204),发范围资源(206)

300系列有五个,服务器的资源:永久重定向(301),临时重定向(302、307),post切换为get后重定向(303),访问不符合条件(304)

400系列有五个,服务器:无法理解(400),权限验证(401),不允许访问(403),没有该资源(404),禁止该方式访问(405)

500系列有三个,服务器:内部故障(500),上游故障(502),正忙(503)

💊 详述

200 OK:服务器端请求已 正常处理

204 No Content:一般在 只从客户端发送信息服务器不需要发送新信息内容 的情况下使用。

206 Partial Content:客户端进行 范围请求,服务端返回 小范围内的资源

301 Moved Permanently:资源 永久重定向,客户端访问新的地址。

302 Found:资源 临时重定向,客户端暂时访问新地址。

303 See Other:服务器让客户端把 post 请求切换为 get 请求重定向访问新的地址。

304 Not Modified:和重定向关系不大,协商缓存中,资源未更新,继续使用本地缓存

307 Temporary Redirect:资源 临时重定向,客户端暂时访问新地址。和 302 类似,有 get 和 post 的访问差别。

400 Bad Request:服务端 无法理解请求报文,可能是格式错误。

401 Unauthorized:客户端需要对访问进行 权限认证

403 Forbidden:资源不允许访问

404 Not Found:没有找到资源

405 Method Not Allowed:服务器 禁止使用该访问方式

500 Internal Server Error:服务完成执行请求时 内部发生了故障

502 Bad Gateway:“中间商” 服务器(代理服务器、网关服务器),无法访问上游服务器

503 Service Unavailable:服务器无法处理请求,正忙(超负荷、停机维护)。

问题:同源策略

💊 同源策略

同源请求、跨域请求。

同源策略(Same origin policy),它是由Netscape网景公司提出的一个著名的安全策略,浏览器都遵守该策略。

  • 同源: 协议、域名、端口号必须完全相同

  • 跨域: 违背同源策略就是跨域,浏览器会 丢弃 非同源的响应数据。

通过 window.originlocation.origin 得到当前源。

http://moxy.com/index.html
http://moxy.com/server.php
//同源

http://a.wang.com
http://wang.com
//不同源,域名必须一模一样

也就是说,服务端有返回数据,浏览器也接收到了响应数据,但浏览器发现我们请求的是一个非同源的数据,浏览器再将其响应报文丢弃掉。

同源策略又分为以下两种:

  1. DOM 同源策略:禁止对不同源的页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。无需再浏览器收到请求后拦截非同源数据,通过 XHR 发送的不同源请求可直接被拦截。

💊 跨域

当用户在 A 域中访问服务器获取资源,服务器会正常的返回资源。而当用户试图在其他域的网站去访问 A 域的资源,出于安全原因,服务器就会拒绝这种访问方式。

  • 浏览器发送请求时,会把本地域放在请求头中发送给服务器,以便服务器对齐对其进行验证。

可以跨域使用CSSJS和图片

  • 同源策略限制的是数据访问,我们引用 CSSJS 和图片等资源不限制。

同源策略会让三种行为受限:

  • Cookie、LocalStorage 和 IndexDB 访问受限;
  • 无法操作跨域 DOM(常见于 iframe);
  • Javascript 发起的 XHR 和 Fetch 请求受限;

💊 为什么跨域限制:

如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:

  1. 做一个假网站,里面用 iframe 嵌套一个银行网站 http://mybank.com
  2. 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
  3. 这时如果用户输入账号密码,我们的主网站可以跨域访问到 http://mybank.com 的 dom 节点,就可以拿到用户的账户密码了。

如果没有 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

  1. 用户登录了自己的银行页面 http://mybank.comhttp://mybank.com 向用户的 cookie 中添加用户标识。
  2. 用户浏览了恶意页面 http://evil.com,执行了页面中的恶意 AJAX 请求代码。
  3. http://evil.comhttp://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去。
  4. 银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
  5. 而且由于 Ajax 在后台执行,用户无法感知这一过程。

https://juejin.cn/post/6844903681683357710

💊 解决跨域问题

解决跨域问题有三种方式:民间的 JSONP + 官方的 CORS(跨域资源共享)+ 不安全的 iframe。

问题:什么是 JSONP

JSONP是 JSON with Padding 的略称,JSONP 是程序员提出的一种跨域解决方案。

  • 在网页有一些标签天生具有跨域能力,比如:imglinkiframescript。发出请求不受跨域限制。

JSONP 通过创建 script 标签,利用 src 属性进行跨域,来发送请求。仅支持 get 请求

缺点:

  1. 代码结构改变,接受到响应数据后的处理代码,要全部放在回调函数中。
  2. 控制反转。调用回调函数,处理返回数据和后续逻辑的契机不在浏览器的js代码中,而是交给了服务器。
  3. 后端协商,需要和后端进行协商,确保服务器会正确调用回调函数,并携带正确的格式。JSONP 不易进行错误检查。

问题:什么是 CORS

CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS 是官方的跨域解决方案。CORS 需要浏览器和服务器同时支持,支持 get 和 post 请求。

  • 允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

  • 服务器的响应请求中设置:"Access-Control-Allow-Origin" = *

    实现 CORS 通信需要浏览器和服务器都支持。

CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息:

  • 通过设置一个响应头来告诉浏览器,该请求允许跨域,而浏览器会对头部信息匹配,如果匹配成功则正常返回信息,浏览器收到该响应以后就会对响应放行,而不会拦截。

注意:CORS 不支持IE8/9如果要在IE8/9使用CORS跨域需要使用XDomainRequest对象来支持CORS

浏览器 CORS

浏览器会限制 从脚本内发起 的跨域 HTTP 请求。 例如 XHR 和 Fetch 就遵循同源策略。这意味着使用 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源。

Web 程序发出跨域请求后,浏览器会 自动 向我们的 HTTP header 添加一个额外的请求头字段:OriginOrigin 标记了请求的站点来源:

GET https://api.website.com/users HTTP/1/1
Origin: https://www.mywebsite.com // <- 浏览器自己加的

服务器返回的 response 也会添加一些响应头字段,这些字段将 显式表明 此服务器是否允许这个跨域请求。

客户端 CORS

服务器开发人员,通过验证 Origin 是否允许跨域访问,然后在 HTTP 响应中添加额外的响应头字段 Access-Control-* 来表明是否允许跨域请求。

all.get("/testAJAX" , function (req , res) {
//通过res设置响应头,允许跨域请求
//res.set("Access-Control-Allow-Origin","http://127.0.0.1:3000");
res.set("Access-Control-Allow-Origin","*");
res.send("testAJAX 返回的响应");
});

问题:什么是 iframe

<iframe> 内容迁入技术。是框架的一种形式,一般用来包含别的页面,例如我们可以在我们自己的网站页面加载别人网站或者本站其他页面的内容。

iframe 的核心属性就是 src,表示要引用的页面一个请求,src 实际就是对跨域服务器的一个请求。

iframe 通常要解决不同页面(不同 iframe)的通信问题:

  • document.domain 作用是获取 / 设置当前文档的原始域。

    同源策略会判断两个文档的原始域是否相同来判断是否跨域。这意味着只要把这个值设置成一样就可以解决跨域问题。

  • window.postMessage 方法可以安全地实现跨源通信,写明目标窗口的协议、主机地址或端口就可以发信息给它。

    为了安全,收到信息后要检测下 event.origin 判断是否要收信息的窗口发过来的。

iframe 的缺点:

  1. 页面多。会产生很多页面,不容易管理,多个页面如果有各自的滚动条,影响用户体验。
  2. 请求多。iframe 框架页面会增加服务器的 http 请求。
  3. 手机兼容性。很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
  4. SEO 不友好。无法被一些搜索引擎索引到,搜索引擎爬虫不能处理 iframe 中的内容,不利于搜索引擎优化。
  5. 不安全。容易发生 xss 攻击,钓鱼等攻击方式。比如非法网站,iframe 内嵌一个合法网站,用户输入了账号密码登录后,非法网站就获取了相关数据。

问题:事件流

🌈 三个阶段

DOM 2 级事件规定,事件的事件流三个阶段:捕获 => 目标 => 冒泡。从 Window 向下到 target,再返回。

  • addEventListener()removeEventListener() 三个参数。
    • 事件名、回调函数、处理阶段:true 捕获,false 冒泡(默认)。
    • 如果要移除事件,对应的三个参数必须完全相同。
  • 停止冒泡:event.stopPropagation()

在 DOM 事件流中, target 在捕获阶段不会接受到事件,所以对 target 绑定 捕获事件,是无效的,默认修改为冒泡。这就是为什么 false 冒泡是默认值的原因。

<div id="container">
<div id="box1">
<div id="box2">点击我</div>
</div>
</div>

var container = document.getElementById("container");
var box1 = document.getElementById("box1");
var box2 = document.getElementById("box2");

container.addEventListener("click",() => console.log("container capturing"), true);
box1.addEventListener("click",() => console.log("target capturing"));
box1.addEventListener("click",() => console.log("target bubbling"), true);
box2.addEventListener("click",() => console.log("box2 bubbling"), true);
box1.addEventListener("click",() => console.log("box2 capturing"));
container.addEventListener("click",function() {console.log("container bubbling")});

// "container capturing"
// "target bubbling" 没有先执行捕获监听,而是按照绑定顺序先执行冒泡
// "target capturing" target 无法绑定捕获,默认是冒泡的。
// "box2 bubbling"
// "box2 capturing"
// "container bubbling"

点击 box2 触发点击事件,target 为 box2。随后会依次触发:捕获(container、box1)、目标(box2)、冒泡(box2、container)。目标 target box2 元素无法绑定捕获阶段,所以默认改为冒泡。

🌈 DOM 0 和 DOM 2 的执行优先级

  • DOM 0 级事件和 DOM 2 级事件可以共存。
  • DOM 0 级只有冒泡。所以先执行 DOM 2 级的捕获阶段。在冒泡阶段,DOM 0 级和 DOM 2 级按绑定顺序执行,无优先级。

🌈 事件委托

  • e.target触发 事件的元素。从该对象中,可以获得触发事件的元素名、id、属性、子节点等要素。
  • e.currentTarget绑定 事件的元素。
<ul id="myLink">
<li id="1">aaa</li>
<li id="2">bbb</li>
<li id="3">ccc</li>
</ul>

ul.addEventListener('click', (e) => {
if(e.target.tagName.toLowerCase() === 'li'){
fn(); // 执行某个函数
}
})

问题:DOM 0、DOM 2 事件模型的区别

DOM 0 级:绑定事件使用属性形式:on + 事件名。如 onclick、onmousemove、onmouseover。

  • 如果对同一个元素绑定相同的事件,后边的会覆盖掉前边的。并且 DOM0 级事件只 触发冒泡,不能触发事件捕获阶段 。

DOM 2 级:绑定事件使用函数形式: addEeventListener();删除事件使用 removeEeventListener()

  • DOM 2 级允许对同一个元素绑定多个相同的事件,后面的 不会覆盖 前面的。 使用 addEventListener 方法为一个元素绑定事件时,它 默认冒泡阶段触发。如果第三个参数为 true,在捕获阶段也能触发。

注意:使用 DOM 0 和 DOM 2 同时添加事件模型,两者是不冲突的。

问题:鼠标点击事件

点击鼠标左键,依次触发:MouseDown、MouseUp、Click

双击鼠标左键,依次触发:MouseDown、MouseUp、Click、Dbclick、MouseUp

单机鼠标右键,依次触发:mousedown、contextmenu、click

click:单击鼠标左键时发生,如果右键也按下则不会发生。
- 当用户的焦点在按钮上并按了 Enter 键时,同样会触发这个事件
dblclick:双击鼠标左键时发生,如果右键也按下则不会发生
mousedown:单击任意一个鼠标按钮时发生
mouseup:松开任意一个鼠标按钮时发生
mousemove:鼠标在某个元素上时持续发生
contextmenu:鼠标按下右键触发,并出现上下文菜单

mouseout:鼠标移动到盒子外触发(子元素算边界、冒泡)
mouseover:鼠标移动到盒子内触发(子元素算边界、冒泡)
mouseleave:鼠标移动到盒子外触发(不冒泡)
mouseenter:鼠标移动到盒子内触发(不冒泡)

// event.button:获取鼠标按下的键 click、mousedown、mouseup事件类型
左键:0
中键:1
右键:2

假设:container 盒子内嵌套 box 盒子,在 container 上绑定 mouseover、mouseout、mouseleave。

  • 当鼠标从 container 移入内部的 box 时,会触发 mouseover 和 mouseout,但不会触发 mouseleave。
  • mouseleave 只有在真正离开盒子才会触发。
  • mouseover,代表鼠标来自哪个元素(包括子元素)。
  • mouseout:代表鼠标去往哪个元素(包括子元素)。

问题:判断两个 dom 节点是否相同

  • 使用 === 来比较两个元素
  • A.isSameNode(B):是同一个节点时返回 true。DOM 4 被废弃。
  • isEqualNode():检查两个节点是否相等,不一定相同。
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

<script>
const lis = [...document.getElementsByTagName('li')];
const ul = document.getElementById('list');

// 点击哪个li,就会输出它的下标。
ul.addEventListener('click',function(e) {
if(e.target.tagName === 'LI') {
const i = lis.findIndex(item => item === e.target); // 使用严格相等比较。
console.log(i);
}
})
</script>

问题:浏览器存储

💊 Storage

WebStorage 主要提供了一种机制,可以让浏览器提供一种比 cookie 更直观的 key / value 存储方式:

  • localStorage:本地存储,永久性存储。网页关闭后,存储的内容依然保留;
  • sessionStorage:会话存储,本次会话的存储。关闭掉会话后,清除存储;

在浏览器中,一个标签是同一个会话。

通过:开发者工具 - Application 可以查看:

截屏2022-08-16 19.25.45

💊 Storage 属性和方法

localStorage 和 sessionStorage 都具有以下方法:

属性 1:

  • Storage.length:只读。 存储在 Storage 对象中的数据项数量;

方法 5:

  • Storage.key(index):获取第 index 个成员的 Key ;
  • Storage.getItem(key):获取 key 对应的 value;
  • Storage.setItem(key, value):把 key / value 添加到存储中;
    • 如果key 已经存在,则更新值;
  • Storage.removeItem(key):删除成员;
  • Storage.clear():清空 storage。

常用:

const person = { 
name: 'huangyanting',
age: 3
}
// 不能直接存储对象,storage会默认调用 toString 后存储。
// sessionStorage.setItem('tg',person);
// sessionStorage.getItem('tg'); // '[object Object]'

// 转化为JSON 存储
sessionStorage.setItem('tg',JSON.stringify(person));
const result = sessionStorage.getItem('tg') // {"name":"huangyanting","age":3}

💊 IndexedDB

DB:Database 数据库。

  • 在服务器端比较常见。实际开发中,大量的数据都是存储在数据库的,客户端主要是请求这些数据并且展示。
  • Storage:(常用)存储简单的数据到本地(浏览器中),如 token、用户名、密码、用户信息等。
  • IndexedDB:(少用)大量的数据需要存储。

IndexedDB 是一种底层的 API。用于在客户端存储大量的结构化数据。

  • 一种事务型数据库系统,基于 JavaScript 面向对象数据库,类似于NoSQL(非关系型数据库)。
  • IndexDB 本身基于事务,程序员只需指定数据库模式,打开与数据库的连接,检索、更新一系列事务即可。
    • 事务:一个操作单元。涉及:事务隔离、事务回滚、事务传播等。
  • 数据库的增删改查,效率比 Storage 效率更高。

截屏2022-08-16 22.17.18

Cookies:文本,通常是网站为了辨别用户身份而存储在用户本地终端 (Client Side)上的数据。

  • 客户端可以通过 setCookie 添加 cookie 到客户端。
  • 浏览器发送请求时,可以把 cookie 携带发送;

Cookie 总是保存在客户端中。按在客户端中的存储位置,Cookie 可以分为内存 Cookie 和硬盘 Cookie。

  • 内存 Cookie 由浏览器维护,保存在内存中,浏览器关闭时 Cookie 就会消失,其存在时间是短暂的;
  • 硬盘 Cookie 保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;
    • 没有设置过期时间,默认是内存 cookie。

截屏2022-08-16 22.35.30

如上图,响应中有 Set-Cookie

Set-Cookie: "name=why; path=/; expires=Wed, 10 Nov 2021 09:35:46 GMT; httponly"
// 保存了:Key/Value 内容、保存路径、过期时间等

如果响应中添加了 cookie,浏览器自动读取并保存 cookie,也会再下次往同一服务器发送请求时,携带 cookie。

Cookie 的属性:

  • 生命期:Expires 具体时间 、Max-Age 生命秒数,下文有;
  • 作用域:Domain 包含的域名、Path 包含的路径,下文有;
  • 安全:
    • Secure:只能使用 HTTPS 安全协议时,才传输 cookie。
    • HTTPOnly:禁止客户端修改 cookie,防止 XSS 攻击(document.cookie 修改 cookie)。
    • SameSite:让 Cookie 在跨站请求时不会被发送,阻止跨站请求伪造攻击(CSRF)。
      • Strict:完全禁止第三方(跨站)Cookie,sso 登录等功能将失效。
      • Lax:允许部分第三方请求携带 Cookie,如:a、link、get。不允许:post、iframe、ajax、图片。
      • None:不限制。跨站可以携带 cookie。
    • 常见网络攻击方式

💊 localStorage、sessionStorage、cookie 区别

相同:

  1. 存储空间均为5101k,约等于4.98M。
  2. 存储的格式为 string。

不同:生命期 + 作用域。

生命期:

  • localStorage:数据是永久性的,除非 Web 应用或用户手动删除。

  • sessionStorage :数据的生命期和标签页相同。标签页关闭后,数据会被删除。

  • cookie

    • expires:设置的是 Date.toUTCString(),具体的某个时间。
    • max-age:设置过期的秒钟,max-age=max-age-in-seconds (例如一年为60*60*24*365);

作用域:

  • localStorage:作用域为 文档来源,由 协议、域名、端口 共同定义。同源文档共享 localstorage 数据。

  • sessionStorage :作用域为 文档来源 + 窗口隔离 。用户在两个标签页中打开了同一来源的文档,这两个标签页的 sessionStorage数据也是隔离的。

  • cookie:(允许 cookie 发送给哪些 URL)

    • Domain:指定哪些主机可以接受 cookie。cookie 允许 同站 发送(顶级 + 二级域名相同 kuai.com
      • 如果不指定,那么默认是 origin 原始域名,不包括子域名。
      • 如果指定,则包含子域名。
      • 例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中,如 developer.mozilla.org
  • Path:指定浏览器在哪些 URL 路径可以携带 cookie 发送请求。
    • 例如,设置 Path=/docs,则以下地址都会匹配: /docs , /docs/Web/ , /docs/Web/HTTP

生命期:

  • 默认:生命期很短,只在浏览器会话期间存在,用户退出浏览器后,就会丢失。
  • 指定生命期:以秒为单位,时间到期后会删除数据。如果指定了时间长的生命期,浏览器就会把 cookie 存储在本地文件中,等时间到了再把它们删除。

作用域:为文档来源 + 文档路径。

  • 文档来源(domain 属性):同 localStorage 效果。
  • 文档路径(path 属性):默认情况下, cookie 的可访问权限为 该网页位于相同目录和子目录下的其他网页(即,兄弟网页、子网页、兄弟的子网页)。
    • 也可以指定 path 路径。这样来自同一服务器的任何网页,都可以访问一个 cookie。

Cookie API

// 获取 cookie
console.log(document.cookie);

// 设置 cookie
document.cookie = "name=ninjee";
document.cookie = "age=18";

// 每一条 cookie,都可以设置生存期,默认无
document.cookie = "name=ninjee&age=18;max-age=10";

Cookie 缺点:

  • 客户端每次请求,都会携带 cookie,有可能不需要,所以造成性能浪费。
  • 有体积限制:4KB。
  • 明文传输,安全性较低。
  • 客户端多样(浏览器、IOS、Android、小程序),有些客户端可能默认不支持 cookie

💊 localStorage、sessionStorage、cookie 使用场景

localStorage

  • 存储用户访问习惯:登录次数、登录时间等
  • 用户本地化配置:用户语言、夜间主题等

sessionStorage

  • 同一域名下的页面传值:A 页面获取的数据,需要在 B 页面发送给后端。
//A页面
//首先检测Storage
if (typeof(Storage) !== "undefined") {
sessionStorage.'name' = value;
} else {
sessionStorage.name = '';
}


//B页面
if (typeof(Storage) !== "undefined") {
var B_name = sessionStorage.name;
}

Cookie

  • SSO 单点登录,保存 Ticket。
  • 用户保持登录状态的 Token。

问题:浏览器缓存

DNS 缓存

DNS 定义:Domain Name System 域名系统。万维网上作为域名和 IP 地址相互映射的数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。DNS 协议运行在 UDP 协议之上,使用端口号 53。

DNS查询过程如下:

  1. 浏览器 DNS 缓存:如果存在,则解析完成。
  2. 操作系统的 hosts 文件:查看是否存在对应的映射关系,如果存在,则解析完成。
  3. 本地 DNS 服务器:(ISP 服务器,或者自己手动设置的DNS服务器),如果存在,则解析完成。
  4. 根服务器:发出请求,递归查询。

11951666519595_.pic

CDN 缓存

CDN 定义:Content Delivery Network 内容分发网络火车票代售点,菜鸟驿站,解决最后 1km。CDN 帮助缓存服务器在最近的 CDN 节点 / 用最短的请求时间拿到资源。同时起到请求分流的作用,减轻服务器负载压力。

在浏览器本地缓存失效后,浏览器会向 CDN 边缘节点发起请求。

类似浏览器缓存,CDN 边缘节点也存在着一套缓存机制。一般都遵循 http 标准协议,和浏览器近似通过响应头中的 Cache-control 字段,来设置 CDN 强缓存的时间和策略。

  • 浏览器协商缓存:浏览器向 CDN 节点发起请求,CDN 节点判断本地缓存数据是否过期:
    • 没过期,则和浏览器按协商规则返回资源;
    • 过期,则 CDN 向服务器更新最新数据,然后再和浏览器按协商规则返回资源。
  • CDN 服务商会基于文件后缀、目录多个维度来指定 CDN 缓存时间,精细化缓存管理。

CDN 优势:

  1. CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低。
  2. 大部分请求在 CDN 边缘节点完成,起到了分流作用,减轻了源服务器的负载。

浏览器缓存:强缓存、协商缓存

浏览器缓存的意义:设置缓存后,就能将第一次访问到的资源存在本地,当第二次再去访问时就可以直接访问本地的资源,也就不用去服务器上拉取。

浏览器缓存的位置:2+2

  • Memory Cache:基于内存的缓存,读取高效速度快,关闭网页,内存释放。

  • Disk Cache:基于磁盘的缓存,容量大,读取慢,存储在本地中。

  • Service Worker:浏览器额外的独立线程,它可以控制缓存文件、匹配方式、读取缓存,存储在磁盘中。

  • Push Cache:HTTP/2,不设置缓存策略时,采用启发式算法(下问),缓存在 sessionStorage 内存中。

缓存策略 3

通过设置 HTTP Header 来实现缓存策略:强缓存协商缓存不设置缓存策略

截屏2022-08-22 21.48.16

  • 强缓存:不向服务器发送请求,直接从缓存中读取资源,两种 HTTP Header 实现:

    • Expires:HTTP/1 属性。缓存过期时间,指定资源到期时间。是服务器端具体的时间点,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

    • Cache-Control: HTTP/1.1 属性,优先级高于 Expires。

      • max-age:缓存最大过期时间。单位秒。
      • no-cache:可以在客户端存储资源,每次都必须要向服务端请求,新鲜度校验,获取新的资源(200)还是使用客户端本地缓存(304)。
      • no-store:禁止在客户端存储资源,每次请求都必须从服务器获取资源。
  • 协商缓存:强制缓存的内容失效后,浏览器携带缓存标识向服务器发起请求,服务器根据缓存标识,判断客户端存储的本地资源是否已经过期,通知客户端是否继续使用本地缓存。一致则返回 304,否则返回 200 和最新的资源。两种 HTTP Header 实现:

    • Last-Modified:HTTP/1,资源的最后修改时间。

      • 第一次访问资源:服务器在 response 头里添加 Last-Modified 服务器最后修改文件的时间点。
      • 第二次访问资源:检测到缓存文件里有 Last-Modified,在请求头里加 If-Modified-Since,值为 Last-Modified 的值。服务器拿这个值和请求文件的最后修改时间作对比:没有变化,返回 304;如果小于最后修改时间,说明文件有更新,就会返回新的资源,状态码为 200。
    • ETag: HTTP/1.1,资源的唯一标识。

      • 初次请求:服务器返回一个新的 Etag: token
      • 二次请求:把 token 包裹在请求头里的 If-None-Match 发送给服务器。服务器比较新旧 token:一致,返回 304 通知浏览器使用本地缓存;不一致,返回新资源,新 ETag,状态码为 200。
    • 对比:ETag 更好。精确 ,Last-Modified 只能精确到秒级、若资源重复生成内容不变,则 Etag 值不变。

  • 不设置缓存策略:浏览器采用启发式算法,缓存时间 = (取响应头中的Date - Last-Modified) * 10%

  • https://juejin.cn/post/7061588533214969892#heading-52

问题:事件循环

浏览器事件循环

(1)浏览器的结构:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。

  • 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。

  • 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。

    • 主线程:处理js代码(解析、执行)。只要消息队列不为空,就会一直从中取任务执行。由于主线程和GUI线程的互斥,所以当一个js任务执行过长时,会阻塞页面的渲染,造成页面的卡顿。
    • GUI渲染线程 负责解析 HTML、CSS、合成 CSSOM 树、布局树、绘制、分层、栅格化、合成。重绘、重排、合成都在该线程中执行。
      • GUI 线程和 JS 引擎线程是冲突的,当 GUI 线程执行时,js 引擎线程会被挂起;当 js 引擎线程执行任务时,有需要 GUI 线程执行的任务,会被保存到一个队列中,等待 js 引擎执行完执行。
    • 事件触发线程:当 js 代码在解析时,遇到事件时,比如鼠标监听,会将这些任务添加到事件触发线程中。等事件触发时,会将任务从事件触发线程中取出,放到消息队列的队尾等待执行。
    • 定时器触发线程:用于存放 setTimeout、setInterval 等任务,在解析遇到这些任务时,js 引擎会将这些任务放到定时器触发线程中,并开始计数,时间到了之后,将任务放到消息队列中等待执行。
    • http请求线程:用于检测 XMLHttpRequest 请求,当请求状态改变时,将设置的回调函数添加到消息队列中等待执行。
  • GPU 进程。其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。

  • 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。

  • 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

(2)浏览器页面是由消息队列和事件循环系统来驱动的。

消息队列和事件循环系统,统筹调度这些线程产生的任务,让各种类型的任务在主线程中有条不紊地执行。

  • 消息队列 是一个队列数据结构,存放主线程要执行的任务,符合队列的先进先出特点。

(3)宏任务和微任务🔗

在渲染进程中,把消息队列中的任务称为 宏任务,每个宏任务中都包含了一个 微任务队列

宏任务和微任务的加入,使任务执行实现了 效率实时性 的平衡。

  • 效率:宏任务按序执行,没有饿死;
  • 实时性:微任务优先级更高,不需要消息队列等待,优先执行完毕。

定义:

  • 宏任务:setTimeout、setInterval、XMLHttpRequest (I/O线程)、UI 渲染等。
  • 微任务:Promise.then (回调)、MutationObserver (监控DOM节点) 等。

(4)执行过程 🔗 没看完

  • 系统调用栈。和 Js 执行时一样,浏览器在运行时也有一个系统调用栈,在开发工具中可以看到 “火焰图”。
  • 消息队列。从消息队列中顺次执行宏任务。
    • 微任务队列。每执行完宏任务,执行完宏任务中的微任务队列。
    • :在此期间产生的新微任务,也放入当前微任务队列中。
  • 异步任务。执行完微任务队列,会判断延迟队列中是否有到期任务,加入到消息队列中。

像 while(true) 循环一样event loop 不断执行下边的步骤:

  1. 宏任务。在 tasks 队列中 pop 一个宏任务,并执行。
  2. 微任务。Microtasks 任务检查点,判断队列是否有微任务,并依次执行
  3. 更新渲染。Update the rendering 判断是否更新渲染:在一帧以内的多次 dom 变动,浏览器不会立即响应,而是会积攒变动以最60HZ 的频率更新视图。

(5)浏览器每一帧要完成的工作

  1. 事件。处理用户的交互,如点击、触碰、滚动等事件
  2. JS 解析执行。
  3. 帧开始。窗口尺寸变更,页面滚动等的处理。
  4. rAF。requestAnimationFrame(rAF)。每一帧都会执行的回调函数
  5. Layout。布局
  6. Paint。绘制
  7. requestIdleCallback。如果绘制后,依然有空闲时间,就会执行回调。

Node事件循环

nodeJ 中,使用 libuv 引擎(一个基于事件驱动的异步 I/O 库)实现事件循环。

(1)六个阶段

libuv 引擎中事件循环分为 6 个阶段(队列),顺次反复执行。当执行到某个队列时,有两个情况会跳转到下一个队列:

  • 当前队列执行为空、执行当前队列的任务数量 / 任务时间达到阈值。

六个阶段:

在事件循环的每个过程中,Node.js检查是否它正在等待异步的I/O和计时器,如果没有则完全关闭。

  1. timers 阶段:执行定时器回调:setTimeout、setInterval。

  2. pending callbacks 阶段:处理上一轮循环中未执行的 I/O 回调,如TCP的错误捕获等。

  3. idle、prepare 阶段:仅 node 内部使用

  4. poll 阶段:获取新的 I/O 事件,执行 I/O 相关回调。

    • 执行 poll 队列里的事件:除 timers / check / 微任务,其他事件全部在这里执行。

    • 阻塞。进入 poll 时,即使没有要执行的事件,poll 也会一直等下去。

      • 停止阻塞:其他队列中有可执行事件、达到阻塞最大时间。
  5. check 阶段:执行 setImmediate() 回调

  6. close callbacks 阶段:执行关闭回调,例如: socket.on('close', ...)

开发者常用:timers、poll、check 这三个阶段。

(2)微任务

和浏览器一样,每个宏任务中都附带一个微任务队列,所以微任务不受事件循环限制,只要微任务队列中有任务存在,就会优先执行。

  • process.nextTick() 优先级高于 promise
  • Promise.then()

问题:前端优化策略

  1. 建立监控体系

    1. 利用现成(阿里云ARMS、听云、监控宝)或自行搭建埋点监测。
  2. 确定采集指标

    • JS Error:解析后可以细分为运行时异常、以及静态资源异常。

    • 请求异常:采集ajax请求异常。

    • DNS, TCP, DOM 解析等阶段的指标。

    • 首次绘制时间( FP ) :即 First Paint,为首次渲染的时间点。

      首次内容绘制时间( FCP ) :即 First Contentful Paint,为首次有内容渲染的时间点。

      最大内容绘制 (LCP):即 Largest Contentful Paint,测量加载性能。为了提供良好的用户体验,LCP 应在页面首次开始加载后的2.5 秒内发生。

      首次交互时间(FID):即 First Input Delay,记录页面加载阶段,用户首次交互操作的延时时间。FID 指标影响用户对页面交互性和响应性的第一印象。

      累积布局偏移 (CLS):即 Cumulative Layout Shift,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1 或更少。

  3. 进行优化

(1)资源优化:

  • 文本优化:Brotli + Gzip 纯文本压缩:HTML、CSS、SVG、JavaScript 等。
  • 图像优化:
    • 使用响应式图像和 WebP。与 pngjpg 相比,相同的视觉体验下,WebP 图像的尺寸缩小了大约30%。缺点:不支持 JPEG 的渐进式渲染,先模糊,后清晰。
    • 图像懒加载,率先加载用户视口出现的图像。
    • 渐进加载图像,先模糊,后清晰。
    • 瀑布流,图片加载完一个,就通过动画添加到瀑布流中。类似 pintrest。
    • 骨架屏,图片不一定要立即加载,可以先加载 dom,再加载耗时资源。
    • 分批加载,主动将对图片的网络请求分批次异步加载,防止耗时过长,掉帧卡顿。
    • 按需加载。按照屏幕分辨率加载图像质量,用户点击图像才加载更高清图像。
    • JPEG、png、SVG 等都有对应的压缩协议。
  • 字体优化:
    • 字体包通常不需要适配所有的文字,可以对字体进行子集化 subfont。
    • 使用 preload 来预加载字体。

(2)构建优化

  • tree-shaking :清理构建包中无用依赖。让构建结果只包含生产中实际使用的代码。借助 Webpack 可以检测到 import 链可以在哪个位置终止并转换为一个内联函数,而不破坏代码。

  • code-spliting:组件懒加载。把代码拆分为按需加载的chunk。并不是所有 JavaScript 都必须立即下载、解析和编译。一旦在代码中定义了分割点,Webpack 就可以处理依赖关系和输出文件。它可以让浏览器保持较小的初始下载量,并在应用程序请求时按需请求代码。

  • preload-webpack-plugin:该插件根据代码的分隔方式,引导浏览器使用 <link rel="preload"><link rel="prefetch"> 对分隔的代码 chunk 进行预加载。

  • 识别并删除未使用的 CSS / JS。Chrome 中的 CSS 和 JavaScript 代码覆盖率工具(Coverage) 可以让我们了解哪些代码已执行或应用,哪些未执行。我们可以启动一个覆盖率检查,然后查看覆盖率结果。一旦检测到未使用的代码,找出那些模块并使用 import() 延迟加载

    • mock 单元检查,将用不到的函数和逻辑检查出来,然后删除。
  • 设置 HTTP 缓存报文头

    • 检查 expiresmax-agecache-control 和其他 HTTP 缓存报文头是否已正确设置。一般来说,资源可以在 很短的时间内或无限期 缓存,并且可以在需要时通过 URL 中更改其版本。确保没有发送不必要的报头。
  • 避免回流和重绘🔗

    • 减少重排的范围。减去不需要重排的元素。
    • 读写分离操作。对 DOM 属性要读写分离,因为读的前提是要绘制出来,读之前要触发绘制。批量写入后再读取,减少了重绘次数。
    • 样式集中改变。通过对 class 名操作样式,而不是频繁操作 style。利用 class 集中改变样式。
    • 批量 dom 操作。例如 createDocumentFragment,或者使用框架,例如 React
      • 将 DOM 从树上摘下(display: none) 后,批量修改再放上。
    • 避免使用 table 布局。table 通常会整体发生改变。
    • 优化动画。动画的平滑效果与对CPU资源的消耗要达到平衡,还可以启动GPU加速。
    • 限制窗口大小的调整。窗口大小变化,就一定会导致重排重绘。
    • 采用合成手段,使用 transform,避开重排和重绘阶段。
  • 服务端渲染

(3)传输优化

  • JavaScript 异步加载。defer 对 script 异步加载,并顺序执行不阻塞 dom。
  • 关键 CSS。找到影响首屏的 CSS 样式规则,并拆分后率先加载。
  • 加快请求速度:预解析DNS;使用HTTP2.0,并行加载; 使用 CDN 分发
  • 减少事件委托、增加防抖节流、HTTP1.0 使用精灵图减少请求次数。CSS 写头、JS 写底。

问题:前端鉴权

  1. 为什么鉴权?

HTTP 无状态,HTTP 请求方和响应方直接无法维护状态,都是一次性的,无法确认请求和响应是否是同一人。用户登陆、上传、关注、评论,都需要状态维护,这是时候双方在沟通前,需要一个既不容易被人窃取,又容易解析并认证识别的 标记

  1. 前端存储标记
  • 挂载到全局变量,一次页面刷新则失效。
  • 存储 cookie、localStorage,本地保存。
  1. 方案

(1)服务端 session

截屏2022-09-27 15.50.41

服务器给浏览器一个 sessonid,包含了用户信息、session状态、登录时间、登录设备等等信息。通常前端保存在 cookie 上。

安全问题:cookie 一旦被窃取,可以随意登陆。

后端问题:保存 sesionid 通常需要通过 k/v 的形式缓存在 Redis 加速访问。

(2)token

截屏2022-09-27 15.54.38

登录成功后,服务器每次返回资源,都会携带一个一次性的 token,客户端把 token 绑定到自身的 dom 树上。可以是 body、html,或者是在提交表单时,做为参数绑定在 src 地址上。

  • 当客户端再次发送申请,服务器会验证 token,并更新 token。
  • token 可以通过 base64 编码,也可以通过对称加密算法,对 token 签名防止篡改。
  • JWT,额外的算法增加了开销,使用 JSON Web Token 开放标准,通过数字签名加密。

截屏2022-09-27 15.59.21

(3)单点登录

单点登录(Single sign-on,SSO),顾名思义,它把两个及以上个产品中的用户登录逻辑抽离出来,达到只输入一次用户名密码,就能同时登录多个产品的效果。

SSO 解决了同一个二级域名下所有产品的统一登录需求,相当于旅游景区的联票机制

CAS 协议是最主流的 SSO 实现方式,有以下几个角色:

  • User:即使用浏览器登录网站,使用相关服务的用户。
  • Browser:浏览器。
  • CAS Server:CAS 服务器,统一管理用户的 CAS 登录信息。
  • Protected Apps:浏览器要访问的目标服务器。

截屏2022-09-27 16.03.32

问题:页面间通信

同源:Local Storage

Local Storage 用于存储数据,通过 storage 事件,可以对存储状态进行监听,从而达到页面间通信的目标。

// A页面
window.onstorage = function(e) {
console.log(e.newValue); // previous value at e.oldValue
};
// B页面
localStorage.setItem('key', 'value');

同源:indexDB

用于客户端存储大量结构化数据,检索性能优秀,区别于 LocalStorage只能存储字符串,IndexedDB可以存储 JS所有的数据类型,包括 nullundefined等,是 HTML5规范里新出现的 API。

同源:BroadcastChannel

兼容性差的 API。A 页面广播信号,创建自身引用。B 页面舰艇同源页面下的广播信号,并通过引用名,判断广播源,来分辨信息。

跨域:postMessage

A 页面通过 window.open 获得 B页面的引用,向 B页面发送信号,并监听 B页面回传回来的信号。

<!-- A页面 -->
<div id="msg"></div>
<script>
window.onload = () => {
// 获取句柄
var opener = window.open('http://127.0.0.1:9001/b.html')
// setTimeout 是为了等到真正获取到 opener的句柄再发送数据
setTimeout(() => {
// 只对 域名为 http://127.0.0.1:9001的页面发送数据信号
opener.postMessage('red', 'http://127.0.0.1:9001');
}, 0)
// 监听从句柄页面发送回来的数据信号
window.addEventListener('message', event => {
if(event.origin === 'http://127.0.0.1:9001'){
document.getElementById('msg').innerHTML = event.data
}
})
}
</script>

<!-- B页面 -->
<div id="box">color from a.html</div>
<script type="text/javascript">
window.addEventListener('message', event => {
// 通过origin属性判断消息来源地址
// 只有当数据信号来源于 http://127.0.0.1:9001的服务器才接收
if(event.origin === 'http://127.0.0.1:9001'){
// 获取信息员的数据信号
document.getElementById('box').style.color = event.data
// 通过 event.source向信号源反向发送数据
event.source.postMessage('got your color!', event.origin)
}
})
</script>

跨域:webSocket

WebSocketHTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,常用的场景是即时通讯。webpack 的热更新就是利用该原理。