网络 & 浏览器

发布于 2023-05-11  47 次阅读


HTTP 请求方式有哪些

序号方法描述
1GET请求指定的页面信息,并返回实体主体。
2HEAD类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
3POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
4PUT从客户端向服务器传送的数据取代指定的文档的内容。
5DELETE请求服务器删除指定的页面。
6CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
7OPTIONS返回服务器针对特定资源所支持的HTTP请求方法
8TRACE回显服务器收到的请求,主要用于测试或诊断。

说说HTTP 常见的状态码有哪些,适用场景?

一、是什么

HTTP状态码(英语:HTTP Status Code),用以表示网页服务器超文本传输协议响应状态的3位数字代码

它由 RFC 2616规范定义的,并得到 RFC 2518、RFC 2817、RFC 2295、RFC 2774与 RFC 4918等规范扩展

简单来讲,http状态码的作用是服务器告诉客户端当前请求响应的状态,通过状态码就能判断和分析服务器的运行状态

二、分类

状态码第一位数字决定了不同的响应状态,有如下:

  • 1 表示消息
  • 2 表示成功
  • 3 表示重定向
  • 4 表示请求错误
  • 5 表示服务器错误

1xx

代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束

常见的有:

  • 100(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应
  • 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级

2xx

代表请求已成功被服务器接收、理解、并接受

常见的有:

  • 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201(已创建):请求成功并且服务器创建了新的资源
  • 202(已创建):服务器已经接收请求,但尚未处理
  • 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
  • 204(无内容):服务器成功处理请求,但没有返回任何内容
  • 205(重置内容):服务器成功处理请求,但没有返回任何内容
  • 206(部分内容):服务器成功处理了部分请求

3xx

表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向

常见的有:

  • 300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
  • 301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
  • 302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
  • 304(协商缓存):查看浏览器的 if-none-match 和 if-modified-since 和 服务器 ETag 和 last-modified 是否一直,一致就返回 304 响应头告知浏览器使用本地缓存
  • 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
  • 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4xx

代表了客户端看起来可能发生了错误,妨碍了服务器的处理

常见的有:

  • 400(错误请求): 服务器不理解请求的语法
  • 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403(禁止): 服务器拒绝请求
  • 404(未找到): 服务器找不到请求的网页
  • 405(方法禁用): 禁用请求中指定的方法
  • 406(不接受): 无法使用请求的内容特性响应请求的网页
  • 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
  • 408(请求超时): 服务器等候请求时发生超时

5xx

表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生

常见的有:

  • 500(服务器内部错误):服务器遇到错误,无法完成请求
  • 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
  • 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
  • 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)
  • 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求
  • 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本

三、适用场景

下面给出一些状态码的适用场景:

  • 100:客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。常用于POST大数据传输
  • 206:一般用来做断点续传,或者是视频文件等大文件的加载
  • 301:永久重定向会缓存。新域名替换旧域名,旧的域名不再使用时,用户访问旧域名时用301就重定向到新的域名
  • 302:临时重定向不会缓存,常用 于未登陆的用户访问用户中心重定向到登录页面,又或者首页临时跳转到活动页
  • 304:协商缓存,告诉客户端有缓存,直接使用缓存中的数据,返回页面的只有头部信息,是没有内容部分
  • 400:参数有误,请求无法被服务器识别
  • 403:告诉客户端进制访问该站点或者资源,如在外网环境下,然后访问只有内网IP才能访问的时候则返回
  • 404:服务器找不到资源时,或者服务器拒绝请求又不想说明理由时
  • 503:服务器停机维护时,主动用503响应请求或 nginx 设置限速,超过限速,会返回503
  • 504:网关超时

参考文献

JS 延迟加载的方法有哪些?

  1. <script async src="script.js"></script>:给script标签加async属性,则加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
  2. <script defer src="script.js"></script>:给script标签加defer属性,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成
  3. 动态创建script标签:等到DOMContentLoaded 事件触发时,生成一个script标签,渲染到页面上上
  4. setTimeout 定时器延迟代码执行

懒加载与预加载区别

1. 懒加载

  • 什么是懒加载?当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片真正的路径,让图片显示出来。这就是图片懒加载。
  • 为什么要使用懒加载?
    • 提升用户体验 如果一个长网页,一次性把图片全部加载出来,图片密集度非常之高,数目较大,等待时间之久
    • 减少无效资源加载 按需要去加载数据,没有进入视口的不需要加载
    • 防止并发加载的资源过多,阻塞js的加载 影响网站的正常使用
  • 懒加载的原理:
    • 页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求。
    • 懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把真正的路径存在元素的“data-url”(这个名字起个自己认识好记的就行)属性里,要用的时候就取出来,再设置;
  • 懒加载的实现步骤:
    • 首先,不要将图片地址放到 src 属性中,而是放到自定义属性(data-original)中。
    • 页面加载完成后,根据 scrollTop 判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中
    • 在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。
  • 懒加载的优点:
    • 页面加载速度快、可以减轻服务器的压力、节约了流量,用户体验好
    • 服务器前端的优化,减少请求数或延迟请求数

2. 预加载

  • 什么是预加载?提前加载图片,当用户需要查看时可直接从本地缓存中渲染
  • 为什么要使用预加载?
    • 在网页全部加载之前,对一些主要内容进行加载,提供给用户更好的体验,减少等待的时间
    • 如果一个长页面,过于庞大,没有使用预加载,页面就会长时间展现为一片空白,直到所有内容加载完毕
  • 实现预加载:
    • 方法一:用CSS和JavaScript实现预加载
    • 方法二:仅使用JavaScript实现预加载
    • 方法三:使用Ajax实现预加载

3. 懒加载和预加载的区别

  • 概念:
    • 懒加载(延迟加载):图片延迟加载,当图片进入视口之后才加载
    • 预加载:提前加载图片,当用户需要查看时,直接从本地缓存读取
  • 区别:二者都是提高页面性能的有效方法
    • 一个是提前加载,一个是延迟加载甚至不加载,懒加载对服务器前端有一定的缓解压力的作用
    • 预加载则会增加服务器前端的压力
  • 意义:
    • 懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数
    • 预加载可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反映
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>预加载</title>
  <style>
    .container {
      width: 300px;
    }
  </style>
</head>
<body>
  <div class="container"></div>
  <script>
    let oDiv = document.querySelector('.container'),
        imgArr = [
          "<https://placekitten.com/200/100>",
          "<https://placekitten.com/300/200>",
          "<https://placekitten.com/400/300>",
          "<https://placekitten.com/500/400>",
        ];

    imgArr.forEach(imgSrc => {
      let img = new Image(); 
      img.style.width = "100%";
      img.src = imgSrc;
      img.onload = function() { // 图片加载完后再添加到页面中去
        oDiv.appendChild(img);
      }
    });
  </script>
</body>
</html>

监听 ajax 上传进度

//【上传进度调用方法实现】
xhr.upload.onprogress = progressFunction

浏览器多标签页之间的通信

  1. websocket
    1. 全双工(full-duplex)通信自然可以实现多个标签页之间的通信。
  2. setInterval + cookie 存值
    1. 在页面 A 设置一个使用 setInterval 定时器不断刷新,检查 Cookies 的值是否发生变化,如果变化就进行刷新的操作。
  3. 使用 localStorage
    1. localStorage 是浏览器多个标签共用的存储空间,所以可以用来实现多标签之间的通信(ps:sessionStorage 是会话级的存储空间,每个标签页都是单独的)。 直接在 window 对象上添加监听即可:
window.onstorage = (e) => {console.log(e)}
// 或者这样
window.addEventListener("storage", (e) => console.log(e))

如何最大限度应用浏览器的网络并发

问题来源是因为:同一域名下,get/post 请求的并发数量是6,超过6个时,后边请求会挂起等待,直到前边至少一个请求返回结果后才会接着请求

  • 优化方案
    • 减少网络请求
      • 雪碧图
      • 后端配置304协商缓存
      • webpack合并css和js,减少文件数量
      • vue动态路由,按需导入js
    • 增加静态资源来源
      • 把静态资源分布在不同的服务器中,使用多个域名,加大并发量

实现一个页面操作不会整页刷新的网站,并且能在浏览器的前进,后退时正确响应。给出你的技术实现方案?

  • onhashchange
  • window.history.pushState(添加新的历史记录)/replaceState(state,title, url)【改掉当前历史记录】
  • 配合 onpopstate 事件来监听历史记录的改变

来源:第34题

 三次握手四次挥手

  1. TCP 连接(三次握手)
    1. 客户端:你在吗(发送包 SYN x)服务器:我在,你听得到吗(发送包 SYN y + 确认包ACK x+1)客户端:听到了,我们可以发消息了(确认包 SYN y+1)
  2. 四次挥手,中断连接
    1. 客户端:我要关闭连接了(发送 FIN 数据包 x)
    2. 服务端:我知道了,你还有要发送的信息嘛?(确认包 ACK x+1)
    3. 服务端:确认自己没有要发送的信息了,告诉客户端我要关闭了(断开连接包 FIN y)
    4. 客户端:好的,我知道了(确认包 ACK y+1)
      1. 2MSL
        1. 客户端由于接收不到服务端发送的信息了,所以会等待2MSL,等待一段时间,发现不会再有数据了,就自己变成 CLOSE 状态2MSL作用(TIME-WAIT的意义):假如服务端没接收到客户端发送的第四次挥手,就会认为自己的第三次挥手客户端没收到,那么服务端就会重新发送第三次挥手,接着客户端收到第三次挥手,再发送第四次挥手。(为什么这个等待MSL过程不在服务端而在客户端,是因为服务端不会再第五次挥手了,所以只有客户端等待才行)。只要服务端重新发送了第三次挥手,客户端的MSL就会重新计时

HTTPS 握手过程中,客户端如何验证证书的合法性

  1. 校验证书的颁发机构是否受客户端信任。
  2. 通过 CRL 或 OCSP 的方式校验证书是否被吊销。
  3. 对比系统时间,校验证书是否在有效期内。
  4. 通过校验对方是否存在证书的私钥,判断证书的网站域名是否与证书颁发的域名一致。

A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态?如何消除服务器程序中的这个状态?

因为 B 会在重启之后进入 tcp 状态机的 listen 状态,只要当 a 重新发送一个数据包(无论是 syn 包或者是应用数据),b 端应该会主动发送一个带 rst 位的重置包来进行连接重置,所以 a 应该在 syn_sent 状态

大文件上传、断点续传

文件切片 -> 算 hash -> 卡的话用 webworker 或者时间切片 -> 顺便讲讲断点续传和秒传 -> 并发控制 -> 慢启动优化

  • 大文件上传
    • 思路:
      • 文件切片,每次上传部分内容,上传完所有切片后通知后端完成上传
    • 实现:
      • File 继承自 Blob ,利用 file.slice(start, end) 切片
      • 切完后 for 循环切片数组分批上传
      • 为了让后端识别到某些切片属于同一文件和保证切片顺序(多人同时上传,同个文件被上传多次)
        • 给切片添加文件标识(文件名 + 文件长度+用户id)
        • 传递切片索引,保证后端还原文件时顺序正确
// 切片
function slice(file, piece = 1024 * 1024 * 5) {
  let totalSize = file.size; // 文件总大小
  let start = 0; // 每次上传的开始字节
  let end = start + piece; // 每次上传的结尾字节
  let chunks = []
  while (start < totalSize) {
    // 根据长度截取每次需要上传的数据
    // File对象继承自Blob对象,因此包含slice方法
    let blob = file.slice(start, end); 
    chunks.push(blob)

    start = end;
    end = start + piece;
  }
  return chunks
}
// 使用
let file =  document.querySelector("[name=file]").files[0];

const LENGTH = 1024 * 1024 * 0.1;
let chunks = slice(file, LENGTH); // 首先拆分切片

chunks.forEach(chunk=>{
  let fd = new FormData();
  fd.append("file", chunk);
  post('/mkblk.php', fd)
})
// 获取context,同一个文件会返回相同的值
function createContext(file) {
     return file.name + file.length
}

let file = document.querySelector("[name=file]").files[0];
const LENGTH = 1024 * 1024 * 0.1;
let chunks = slice(file, LENGTH);

// 获取对于同一个文件,获取其的context
let context = createContext(file);

let tasks = [];
chunks.forEach((chunk, index) => {
  let fd = new FormData();
  fd.append("file", chunk);
  // 传递context
  fd.append("context", context);
  // 传递切片索引值
  fd.append("chunk", index + 1);
    
  tasks.push(post("/mkblk.php", fd));
});
// 所有切片上传完毕后,调用mkfile接口
Promise.all(tasks).then(res => {
  let fd = new FormData();
  fd.append("context", context);
  fd.append("chunks", chunks.length);
  post("/mkfile.php", fd).then(res => {
    console.log(res);
  });
});
  • 断点续传
    • 思路:
      • 本地 localStorage 缓存已上传切片信息,下次续传遍历切片时排除已传切片
    • 实现:
      • localStorage 中 setItem(标识名, 切片索引) 保存的是个当前文件的切片索引数组
      • 下次续传时,排除掉 localStorage 中已上传的切片索引
let context = createContext(file);
// 获取上传记录
let record = getUploadSliceRecord(context);
let tasks = [];
chunks.forEach((chunk, index) => {
  // 已上传的切片则不再重新上传
  if(record.includes(index)){
    return
  }
    
  let fd = new FormData();
  fd.append("file", chunk);
  fd.append("context", context);
  fd.append("chunk", index + 1);

  let task = post("/mkblk.php", fd).then(res=>{
    // 上传成功后保存已上传切片记录
    saveUploadSliceRecord(context, index)
    record.push(index)
  })
  tasks.push(task);
});

一句话概括 RESTFUL

用 URL 定位资源,用 HTTP 描述操作

BOM 浏览器对象

说几个很实用的 BOM 属性对象方法?

  • location 对象
    • location.href -- 返回或设置当前文档的 URL
    • location.search -- 返回 URL 中的查询字符串部分。例如 http://www.dreamdu.com/dreamdu.php?id=5&name=dreamdu 返回包括(?)后面的内容 ?id=5&name=dreamdu
    • location.hash -- 返回 URL # 后面的内容,如果没有 #,返回空
    • location.host -- 返回 URL 中的域名部分,例如 www.dreamdu.com
    • location.hostname -- 返回 URL 中的主域名部分,例如 dreamdu.com
    • location.pathname -- 返回 URL 的域名后的部分。例如 http://www.dreamdu.com/xhtml/ 返回 /xhtml/
    • location.port -- 返回 URL 中的端口部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回 8080
    • location.protocol -- 返回 URL 中的协议部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回(// )前面的内容 http:
    • location.assign -- 设置当前文档的 URL
    • location.replace() -- 设置当前文档的 URL,并且在 history 对象的地址列表中移除这个 URL location.replace(url);
    • location.reload() -- 重载当前页面
  • history 对象
    • history.go() -- 前进或后退指定的页面数 history.go(num);
    • history.back() -- 后退一页
    • history.forward() -- 前进一页
  • Navigator 对象
    • navigator.userAgent -- 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)
    • navigator.cookieEnabled -- 返回浏览器是否支持(启用) cookie

浏览器渲染

浏览器渲染页面的过程

从耗时的角度,浏览器请求、加载、渲染一个页面,时间花在下面五件事情上:

  1. DNS 查询
  2. TCP 连接
  3. HTTP 请求及响应
  4. 服务器响应
  5. 客户端渲染

浏览器对内容的渲染,这一部分(渲染树构建、布局及绘制),又可以分为下面五个步骤

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。
    1. 绘制 render 树(painting 重绘),绘制页面像素信息
    2. 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成(composite)显示在屏幕上。

这五个步骤并不一定一次性顺序完成。如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM

参考来源:浏览器的渲染:过程与原理

构建渲染树时,浏览器主要完成以下工作

  • 从 DOM 树的根节点开始遍历每个可见节点
  • 对每个可见节点,找到 CSS 规则树中对应的规则,并应用它们
  • 根据每个可见节点以及其对应的样式,组合生成渲染树

不可见节点(也就是不会出现在渲染树中的节点)

  • 一些不会渲染输出的节点(如:script、meta、link 等)
  • 一些通过 css 进行隐藏的节点(如:display: none)。

不过要注意:visiblity 和 opacity 隐藏的节点是会显示在渲染树上的。

浏览器对内容的渲染中,浏览器已经拿到了后端返回的 HTML 内容,开始解析并渲染。最初拿到的内容就是一堆字符串,必须先结构化成计算机擅长处理的基本数据结构,因此要把 HTML 字符串转化成 DOM 树 —— 树是最基本的数据结构之一。

解析过程中,如果遇到<link href="..."><script src="...">这种外链加载 CSS 和 JS 的标签,浏览器会异步下载,下载过程和上文中下载 HTML 的流程一样。只不过,这里下载下来的字符串是 CSS 或者 JS 格式的。

浏览器将 CSS 生成 CSSOM,再将 DOM 和 CSSOM 整合成 RenderTree ,然后针对 RenderTree 即可进行渲染了。大家可以想一下,有 DOM 结构、有样式,此时就能满足渲染的条件了。另外,这里也可以解释一个问题 —— 为何要将 CSS 放在 HTML 头部?—— 这样会让浏览器尽早拿到 CSS 尽早生成 CSSOM,然后在解析 HTML 之后可一次性生成最终的 RenderTree,渲染一次即可。如果 CSS 放在 HTML 底部,会出现渲染卡顿的情况,影响性能和体验。

最后,渲染过程中,如果遇到<script>就停止渲染,执行 JS 代码。因为浏览器渲染和 JS 执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染 DOM 冲突。待<script>内容执行完之后,浏览器继续渲染。最后再思考一个问题 —— 为何要将 JS 放在 HTML 底部?—— JS 放在底部可以保证让浏览器优先渲染完现有的 HTML 内容,让用户先看到内容,体验好。另外,JS 执行如果涉及 DOM 操作,得等待 DOM 解析完成才行,JS 放在底部执行时,HTML 肯定都解析成了 DOM 结构。JS 如果放在 HTML 顶部,JS 执行的时候 HTML 还没来得及转换为 DOM 结构,可能会报错。

关于浏览器整个流程,百度的多益大神有更加详细的文章,推荐阅读下:《从输入 URL 到页面加载完成的过程中都发生了什么事情? 》。

异步(延迟)加载 js 加载的方式有哪些?

将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

  • 无阻塞脚本加载
    • defer
      • 加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
    • async
      • 加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
  • 动态添加脚本元素
    • 动态创建DOM方式(用得最多)
// jquery创建元素
var container = $('.pc-container');
$('<img>').attr({
	src:'images/star.png'
}).css({
    top:'50px',
    left:'50px',
    transform:'scale(.5) rotateZ(90deg)',
    position: 'absolute'
}).addClass('myImg').appendTo(container);

// 原生js创建元素
var container=document.getElementsByClassName('.pc-container')[0];
var div=document.createElement('div');
div.setAttribute('id','example');// div.id = "example";div.className = "slogan";
div.style.width='120px';
container.appendChild(div);
  • 动态添加js脚本(封装)
// 创建script,插入到DOM中,加载完毕后callBack
function LoadScript(url, callback) {
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // IE浏览器下
    if (script.readyState) {
        script.onreadystatechange = function () {
            if (script.readyState == 'loaded' || script.readyState == 'complete') {
                // 确保执行两次
                script.onreadystatechange = null;
                // todo 执行要执行的代码
                callback()
            }
        }
    } else {
        script.onload = function () {
            callback();
        }
    }

    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script);
}
  • 按需异步载入js
将相关的 JavaScript 逻辑代码放到一个文件中,
在需要的时候,创建一个 script 对象,插入 document 对象,由浏览器加载并执行该文件

// 百度统计
var _hmt = _hmt || [];
(function () {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?<xxxxx>";
  var s = document.getElementsByTagName("script")[0];
  s.parentNode.insertBefore(hm, s);
})();

参考:

⭐ load、$(document).ready、DOMContentLoaded的区别?

  • $(document).ready、DOMContentLoaded:DOM树构建完毕,但还没有请求静态资源
  • load:静态资源请求完毕

⭐ 如何计算白屏时间和首屏时间

  • 白屏时间: window.performance.timing.domLoading - window.performance.timing.navigationStart
  • 首屏时间: window.performance.timing.domInteractive - window.performace.timing.navigationStart

浏览器兼容性

⭐ 浏览器的差异怎么抹平

  • css: normalize.css 或者 reset.css
  • js: 各种 polyfill(垫片,打补丁)实现当前浏览器不支持的原生API功能
    • 比如 babel-plugin-transform-runtime 实现 Promise

安全

⭐ 为什么浏览器要添加同源策略

  • 为了减少服务器压力
  • 为了数据安全

JSONP 可以跨域请求,原理是利用 script 标签可以跨域,那么为什么 script 可以跨域

浏览器为了安全,提供了同源策略,但完全封闭的话,又太过死板,违背了互联网的开放性的理念,同时也不利于资源的共享,所以又开放了一些,比如可以让img src访问非同源的资源 总结下来,就是安全和灵活的一个平衡

⭐ XSS(Cross Site Scripting,跨站脚本攻击)

这是前端最常见的攻击方式,很多大型网站(如 Facebook)都被 XSS 攻击过。

举一个例子,我在一个博客网站正常发表一篇文章,输入汉字、英文和图片,完全没有问题。但是如果我写的是恶意的 JS 脚本,例如获取到document.cookie然后传输到自己的服务器上,那我这篇博客的每一次浏览都会执行这个脚本,都会把访客 cookie 中的信息偷偷传递到我的服务器上来。

其实原理上就是黑客通过某种方式(发布文章、发布评论等)将一段特定的 JS 代码隐蔽地输入进去。然后别人再看这篇文章或者评论时,之前注入的这段 JS 代码就执行了。JS 代码一旦执行,那可就不受控制了,因为它跟网页原有的 JS 有同样的权限,例如可以获取 server 端数据、可以获取 cookie 等。于是,攻击就这样发生了。

XSS 的危害

XSS 的危害相当大,如果页面可以随意执行别人不安全的 JS 代码,轻则会让页面错乱、功能缺失,重则会造成用户的信息泄露。

比如早些年社交网站经常爆出 XSS 蠕虫,通过发布的文章内插入 JS,用户访问了感染不安全 JS 注入的文章,会自动重新发布新的文章,这样的文章会通过推荐系统进入到每个用户的文章列表面前,很快就会造成大规模的感染。

还有利用获取 cookie 的方式,将 cookie 传入入侵者的服务器上,入侵者就可以模拟 cookie 登录网站,对用户的信息进行篡改。

XSS 的预防

那么如何预防 XSS 攻击呢?—— 最根本的方式,就是对用户输入的内容进行验证和替换,需要替换的字符有:

& 替换为:&amp;
< 替换为:&lt;
> 替换为:&gt;
” 替换为:&quot;
‘ 替换为:&#x27;
/ 替换为:&#x2f;

替换了这些字符之后,黑客输入的攻击代码就会失效,XSS 攻击将不会轻易发生。

除此之外,还可以通过对 cookie 进行较强的控制,比如对敏感的 cookie 增加http-only限制,让 JS 获取不到 cookie 的内容。

SQL注入

todo

⭐ 描述一下 XSS 和 CSRF 攻击?防御方法?

  • XSS
    • 即为(Cross Site Scripting), 中文名为跨站脚本
    • 是发生在目标用户的浏览器层面上的,当渲染 DOM 树的过程成发生了不在预期内执行的 JS 代码时,就发生了 XSS 攻击。
    • 大多数 XSS 攻击的主要方式是嵌入一段远程或者第三方域上的 JS 代码。实际上是在目标网站的作用域下执行了这段 JS 代码。
  • CSRF(Cross Site Request Forgery,跨站请求伪造)
    • 字面理解意思就是在别的站点伪造了一个请求。专业术语来说就是在受害者访问一个网站时,其 Cookie 还没有过期的情况下,攻击者伪造一个链接地址发送受害者并欺骗让其点击,从而形成 CSRF 攻击。
  • 防御
    • XSS 防御的总体思路是: 对输入(和 URL 参数)进行过滤,对输出进行编码。也就是对提交的所有内容进行过滤,对 url 中的参数进行过滤,过滤掉会导致脚本执行的相关内容; 然后对动态输出到页面的内容进行 html 编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的 XSS 攻击。
    • 防御 CSRF 攻击主要有三种策略:
      • 不使用cookie
      • 验证 HTTP Referer 字段
      • 在请求地址中添加 token 并验证
      • 在 HTTP 头中自定义属性并验证

⭐ cookie 和 token 都存放在 header 中,为什么不会劫持 token?

  1. 攻击者通过 xss 拿到用户的 cookie 然后就可以伪造 cookie 了。
  2. 或者通过 csrf 在同个浏览器下面通过浏览器会自动带上 cookie 的特性,在通过用户网站-攻击者网站-攻击者请求用户网站的方式浏览器会自动带上 cookie

但是 token

  1. 不会被浏览器带上. 问题2 解决
  2. token 是放在 jwt 里面下发给客户端的而且不一定存储在哪里。不能通过 document.cookie 直接拿到,通过 jwt+ip 的方式可以防止被劫持即使被劫持也是无效的 jwt

介绍下 HTTPS 中间人攻击

针对 HTTPS 攻击主要有 SSL 劫持攻击和 SSL 剥离攻击两种。

SSL 劫持攻击是指攻击者劫持了客户端和服务器之间的连接,将服务器的合法证书替换为伪造的证书,从而获取客户端和服务器之间传递的信息。这种方式一般容易被用户发现,浏览器会明确的提示证书错误,但某些用户安全意识不强,可能会点击继续浏览,从而达到攻击目的。

SSL 剥离攻击是指攻击者劫持了客户端和服务器之间的连接,攻击者保持自己和服务器之间的 HTTPS 连接,但发送给客户端普通的 HTTP 连接,由于 HTTP 连接是明文传输的,即可获取客户端传输的所有明文数据。


https 协议由 http + ssl 协议构成,具体的链接过程可参考 SSL 或TLS 握手的概述 中间人攻击过程如下:

  1. 服务器向客户端发送公钥。
  2. 攻击者截获公钥,保留在自己手上。
  3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
  4. 客户端收到伪造的公钥后,生成加密 hash 值发给服务器。
  5. 攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥。
  6. 同时生成假的加密 hash 值,发给服务器。
  7. 服务器用私钥解密获得假秘钥。
  8. 服务器用加秘钥加密传输信息

防范方法:

服务端在发送浏览器的公钥中加入 CA 证书,浏览器可以验证 CA 证书的有效性

简单说说 HTTP 劫持、DNS 劫持与 XSS

  • http 劫持是指攻击者在客户端和服务器之间同时建立了连接通道,通过某种方式,让客户端请求发送到自己的服务器,然后自己就拥有了控制响应内容的能力,从而给客户端展示错误的信息,比如在页面中加入一些广告内容。
  • DNS 劫持是指攻击者劫持了 DNS 服务器,获得了修改 DNS 解析记录的权限,从而导致客户端请求的域名被解析到了错误的 IP 地址,攻击者通过这种方式窃取用户资料或破坏原有正常服务。
  • XSS 是指跨站脚本攻击。攻击者利用站点的漏洞,在表单提交时,在表单内容中加入一些恶意脚本,当其他正常用户浏览页面,而页面中刚好出现攻击者的恶意脚本时,脚本被执行,从而使得页面遭到破坏,或者用户信息被窃取。
    • 要防范 XSS 攻击,需要在服务器端过滤脚本代码,将一些危险的元素和属性去掉或对元素进行HTML实体编码。
最后更新于 2023-07-20