三余

三余 查看完整档案

填写现居城市  |  填写毕业院校「前端进阶之路」公众号  |  前端开发 编辑填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

三余 发布了文章 · 2月4日

十分钟教你用svg做出精美的动画!

前言

经常在Codepen上看到大侠们用SVG画出不可思议的动画,我一直很好奇他们是怎么运作的,总觉得这需要对SVG有足够透彻的了解,并且自己画出那些SVG图案,才有办法让他动起来。

但其实不然,今天教大家一个简单的小技巧,让你快速实现一个svg动画!

打开Codepen,点击界面中的build按钮,就可以使用动画构建一个房子,并且让它升起寥寥炊烟!🏭


codepen

寻找精美的svg图案

既然自己画不出来,那我们就去找现成的库,svg库有很多,如FlaticoniconfontIconfindericons8等网站会提供很多免费的svg图案。

分析svg图案

打开devtool观察 svg 图案,你会看到下面的结果:

element里头pathcircle都是svg的DOM元素,分别表示svg图案内的线条与圆形。

举个例子🌰:

<path d="M 10 25
         L 10 75
         L 60 75
         L 10 25">

上面代码中的d的内容:M代表将笔移动到(10, 25),接着L画一条线到(10, 75),最后回到起点画出一个三角形。

通过devtool,我们可以看到每个path对应图案的哪个部分:

这时候应该形成思路,既然我们可以知道每个元素对应到图案的哪个部分,我们就可以针对想要套上动画的DOM 元素来操作!

TimelineLite/TimelineMax 工具

如果单纯通过id、className 来使用 CSS 或JavaScript 自行处理动画,难度还是颇高,更重要的是,要耗费大量的时间

所以我们得借用工具,Timeline(Lite|Max)跟TweenMax是知名的GreenSock Animation Platform(简称GSAP)推出的可创建时间轴(timeline)作为动画或其他时间轴的容器,这使得整个动画控制和精确管理时间变得简单。

GSAP甚至为我们提供了Ease Visualizer来展示每种Ease function的效果,更顺带附上代码:


codepen

简单几句代码就能达到如下效果:

3.gif

上手GSAP

GSAP的API功能十分强大👍,还有相关社区:官网文档论坛TimelineMax中文手册

在一开始的房子构建🌰中,我主要使用的是TimelineMax的fromstaggerFrom,这两个API只需要设定初始值,他会在指定时间内将补间动画完成:

  tl.from("#House > rect:nth-child(24)", 1, {
      scaleX: 0,
      transformOrigin: "center",
      ease: Power2.easeOut
    })

这一步我们将CSS Selector #House > rect:nth-child(24) 这个元素,从scaleX为0开始,以center(中心)为变形起点,利用Power2.easeOut,在一秒内回复到原始状态,并执行补间动画。

    .staggerFrom(
      ["#House > path:nth-child(34)", "#House > path:nth-child(32)"],
      0.8,
      {
        scaleY: 0,
        transformOrigin: "bottom",
        ease: Bounce.easeOut,
        stagger: 0.2
      },
      0,
      "scene1+=0.5"
    )

from相似,只是staggerFrom可以一次放入多个CSS Selector,用stagger这个属性来设置数组中的Selector要以怎样的时间差出现。

详细API参数可以参考官方文档

接着回到我们的SVG,在devtool的帮助下,要取出svg内部元素的 CSS Selector 非常容易,在element面板中找到对应的DOM元素点击右键,选择 Copy -> Copy selector,就可以直接复制到该元素的CSS Selector:

现在我们能取得svg 中任意部分的CSS Selector,也知道怎么用GSAP API 来进行补间动画,现在是时候将其结合起来!

我们先调整下基本布局,一般在空白Html内直接放入svg时,图案大多会紧靠页面左上角,我们可以套用个margin: 0 auto将其置中,看起来会顺眼一些,你也能额外加些padding。我们在页面添加一个按钮来调用动画:

<!--html part-->
<button onclick="animateBike()"> Build! </button>
<!--css part-->
<style>
#Capa_1 {
  margin: 0 auto;
  display: block;
  width: 256px;
  height: 100%;
}
</style>

接着我们使用TimelineMax提供的staggerFrom函数,利用devtool将滑板车的轮子部分找出来,复制它们的CSS Selector,放入staggerFrom函数参数中,设定x与y轴的scale都从0开始,由center增长,采用Bounce.easeOut的ease function ,而四个Selector间以stagger: 0.2的属性值作为补间动画出现的时间差:

  const tl = new TimelineMax();
  tl.staggerFrom(
      [
        "#Capa_1 > g > path:nth-child(1)",
        "#Capa_1 > circle:nth-child(7)",
        "#Capa_1 > path:nth-child(6)",
        "#Capa_1 > circle:nth-child(5)"
      ],
      1,
      {
        scaleY: 0,
        scaleX: 0,
        transformOrigin: "center",
        ease: Bounce.easeOut,
        stagger: 0.2
      }
    )

简单几行代码,就能让我们的滑板车动起来!


codepen

补间是一个术语,用于描述逐帧序列,有时也称为"中间"。 在那个地方,一个动作导致下一个动作产生一个流畅的动作。 

完善动画

你可以把TimelineMax想像成时间轴,动画按指定顺序执行,而staggerFrom则可以同时让多个DOM元素以微小时间差的顺序启动,另外我们还可以设置一些Flag来指定要等到哪几个动画完成后,才接续其他动画。

最后,发挥自己的创意,使用各种API打出一套组合拳 👊:


codepen

结论🎉

看到这里,跃跃欲试了吗?

总之,我自己觉得蛮有趣的,希望或多或少对读到这篇文章的人有点帮助。

最后给大家分享一个很酷的demo,来自我的文章封面

参考文章 📜

GreenSock Animation Platform

How to Create Beautiful SVG Animations Easily

查看原文

赞 49 收藏 33 评论 1

三余 发布了文章 · 1月22日

零距离接触websocket🚀

什么是WebSocket

定义

Websocket是一个持久化的网络通信协议,可以在单个 TCP 连接上进行全双工通讯,没有了RequestResponse的概念,两者地位完全平等,连接一旦建立,客户端和服务端之间实时可以进行双向数据传输

关联和区别

  • HTTP
  1. HTTP是非持久的协议,客户端想知道服务端的处理进度只能通过不停地使用 Ajax进行轮询或者采用 long poll 的方式来,但是前者对服务器压力大,后者则会因为一直等待Response造成阻塞
  2. 虽然http1.1默认开启了keep-alive长连接保持了这个TCP通道使得在一个HTTP连接中,可以发送多个Request,接收多个Response,但是一个request只能有一个response。而且这个response也是被动的,不能主动发起。
  3. websocket虽然是独立于HTTP的一种协议,但是websocket必须依赖 HTTP 协议进行一次握手(在握手阶段是一样的),握手成功后,数据就直接从 TCP通道传输,与 HTTP 无关了,可以用一张图理解两者有交集,但是并不是全部。
  • socket
  1. socket也被称为套接字,与HTTP和WebSocket不一样,socket不是协议,它是在程序层面上对传输层协议(可以主要理解为TCP/IP)的接口封装。可以理解为一个能够提供端对端的通信的调用接口(API)
  2. 对于程序员而言,其需要在 A 端创建一个 socket 实例,并为这个实例提供其所要连接的 B 端的 IP 地址和端口号,而在 B 端创建另一个 socket 实例,并且绑定本地端口号来进行监听。当 A 和 B 建立连接后,双方就建立了一个端对端的 TCP 连接,从而可以进行双向通信。WebSocekt借鉴了 socket 的思想,为 client 和 server 之间提供了类似的双向通信机制

应用场景

WebSocket可以做弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育、聊天室等应用实时监听服务端变化

Websocket握手

  • Websocket握手请求报文:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
复制代码

下面是与传统 HTTP 报文不同的地方:

Upgrade: websocket
Connection: Upgrade
复制代码

表示发起的是 WebSocket 协议

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
复制代码

Sec-WebSocket-Key 是由浏览器随机生成的,验证是否可以进行Websocket通信,防止恶意或者无意的连接。

Sec_WebSocket-Protocol 是用户自定义的字符串,用来标识服务所需要的协议

Sec-WebSocket-Version 表示支持的 WebSocket 版本。

  • 服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
复制代码

101 响应码 表示要转换协议。

Connection: Upgrade 表示升级新协议请求。

Upgrade: websocket 表示升级为 WebSocket 协议。

Sec-WebSocket-Accept 是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。用来证明客户端和服务器之间能进行通信了。

Sec-WebSocket-Protocol 表示最终使用的协议。

至此,客户端和服务器握手成功建立了Websocket连接,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行通信了。

关于Websocket

WebSocket心跳

可能会有一些未知情况导致SOCKET断开,而客户端和服务端却不知道,需要客户端定时发送一个心跳 Ping 让服务端知道自己在线,而服务端也要回复一个心跳 Pong告诉客户端自己可用,否则视为断开

WebSocket状态

WebSocket 对象中的readyState属性有四种状态:

  • 0: 表示正在连接
  • 1: 表示连接成功,可以通信了
  • 2: 表示连接正在关闭
  • 3: 表示连接已经关闭,或者打开连接失败

WebSocket实践

服务端接收发送消息

WebSocket的服务端部分,本文会以Node.js搭建

安装express和负责处理WebSocket协议的ws

npm install express ws
复制代码

安装成功后的package.json:

接着在根目录创建server.js文件:

//引入express 和 ws
const express = require('express');
const SocketServer = require('ws').Server;
//指定开启的端口号
const PORT = 3000;
// 创建express,绑定监听3000端口,且设定开启后在consol中提示
const server = express().listen(PORT, () => console.log(`Listening on ${PORT}`));
// 将express交给SocketServer开启WebSocket的服务
const wss = new SocketServer({ server });
//当 WebSocket 从外部连接时执行
wss.on('connection', (ws) => {
  //连接时执行此 console 提示
  console.log('Client connected');
  // 对message设置监听,接收从客户端发送的消息
  ws.on('message', (data) => {
    //data为客户端发送的消息,将消息原封不动返回回去
    ws.send(data);
  });
  // 当WebSocket的连接关闭时执行
  ws.on('close', () => {
    console.log('Close connected');
  });
});
复制代码

执行node server.js启动服务,端口打开后会执行监听时间打印提示,说明服务启动成功

在开启WebSocket后,服务端会在message中监听,接收参数data捕获客户端发送的消息,然后使用send发送消息

客户端接收发送消息

分别在根目录创建index.html和index.js文件

  • index.html
<html>
  <body>
    <script data-original="./index.js"></script>
  </body>
</html>
复制代码
  • index.js
// 使用WebSocket的地址向服务端开启连接
let ws = new WebSocket('ws://localhost:3000');
// 开启后的动作,指定在连接后执行的事件
ws.onopen = () => {
  console.log('open connection');
};
// 接收服务端发送的消息
ws.onmessage = (event) => {
  console.log(event);
};
// 指定在关闭后执行的事件
ws.onclose = () => {
  console.log('close connection');
};
复制代码

上面的url就是本机node开启的服务地址,分别指定连接(onopen),关闭(onclose)和消息接收(onmessage)的执行事件,访问html,打印ws信息

打印了open connection说明连接成功,客户端会使用onmessage处理接收

其中event参数包含这次沟通的详细信息,从服务端回传的消息会在event的data属性中。

手动在控制台调用send发送消息,打印event回传信息:

服务端定时发送

上面是从客户端发送消息,服务端回传。我们也可以通过setInterval让服务端在固定时间发送消息给客户端:

server.js修改如下:

//当WebSocket从外部连接时执行
wss.on('connection', (ws) => {
  //连接时执行此 console 提示
  console.log('Client connected');
+  //固定发送最新消息给客户端
+  const sendNowTime = setInterval(() => {
+    ws.send(String(new Date()));
+  }, 1000);
-  //对message设置监听,接收从客户端发送的消息
-  ws.on('message', (data) => {
-    //data为客户端发送的消息,将消息原封不动返回回去
-    ws.send(data);
-  });
  //当 WebSocket的连接关闭时执行
  ws.on('close', () => {
    console.log('Close connected');
  });
});
复制代码

客户端连接后就会定时接收,直至我们关闭websocket服务

多人聊天

如果多个客户端连接按照上面的方式只会返回各自发送的消息,先注释服务端定时发送,开启两个窗口模拟:

如果我们要让客户端间消息共享,也同时接收到服务端回传的消息呢?

我们可以使用clients找出当前所有连接中的客户端 ,并通过回传消息发送到每一个客户端 中:

修改server.js如下:

...
//当WebSocket从外部连接时执行
wss.on('connection', (ws) => {
  //连接时执行此 console 提示
  console.log('Client connected');
-  //固定发送最新消息给客户端
-  const sendNowTime = setInterval(() => {
-    ws.send(String(new Date()));
- }, 1000);
+  //对message设置监听,接收从客户端发送的消息
+   ws.on('message', (data) => {
+    //取得所有连接中的 客户端
+    let clients = wss.clients;
+    //循环,发送消息至每个客户端
+    clients.forEach((client) => {
+      client.send(data);
+    });
+   });
  //当WebSocket的连接关闭时执行
  ws.on('close', () => {
    console.log('Close connected');
  });
});
复制代码

这样一来,不论在哪个客户端发送消息,服务端都能将消息回传到每个客户端 : 可以观察下连接信息:

总结 🥇

纸上得来终觉浅,绝知此事要躬行,希望大家可以把理论配合上面的实例进行消化,搭好服务端也可以直接使用测试工具好好玩耍一波

参考文章 📜

❤️ 阮一峰-WebSocket 教程

❤️ Using WebSockets on Heroku with Node.js

❤️ WebSocket 是什么原理?为什么可以实现持久连接?

扩展 🏆

如果你觉得本文对你有帮助,可以查看我的其他文章❤️:

👍 Web开发应了解的5种设计模式🍊

👍 10个简单的技巧让你的 vue.js 代码更优雅🍊

👍 Web开发应该知道的数据结构🍊

👍 经典面试题!从输入URL到页面展示你还不赶紧学起来?🍊

👍 浅谈SSL协议的握手过程🍊

查看原文

赞 27 收藏 19 评论 2

三余 发布了文章 · 1月13日

「多图预警」那些年,被blob虐过的程序猿觉醒了!

前言

本文以图文的方式深入浅出二进制的概念,前面的概念描述较为枯燥,但是非常重要!希望大家耐心往下看,后面有惊喜,定能让您虎躯一震~😉

Blob

Blob表示二进制类型的大对象,通常是影像、声音或多媒体文件,在 javaScript中Blob表示一个不可变、原始数据的类文件对象。

其构造函数如下:

new Blob(blobParts, options);
  • lobParts:数组类型,可以存放任意多个ArrayBuffer, ArrayBufferView, Blob或者DOMString(会编码为UTF-8),将它们连接起来构成Blob对象的数据。
  • options:可选项,用于设置blob对象的属性,可以指定如下两个属性:

    • type:存放到blob中数组内容的MIME类型(默认为"")。
    • endings:用于指定包含行结束符n的字符串如何被写入。值为native表示行结束符会被更改为适合宿主操作系统文件系统的换行符(默认值为transparent表示会保持blob中保存的结束符不变)
DOMString 是一个UTF-16字符串。由于JavaScript已经使用了这样的字符串,所以DOMString直接映射到 一个String。
ArrayBuffer(二进制数据缓冲区)、ArrayBufferView(二进制数据缓冲区的array-like视图)

示例如下👇

  1. 创建一个包含domstring对象的blob对象
    const blob = new Blob(['<div>john</div>'], { type: 'text/xml' });
    console.log(blob); // Blob {size: 15, type: "text/xml"}
  1. 创建一个包含arraybuffer对象的blob对象
    var abf = new ArrayBuffer(8);
    const blob = new Blob([abf], { type: 'text/plain' });
    console.log(blob); // Blob {size: 8, type: "text/plain"}
  1. 创建一个包含arraybufferview对象的blob对象
    var abf = new ArrayBuffer(8);
    var abv = new Int16Array(abf);
    const blob = new Blob(abv, { type: 'text/plain' });
    console.log(blob); // Blob {size: 4, type: "text/plain"}

属性

Blob对象有两个属性,参见下表👇:

属性名描述
sizeBlob对象中所包含数据的大小。字节为单位。 只读。
type一个字符串,表明该Blob对象所包含数据的MIME类型。如果类型未知,则该值为空字符串。 只读。

方法

  • slice(start:number, end:number, contentType:DOMString):类似于数组的slice方法,将原始Blob对象按照指定范围分割成新的blob对象并返回,可以用作切片上传

    • start:开始索引,默认为0
    • end:结束索引,默认为最后一个索引
    • contentType:新Blob的MIME类型,默认情况下为空字符串
  • stream():返回一个能读取blob内容的ReadableStream
  • text():返回一个Promise对象且包含blob所有内容的UTF-8格式的 USVString
  • arrayBuffer():返回一个Promise 对象且包含blob所有内容的二进制格式的ArrayBuffer

将blob(或者file)二进制文件保存到formData进行网络请求(之后可以获取到图片的imageUrl可以用作图片展示或者后续的通过websocket发送图片地址)

File

File对象是一种特殊的Blob对象,继承了所有Blob的属性和方法,当然同样也可以用作formData二进制文件上传

File的获取:

下面我们分别使用input和拖放方式选择多张图片操作👇:

  • input获取本地文件
  <input type="file" multiple id="f" />
  <script>
    var elem = document.getElementById('f');
    elem.onchange = function (event) {
      var files = event.target.files;
      console.log(files); // [{{name: "1.jpg",lastModified: 1594369580771...},{name:'2.jpg',lastModified: 1596012406708...}]
      var file = files[0];
      console.log(file); // {name: "1.jpg",lastModified: 1594369580771,size: 22344,type: "image/jpeg"...}
      console.log(file instanceof File); //true
      console.log(files instanceof FileList); // true
      
      /* File继承Blob */
      console.log(file.__proto__.__proto__); // Blob {size: 22344, type: ""}
    };
  </script>
  • 拖放获取

    <div id="content" ondrop="drop(event)" ondragover="allowDrop(event);" />
    <script>
      function allowDrop(ev) {
        ev.preventDefault();
      }
      function drop(ev) {
        ev.preventDefault();
        const files = ev.dataTransfer.files;
        console.log(files); // [{{name: "1.jpg",lastModified: 1594369580771...},{name:'2.jpg',lastModified: 1596012406708...}]
        console.log(files instanceof FileList); // true
      }
    </script>
    <style type="text/css">
      #content {
        width: 500px;
        height: 500px;
        border: 1px solid brown;
      }
    </style>
为input元素添加multiple属性,允许用户选择多个文件,用户选择的每一个文件都是一个file对象,而FileList对象则是这些file对象的列表,代表用户选择的所有文件,是file对象的集合。

属性

File对象属性,参见下表👇:

属性名描述
lastModified引用文件最后修改日期
name文件名或文件路径
size以字节为单位返回文件的大小
type文件的 MIME 类型

方法

File 对象没有自己的实例方法,由于继承了 Blob 对象,因此可以使用 Blob 的实例方法slice()。

数据缓冲区

XHRFile APICanvas等等各种地方,读取了一大串字节流,如果用JS里的Array去存,又浪费,又低效。在编程中,数据缓冲区(或简称为缓冲区)是物理内存中中操作二进制数据的存储区(比硬盘驱动器访问快),用于在数据从一个位置移动到另一位置时存储临时数据,解释器借助存储二进制数据的内存缓冲区读取行。主内存中有一个正在运行的文件,如果解释器必须返回文件以读取每个位,则执行过程将耗费大量时间。为了防止这种情况,JavaScript使用数据缓冲区,该缓冲区将一些位存储在一起,然后将所有位一起发送给解释器。这样,JavaScript解释器就不必担心从文件数据中检索文件。这种方法节省了执行时间并加快了应用程序的速度。各种缓冲区类对数据执行有效的二进制操作,包括FileBlobArrayBufferArray。选择的方法决定了内存中缓冲区的内部结构。

Buffer

BufferNode.js提供的对象,前端没有。 它一般应用于IO操作,例如接收前端请求数据时候,可以通过Buffer相关的API创建一个专门存放二进制数据的缓存区对接收到的前端数据进行整合,一个Buffer类似于一个整数数组,但它对应于V8堆内存之外的一块原始内存。

ArrayBuffer、ArrayBufferView

ArrayBuffer

ArrayBuffer表示固定长度的二进制数据的原始缓冲区,它的作用是分配一段可以存放数据的连续内存区域,因此对于高密度的访问(如音频数据)操作而言它比JS中的Array速度会快很多,ArrayBuffer存在的意义就是作为数据源提前写入在内存中,因此其长度固定

先大致看下ArrayBuffer的功能:

ArrayBuffer对象的构造函数如下(length表示ArrayBuffer的长度)👇:

ArrayBuffer(length);

Array和ArrayBuffer的区别👇:

ArrayArrayBuffer
可以放数字、字符串、布尔值以及对象和数组等只能存放0和1组成的二进制数据
数据放在堆中数据放在栈中,取数据时更快
可以自由增减只读,初始化后固定大小,无论缓冲区是否为空,只能借助TypedArrays、Dataview写入

属性

ArrayBuffer对象属性,参见下表👇:

属性名描述
byteLength表示ArrayBuffer的大小

方法

  • slice:有两个参数👉begin表示起始,end表示结束点。方法返回一个新的 ArrayBuffer ,它的内容是这个ArrayBuffer的字节副本,从begin(包括),到end(不包括)。

ArrayBuffer不能直接操作,而是要通过TypedArrayDataView对象来操作,它们会将缓冲区中的数据转换为各种数据类型的数组,并通过这些格式来读写缓冲区的内容。👇

ArrayBufferView

由于ArrayBuffer对象不提供任何直接读写内存的方法,而ArrayBufferView对象实际上是建立在ArrayBuffer对象基础上的视图,它指定了原始二进制数据的基本处理单元,通过ArrayBufferView对象来读取ArrayBuffer对象的内容。类型化数组(TypedArrays)和DataView是ArrayBufferView的实例。

TypedArrays

类型化数组(TypedArrays)是JavaScript中新出现的一个概念,专为访问原始的二进制数据而生,本质上,类型化数组和ArrayBuffer是一样的,只不过是他具备读写功能

类型数组的类型有:👇:

名称大小 (以字节为单位)说明
Int8Array18位有符号整数
Uint8Array18位无符号整数
Int16Array216位有符号整数
Uint16Array216位无符号整数
Int32Array432位有符号整数
Uint32Array432位无符号整数
Float32Array432位浮点数
Float64Array864位浮点数

类型转换如图👇:

举一些代码例子展示如何转换:

// 创建一个8字节的ArrayBuffer  
var b = new ArrayBuffer(8);  
  
// 创建一个指向b的视图v1,采用Int32类型,开始于默认的字节索引0,直到缓冲区的末尾  
var v1 = new Int32Array(b);  // Int32Array(2) [0, 0]
v1[0] = 1
console.log(v1); // Int32Array(2) [1, 0]
  
// 创建一个指向b的视图v2,采用Uint8类型,开始于字节索引2,直到缓冲区的末尾  
var v2 = new Uint8Array(b, 2);  // Uint8Array(6) [0, 0, 0, 0, 0, 0]
  
// 创建一个指向b的视图v3,采用Int16类型,开始于字节索引2,长度为2  
var v3 = new Int16Array(b, 2, 2);  // Int16Array(2) [0, 0]

因为普通Javascript数组使用的是Hash查找方式,而类型化数组直接访问固定内存,因此,速度很赞,比传统数组要快!同时,类型化数组天生处理二进制数据,这对于XMLHttpRequestcanvaswebGL等技术有着先天的优势。
TypedArray的应用如何拼接两个音频文件

fetch请求音频资源 -> ArrayBuffer -> TypedArray -> 拼接成一个 TypedArray -> ArrayBuffer -> Blob -> Object URL

DataView

DataView对象可以在ArrayBuffer中的任意位置读取和存储不同类型的二进制数据。

创建DataView的语法如下:

var dataView = new DataView(DataView(buffer, byteOffset[可选], byteLength[可选]);
属性

DataView对象有三个属性,参见下表👇:

属性名描述
buffer表示ArrayBuffer
byteOffset指缓冲区开始处的偏移量
byteLength指缓冲区部分的长度
方法
  • setint8():从DataView起始位置以byte为计数的指定偏移量(byteOffset)处存储一个8-bit数(一个字节)
  • getint8():从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个8-bit数(一个字节)

除此之外还有getInt16, getUint16, getInt32, getUint32... 使用方法一致,这里就不一一例举

用法如下👇:

let buffer = new ArrayBuffer(32);
let dataView = new DataView(buffer,0);
dataView.setInt16(1,56);
dataView.getInt16(1); // 56

FileReader

我们无法直接访问Blob或者文件对象的内容,如果想要读取它们并转化为其他格式的数据,可以借助FileReader对象的API进行操作
  • readAsText(Blob):将Blob转化为文本字符串
  • readAsArrayBuffer(Blob): 将Blob转为ArrayBuffer格式数据
  • readAsDataURL(): 将Blob转化为Base64格式的DataURL

使用分别如下👇:

    const blob = new Blob(['<xml>foo</xml>'], { type: 'text/xml' });
    console.log(blob); // Blob(14) {size: 14, type: "text/xml"}

    const reader = new FileReader();
    reader.onload = () => {
      console.log(reader.result);
    };
    reader.readAsText(blob); // <xml>foo</xml>
    reader.readAsArrayBuffer(blob); // ArrayBuffer(14) {}
    reader.readAsDataURL(blob); // data:text/xml;base64,PHhtbD5mb288L3htbD4

下面我们尝试把一个文件的内容通过字符串的方式读取出来:

<input type="file" id='f' />
<script>
  document.getElementById('f').addEventListener('change', function (e) {
    var file = this.files[0];
    // 首先,需要创建一个FileReader的实例。
    const reader = new FileReader();
    reader.onload = function () {
        // 在加载完成时回调
        const content = reader.result;
        console.log(content);
    }
    reader.readAsText(file); // 将blob转化为文本字符串读取
  }, false);
</script>

读取结果如下👇:

BlobURL

BlobURL(ObjectURL)是一种伪协议,只能由浏览器在内部生成,我们知道script/img/video/iframe等标签的src属性和background的url可以通过url和base64来显示,我们同样可以把blob或者file转换为url生成BlobURL来展示图像,BlobURL允许Blob和File对象用作图像,下载二进制数据链接等的URL源。

图像展示👇:

  <div id="content">
    <input type="file" multiple id="f" />
  </div>
  <script>
    const elem = document.getElementById('f');
    const content = document.getElementById('content');
    
    // 根据不同浏览器封装一个转换BlobUrl的方法:file可以是File对象也可以是Blob对象
    const getObjectURL = (file) => {
      let url;
      if (window.createObjectURL) {
        url = window.createObjectURL(file);
      } else if (window.URL) {
        url = window.URL.createObjectURL(file);
      } else if (window.webkitURL) {
        url = window.webkitURL.createObjectURL(file);
      }
      return url;
    };

    elem.onchange = function (event) {
      const files = event.target.files;
      const file = files[0];
      const img = document.createElement('img');
      img.src = getObjectURL(file);
      content.appendChild(img);
    };
  </script>

我们查看demo页面这个mm图片元素,会发现其URL地址既不是传统HTTP,也不是Base64 URL,而是blob:开头的字符串,可以通过将其放在地址栏中进行检查。

文件下载👇:

<body>
 <button onclick="download()">download.txt</button>

 <script>
      const getObjectURL = (file) => {
        let url;
        if (window.createObjectURL) {
          url = window.createObjectURL(file);
        } else if (window.URL) {
          url = window.URL.createObjectURL(file);
        } else if (window.webkitURL) {
          url = window.webkitURL.createObjectURL(file);
        }
        return url;
      };
      function download() {
        const fileName = 'download.txt';
        const myBlob = new Blob(['johnYu'], { type: 'text/plain' });
        downloadFun(fileName, myBlob);
      }
      function downloadFun(fileName, blob) {
        const link = document.createElement('a');
        link.href = getObjectURL(blob);
        link.download = fileName;
        link.click();
        link.remove();
        URL.revokeObjectURL(link.href);
      }
    </script>
  </body>

点击按钮下载文档,文档内容为:johnYu

这里不调用revokeObjectURL时访问chrome://blob-internals/可以看到当前内部的blob文件列表:

不再使用的BlobUrl后续会自动清除(关闭浏览器也会自动清除),但是最好使用URL.revokeObjectURL(url)手动清除它们:

URL.revokeObjectURL('blob:http://127.0.0.1:5500/d2a9a812-0dbf-41c5-a96b-b6384d33f281');

执行后再次访问chrome://blob-internals/可以看到文件已经被清除

dataURL

dataURL允许内容的创建者将较小的文件嵌入到文档中。与常规的URL使用场合类似

其语法格式格式如下👇:

data:[<mediatype>][;base64],data
  • data:前缀
  • mediatype表明数据类型,是一个MIME类型字符串,如image/jpeg表示一个JPEG图片文件。如果省略,默认值为text/plain;charset=US-ASCII
  • base64:标志位(如果是文本,则可选)
  • data:数据本身

如何获取DataUrl

  1. 上面示例中使用的方法readAsDataURL()就是将Blob转化为Base64格式的DataUrl;
  2. 使用原生Web API编码/解码
Javascript中有两个函数负责编码和解码base64字符串,分别是atob和btoa。两者都只针对Data URL中的data进行处理。
btoa('hello base64') // "PHhtbD5mb288L3htbD4="
atob('PHhtbD5mb288L3htbD4=') // "<xml>foo</xml>"
    • atob(): 负责解码已经使用base64编码了的字符串。
    • btoa(): 将二进制字符串转为base64编码的ASCII字符串。
    1. Canvas的toDataURL方法:
    Canvas提供了toDataURL方法,用于获取canvas绘制内容,将其转为base64格式。
      <body>
        <canvas id="canvas" width="200" height="50"></canvas>
        <textarea id="content" style="width: 200px; height: 200px"></textarea>
    
        <script>
          var canvas = document.getElementById('canvas');
          if (canvas.getContext) {
            var ctx = canvas.getContext('2d');
            // canvas的绘制
            ctx.font = 'Bold 20px Arial';
            ctx.textAlign = 'left';
            ctx.fillStyle = 'purple';
            ctx.fillText('johnYu', 10, 30);
            // 获取 Data URL
            document.getElementById('content').value = canvas.toDataURL();
          }
        </script>
      </body>

    如下图所示,文本框中的内容即为canvas中绘制内容的base64格式。

    如果我们将前面的返回结果data:text/xml;base64,PHhtbD5mb288L3htbD4=放在浏览器的地址栏中,则可以看到显示的内容。

    DataUrl的使用

    1. 由于可以将其用作URL的替代,因此DataURL和BlobUrl一样可以在script/img/video/iframe等标签的src属性和background的url中使用,用法与BlobUrl基本一致,只需要将前面的elem.onchange做如下改造
    <body>
        <div id="content">
          <input type="file" multiple id="f" />
        </div>
        <script>
          const elem = document.getElementById('f');
          const content = document.getElementById('content');
    
          elem.onchange = function (event) {
            const files = event.target.files;
            const file = files[0];
            const img = document.createElement('img');
    -        img.src = getObjectURL(file);
    +        const reader = new FileReader();
    +        reader.onload = function () {
    +          img.src = reader.result;
    +        };
    +        reader.readAsDataURL(file);
            content.appendChild(img);
          };
        </script>
      </body>
    1. 由于数据本身由URL表示,因此可以将其保存在Cookie中传递给服务器。
    2. 当图片的体积太小,占用一个HTTP会话不是很值得时。
    3. 当访问外部资源很麻烦或受限时
    4. DataUrl不会被浏览器缓存,但是小部分会通过css缓存,在下面例子中,DataUrl的使用是完全符合场景的。它避免了让这个小小的背景图片独自产生一次HTTP请求,而且,这个小图片还能同CSS文件一起被浏览器缓存起来,重复使 用,不会每次使用时都加载一次。只要这个图片不是很大,而且不是在CSS文件里反复使用,就可以DataUrl方法呈现图片降低页面的加载时间,改善用户的浏览体验。

       background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7"); 
    5. 作为下载连接使用
      <script>
        const createDownload = (fileName, content) => {
          const blob = new Blob([content]);
          const reader = new FileReader();
          const link = document.createElement('a');
          link.innerHTML = fileName;
          link.download = fileName;
          reader.onload = () => {
            link.href = reader.result;
            document.getElementsByTagName('body')[0].appendChild(link);
          };
          reader.readAsDataURL(blob);
        };
    
        createDownload('download.txt', 'johnYu');
      </script>

    点击a标签后后下载文本内容为johnYu的txt文件,在下面的BlobURL同样可以实现👇


    区别

    BlobURL基本用法与DataUrl相同,都可以通过将其放在地址栏中进行检查也可以用作普通URL使用。

    但是,存在以下差异。

    1. BlobUrl始终是唯一字符串,即时你每次传递相同的Blob,每次也会生成不同的BlobUrl;DataUrl值跟随blob变化;
    2. 就BlobUrl而言,它并不代表数据本身,数据存储在浏览器中,BlobUrl只是访问它的key。数据会一直有效,直到关闭浏览器或者手动清除。而DataUrl是直接编码的数据本身。因此即使将BlobUrl传递给服务器等也无法访问数据。关闭浏览器后仍然可以在地址栏访问后DataUrl,但是访问不到BlobUrl
    3. BlobUrl的长度一般比较短,但DataUrl因为直接存储图片base64编码后的数据,往往很长(Base64编码的数据体积通常会比二进制格式的图片体积大1/3。),因此当显式大图片时,使用BlobUrl能获取更好的可能性,速度和内存比DataUrl更有效
    4. BlobUrl可以方便的使用XMLHttpRequest获取源数据(xhr.responseType = 'blob')。对于DataUrl,并不是所有浏览器都支持通过XMLHttpRequest获取源数据的
      <body>
        <button onclick="download1()">XMLHttpRequest下载</button>
        <button onclick="download2()">fetch下载</button>
        <img id="img" />
        <script>
          var eleAppend = document.getElementById('forAppend');
          const url = 'https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/9ecb4e119c26e64b8b4ec5258f159b3b~300x300.image';
          const pingan = document.querySelector('#pingan');
          function download1() {
            const xhr = new XMLHttpRequest();
            xhr.open('get', url, true);
            xhr.responseType = 'blob';
            xhr.onload = function () {
              if (this.status == 200) {
                renderImage(this.response);
              }
            };
            xhr.send(null);
          }
          function download2() {
            fetch(url)
              .then((res) => {
                return res.blob();
              })
              .then((myBlob) => {
                renderImage(myBlob);
              });
          }
    
          function renderImage(blob) {
            window.URL = window.URL || window.webkitURL;
            var img = document.getElementById('img');
            img.onload = function (e) {
              window.URL.revokeObjectURL(img.src); // 清除释放
            };
            img.src = window.URL.createObjectURL(blob);
          }
        </script>
      </body>
    1. BlobUrl除了可以用作图片资源的网络地址,BlobUrl也可以用作其他资源的网络地址,例如html文件、json文件等,为了保证浏览器能正确的解析BlobUrl返回的文件类型,需要在创建Blob对象时指定相应的type
        const createDownload = (fileName, content) => {
          const blob = new Blob([content], { type: 'text/html' });
          const link = document.createElement('a');
          link.innerHTML = fileName;
          link.download = fileName;
          link.href = getObjectURL(blob);
          document.getElementsByTagName('body')[0].appendChild(link);
        };
        createDownload('download.html', '<button>foo</button>');

    1. DataUrl不会被浏览器缓存,这意味着每次访问这样页面时都被下载一次。这是一个使用效率方面的问题——尤其当这个图片被整个网站大量使用的时候。但是小部分可以通过css缓存

    canvas

    Canvas对象元素负责在页面中设定一个区域,然后就可以通过 JavaScript 动态地在这个区域中绘制图形。

    方法

    • toDataURL(type, encoderOptions)):以指定格式返回 DataUrl,该方法接收两个可选参数

      • type:表示图片格式,默认为 image/png
      • encoderOptions:表示图片的质量,在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。
    • toBlob(callback, type, encoderOptions):创造Blob对象, 用于展示canvas的图片,默认图片类型是image/png,分辨率是96dpi

      • callback: 参数是blob对象的回调函数
    • getImageData(x,y,width,height):返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。

      • x: 开始复制的左上角位置的 x 坐标。
      • y: 开始复制的左上角位置的 y 坐标。
      • width: 将要复制的矩形区域的宽度。
      • height: 将要复制的矩形区域的高度。
    • putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight):将图像数据(从指定的 ImageData 对象)放回画布上。

      • imgData: 规定要放回画布的 ImageData 对象。
      • x: ImageData 对象左上角的 x 坐标,以像素计。
      • y: ImageData 对象左上角的 y 坐标,以像素计。
      • dirtyX: 可选。水平值(x),以像素计,在画布上放置图像的位置。
      • dirtyY: 可选。水平值(y),以像素计,在画布上放置图像的位置。
      • dirtyWidth: 可选。在画布上绘制图像所使用的宽度。
      • dirtyHeight: 可选。在画布上绘制图像所使用的高度。

    应用场景

    当我们需要获取到canvas的内容,可以用到toDataURLtoBlob属性(可用于签名,图片剪裁,图片压缩等场景),putImageDatagetImageData可以用于图片灰度或者复制时使用(见后面的使用场景章节👇)

    获取内容:

    <body>
        <div id="content">
          <button onclick="drawnImg()">绘制图像</button>
          <button onclick="getImg()">获取图像</button>
          <canvas style="border: 1px solid black" id="drawing" width="200" height="200">A drawing of something.</canvas>
          <img data-original="./timg.jpg" alt="" />
        </div>
        <script>
          var drawing = document.getElementById('drawing');
          var quality = 0.3;
          const imgType = 'image/jpeg';
    
          var drawnImg = function () {
            if (drawing.getContext) {
              var context = drawing.getContext('2d');
              //取得图像的数据 URI
              var image = document.images[0];
              context.drawImage(image, 20, 20, 100, 100);
            }
          };
          var getImg = async function () {
            const content = getContent('base64');
            console.log(content);
            const content1 = await getContent('file');
            console.log(content1);
          };
          var getContent = function (type) {
            switch (type) {
              case 'base64':
                {
                  const imgURL = drawing.toDataURL(imgType, quality);
                  return imgURL;
                }
                break;
              case 'file':
                {
                  // 转为文件格式
                  return new Promise((resolve) => {
                    drawing.toBlob(
                      (blob) => {
                        resolve(blob);
                      },
                      imgType,
                      quality
                    );
                  });
                }
                break;
            }
          };
        </script>
      </body>

    关系及转换

    字符串 → Uint8Array

        var str = 'ab';
        console.log(Uint8Array.from(str.split(''), (e) => e.charCodeAt(0))); // Uint8Array(2) [97, 98]

    Uint8Array → 字符串

        var u8 = Uint8Array.of(97, 98);
        console.log(Array.from(u8, (e) => String.fromCharCode(e)).join('')); // ab

    字符串 → DataUrl

        var str = 'ab';
        console.log('data:application/octet-stream;base64,' + btoa(str)); // data:application/octet-stream;base64,YWI=

    DataUrl -> 字符串

        var data = 'data:application/octet-stream;base64,YWI=';
        console.log(atob(data.split(',')[1])); // ab

    Uint8Array -> ArrayBuffer

        var u8 = Uint8Array.of(1, 2);
        console.log(u8.buffer); // ArrayBuffer(2) {}

    ArrayBuffer -> Uint8Array

        var buffer = new ArrayBuffer(2);
        console.log(new Uint8Array(buffer)); // Uint8Array(2) [0, 0]

    ArrayBuffer -> DataView

        var buffer = new ArrayBuffer(2);
        var dataView = new DataView(buffer, 0); // DataView(2) {}

    DataView -> ArrayBuffer

        console.log(dataView.buffer); // ArrayBuffer(2) {}

    ArrayBuffer → Blob

        var buffer = new ArrayBuffer(32);
        var blob = new Blob([buffer]);  // Blob {size: 32, type: ""}

    UintXXArray → Blob

        var u8 = Uint8Array.of(97, 32, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33);
        var blob = new Blob([u8]);

    字符串 → Blob

        var blob = new Blob(['Hello World!'], {type: 'text/plain'}); // Blob {size: 12, type: "text/plain"}
    以上都是用new Blob()转blob

    DataUrl -> blob

        var data = 'data:application/octet-stream;base64,YWI=';
        function dataURLtoBlob(dataurl) {
          var arr = dataurl.split(','),
            mime = arr[0].match(/:(.*?);/)[1],
            bstr = atob(arr[1]),
            n = bstr.length,
            u8arr = new Uint8Array(n);
    
          while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
          }
          return new Blob([u8arr], { type: mime });
        }
        console.log(dataURLtoBlob(data)); // Blob {size: 2, type: "application/octet-stream"}

    Blob →

    需要用到FileReader的Api转换readAsText(Blob)、readAsArrayBuffer(Blob)、readAsDataURL(),但是需要异步执行
        var blob = new Blob(['a Hello world!'], { type: 'text/plain' });
        var reader = new FileReader();
        reader.readAsText(blob, 'utf-8');
        reader.onload = function (e) {
          console.info(reader.result); // a Hello world!
        };
        reader.onerror = function (e) {
          console.error(reader.error);
        };

    可以用promise做多次转换

        var blob = new Blob(['a Hello world!'], { type: 'text/plain' });
        function read(blob) {
          var fr = new FileReader();
          var pr = new Promise((resolve, reject) => {
            fr.onload = (eve) => {
              resolve(fr.result);
            };
            fr.onerror = (eve) => {
              reject(fr.error);
            };
          });
    
          return {
            arrayBuffer() {
              fr.readAsArrayBuffer(blob);
              return pr;
            },
            binaryString() {
              fr.readAsBinaryString(blob);
              return pr;
            },
            dataURL() {
              fr.readAsDataURL(blob);
              return pr;
            },
            text() {
              fr.readAsText(blob);
              return pr;
            },
          };
        }
        var pstr1 = read(blob).binaryString();
        var pstr2 = read(blob)
          .arrayBuffer()
          .then((e) => Array.from(new Uint8Array(e), (e) => String.fromCharCode(e)).join(''));
        Promise.all([pstr1, pstr2]).then((e) => {
          console.log(e[0]); // a Hello world!
          console.log(e[0] === e[1]); // true
        });![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/78f4c53fd471419fb10f3c803a4dd94a~tplv-k3u1fbpfcp-watermark.image)

    应用场景

    图像灰度化

    这里主要用到canvasimageData的转换
    <body>
        <button onclick="drawngray()">黑白图片</button>
        <img data-original="./syz.jpg" alt="" />
        <canvas id="myCanvas">canvas</canvas>
        <script>
          var drawngray = function () {
            var myCanvas = document.getElementById('myCanvas');
            if (myCanvas.getContext) {
              var context = myCanvas.getContext('2d');
              var image = document.images[0];
              // 动态设置canvas的大小
              myCanvas.width = image.width;
              myCanvas.height = image.height;
              var imageData, data, i, len, average, red, green, blue, alpha;
              //绘制原始图像
              context.drawImage(image, 0, 0);
              //取得图像数据
              imageData = context.getImageData(0, 0, image.width, image.height);
              data = imageData.data;
              for (i = 0, len = data.length; i < len; i += 4) {
                red = data[i];
                green = data[i + 1];
                blue = data[i + 2];
                // alpha = data[i + 3];
                //求得 rgb 平均值
                average = Math.floor((red + green + blue) / 3);
                //设置颜色值,透明度不变
                data[i] = average;
                data[i + 1] = average;
                data[i + 2] = average;
              }
    
              //回写图像数据并显示结果
              imageData.data = data;
              context.putImageData(imageData, 0, 0);
            }
          };
        </script>
      </body>

    除次之外getImageData和putImageData还可以用作cavas图片复制:https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_getimagedata

    图片压缩

    在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL() 方法

    compress.js

    const MAX_WIDTH = 800; // 图片最大宽度
    
    function compress(base64, quality, mimeType) {
      let canvas = document.createElement('canvas');
      let img = document.createElement('img');
      img.crossOrigin = 'anonymous';
      return new Promise((resolve, reject) => {
        img.src = base64;
        img.onload = () => {
          let targetWidth, targetHeight;
          if (img.width > MAX_WIDTH) {
            targetWidth = MAX_WIDTH;
            targetHeight = (img.height * MAX_WIDTH) / img.width;
          } else {
            targetWidth = img.width;
            targetHeight = img.height;
          }
          canvas.width = targetWidth;
          canvas.height = targetHeight;
          let ctx = canvas.getContext('2d');
          ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
          // 通过toDataURL压缩后的base64
          let imageData = canvas.toDataURL(mimeType, quality / 100);
          resolve(imageData);
        };
      });
    }

    test.html

      <body>
        <input type="file" accept="image/*" onchange="loadFile(event)" />
        <script data-original="./compress.js"></script>
        <script>
          function dataUrlToBlob(base64) {
            var arr = base64.split(','),
              mime = arr[0].match(/:(.*?);/)[1],
              bstr = atob(arr[1]),
              n = bstr.length,
              u8arr = new Uint8Array(n);
    
            while (n--) {
              u8arr[n] = bstr.charCodeAt(n);
            }
            return new Blob([u8arr], { type: mime });
          }
    
          function uploadFile(url, blob) {
            let formData = new FormData();
            let request = new XMLHttpRequest();
            // 封装到FormData中进行文件的上传
            formData.append('image', blob);
            request.open('POST', url, true);
            request.send(formData);
          }
    
          const loadFile = function (event) {
            const reader = new FileReader();
            reader.onload = async function () {
              let compressedDataURL = await compress(reader.result, 90, 'image/jpeg');
              // 压缩后将base64转为Blob 对象减少传输数据量
              let compressedImageBlob = dataUrlToBlob(compressedDataURL);
              uploadFile('https://httpbin.org/post', compressedImageBlob);
            };
            // 获取用户选取的图片文件,通过FileReader转化成base64
            reader.readAsDataURL(event.target.files[0]);
          };
        </script>
      </body>

    分片上传

    <body>
        <input type="file" name="file" onchange="selfile();" />
    
        <script>
          const url = 'https://httpbin.org/post';
          /**
           * @param file 原始文件
           * @param chunkSize 默认每次上传分片大小
           */
          async function chunkedUpload(file, chunkSize = 1024 * 1024 * 5) {
            // 将文件拆分成chunkSize大小的分块,然后每次请求只需要上传这一个部分的分块即可
            for (let start = 0; start < file.size; start += chunkSize) {
              // File对象继承自Blob对象,因此可以使用slice方法对大文件进行切
              const chunk = file.slice(start, start + chunkSize + 1);
              const fd = new FormData();
              fd.append('data', chunk);
    
              await fetch(url, { method: 'post', body: fd })
                .then((res) => res.text())
                .then((res) => console.log(res)); // 打印上传结果
            }
          }
    
          function selfile() {
            let file = document.querySelector('[name=file]').files[0];
    
            // 自定义分片大小
            const LENGTH = 1024 * 1024 * 1;
            chunkedUpload(file, LENGTH);
          }
        </script>
      </body>

    服务器接收到这些切片后,再将他们拼接起来就可以了,下面是PHP拼接切片的示例代码:

    $filename = './upload/' . $_POST['filename'];//确定上传的文件名
    //第一次上传时没有文件,就创建文件,此后上传只需要把数据追加到此文件中
    if(!file_exists($filename)){
        move_uploaded_file($_FILES['file']['tmp_name'],$filename);
    }else{
        file_put_contents($filename,file_get_contents($_FILES['file']['tmp_name']),FILE_APPEND);
        echo $filename;
    }

    测试时记得修改nginx的server配置,否则大文件可能会提示413 Request Entity Too Large的错误。

    server {
        // ...
        client_max_body_size 50m;
    }

    参考文章 📜

    ❤️ 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型

    ❤️ 聊聊JS的二进制家族:Blob、ArrayBuffer和Buffer

    ❤️ 你不知道的 Blob

    扩展 🏆

    如果你觉得本文对你有帮助,可以查看我的其他文章❤️:

    👍 vue3实战笔记 | 快速入门🚀

    👍 10个简单的技巧让你的 vue.js 代码更优雅🍊

    👍 零距离接触websocket🚀

    👍 Web开发应了解的5种设计模式

    👍 Web开发应该知道的数据结构

    👍 如何在JavaScript中获取屏幕,窗口和网页大小

    查看原文

    赞 0 收藏 0 评论 0

    三余 发布了文章 · 2020-12-31

    vue3实战笔记 | 快速入门🚀

    前言

    vue3正式版已经发布好几个月了。相信有不少人早已跃跃欲试,这里根据这几天的项目经验罗列几点在项目中可能用到的知识点跟大家分享总结,在展开功能点介绍之前,先从一个简单的demo帮助大家可以快速入手新项目🌉

    案例🌰

    在正式介绍之前,大家可以先跑一下这个 demo 快速熟悉用法
    <template>
      <div>
        <el-button type="primary" @click="handleClick">{{
          `${vTitle}${state.nums}-${staticData}`
        }}</el-button>
        <ul>
          <li v-for="(item, index) in state.list" :key="index"> {{ item }} </li>
        </ul>
      </div>
    </template>
    
    <script lang="ts">
      import { defineComponent, reactive, ref, watch, onMounted, computed, nextTick } from 'vue';
      interface State {
        nums: number;
        list: string[];
      }
    
      export default {
        setup() {
          const staticData = 'static';
          let title = ref('Create');
          const state = reactive<State>({
            nums: 0,
            list: [],
          });
    
          watch(
            () => state.list.length,
            (v = 0) => {
              state.nums = v;
            },
            { immediate: true }
          );
          const vTitle = computed(() => '-' + title.value + '-');
    
          function handleClick() {
            if (title.value === 'Create') {
              title.value = 'Reset';
              state.list.push('小黑');
            } else {
              title.value = 'Create';
              state.list.length = 2;
            }
          }
    
          const getList = () => {
            setTimeout(() => {
              state.list = ['小黄', '小红'];
            }, 2000);
            nextTick(() => {
              console.log('nextTick');
            });
          };
    
          onMounted(() => {
            getList();
          });
    
          return {
            staticData,
            state,
            handleClick,
            title,
            vTitle,
          };
        },
      };
    </script>

    效果如下👇

    vue3生命周期

    vue3的生命周期函数只能用在setup()里使用,变化如下👇
    vue2vue3
    beforeCreatesetup
    createdsetup
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeDestroyonBeforeUnmount
    destroyedonUnmounted
    activatedonActivated
    deactivatedonDeactivated

    扩展

    1. 可以看出来vue2的beforeCreatecreated变成了setup
    2. 绝大部分生命周期都是在原本vue2的生命周期上带上了on前缀

    使用

    在setup中使用生命周期:

    import {  onMounted } from 'vue';
    
    export default {
      setup() {
        onMounted(() => {
          // 在挂载后请求数据
          getList();
        })
      }
    };

    vue3常用api

    上述案例中使用了一些常用的api,下面带大家一一认识下我们的新朋友

    setup()

    setup函数是一个新的组件选项。作为在组件内使用Composition API的入口点。从生命周期钩子的视角来看,它会在beforeCreate钩子之前被调用,所有变量、方法都在setup函数中定义,之后return出去供外部使用

    该函数有2个参数:

    • props
    • context

    其中context是一个上下文对象,具有属性(attrsslotsemitparentroot),其对应于vue2中的this.$attrsthis.$slotsthis.$emitthis.$parentthis.$root

    setup也用作在tsx中返回渲染函数:

      setup(props, { attrs, slots }) {
        return () => {
          const propsData = { ...attrs, ...props } as any;
          return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
        };
      },
    *注意:this关键字在setup()函数内部不可用,在方法中访问setup中的变量时,直接访问变量名就可以使用。

    扩展

    为什么props没有被包含在上下文中?

    1. 组件使用props的场景更多,有时甚至只需要使用props
    2. 将props独立出来作为一个参数,可以让TypeScript对props单独做类型推导,不会和上下文中其他属性混淆。这也使得setup、render和其他使用了TSX的函数式组件的签名保持一致。

    reactive, ref

    reactiveref都是vue3中用来创建响应式数据的api,作用等同于在vue2中的data,不同的是他们使用了ES6Porxy API解决了vue2 defineProperty 无法监听数组和对象新增属性的痛点

    用法

    <template>
      <div class="contain">
        <el-button type="primary" @click="numadd">add</el-button>
        <span>{{ `${state.str}-${num}` }}</span>
      </div>
    </template>
    
    <script lang="ts">
      import { reactive, ref } from 'vue';
      interface State {
        str: string;
        list: string[];
      }
    
      export default {
        setup() {
          const state = reactive<State>({
            str: 'test',
            list: [],
          });
          //ref需要加上value才能获取
          const num = ref(1);
          const numadd = () => {
            num.value++;
          };
          return { state, numadd, num };
        },
      };
    </script>

    效果如下👇

    区别

    • 使用时在setup函数中需要通过内部属性.value来访问ref数据,return出去的ref可直接访问,因为在返回时已经自动解套;reactive可以直接通过创建对象访问
    • ref接受一个参数,返回响应式ref对象,一般是基本类型值(StringNmuberBoolean 等)或单值对象。如果传入的参数是一个对象,将会调用 reactive 方法进行深层响应转换(此时访问ref中的对象会返回Proxy对象,说明是通过reactive创建的);引用类型值(ObjectArray)使用reactive

    toRefs

    将传入的对象里所有的属性的值都转化为响应式数据对象(ref)

    使用reactive return 出去的值每个都需要通过reactive对象 .属性的方式访问非常麻烦,我们可以通过解构赋值的方式范围,但是直接解构的参数不具备响应式,此时可以使用到这个api(也可以对props中的响应式数据做此处理)

    将前面的例子作如下👇修改使用起来更加方便:

    <template>
      <div class="contain">
        <el-button type="primary" @click="numadd">add</el-button>
    -    <span>{{ `${state.str}-${num}` }}</span>
    +    <span>{{ `${str}-${num}` }}</span>
      </div>
    </template>
    
    <script lang="ts">
      import { reactive, ref, toRefs } from 'vue';
      interface State {
        str: string;
        list: string[];
      }
    
      export default {
        setup() {
          const state = reactive<State>({
            str: 'test',
            list: [],
          });
          //ref需要加上value才能获取
          const num = ref(1);
          const numadd = () => {
            num.value++;
          };
    -      return { state, numadd, num };
    +      return { ...toRefs(state), numadd, num };
        },
      };
    </script>

    toRef

    toRef 用来将引用数据类型或者reavtive数据类型中的某个值转化为响应式数据

    用法

    • reactive数据类型
         /* reactive数据类型 */
          let obj = reactive({ name: '小黄', sex: '1' });
          let state = toRef(obj, 'name');
    
          state.value = '小红';
          console.log(obj.name); // 小红
          console.log(state.value); // 小红
    
          obj.name = '小黑';
          console.log(obj.name); // 小黑
          console.log(state.value); // 小黑
    • 引用数据类型
    <template>
      <span>ref----------{{ state1 }}</span>
      <el-button type="primary" @click="handleClick1">change</el-button>
      <!-- 点击后变成小红 -->
      <span>toRef----------{{ state2 }}</span>
      <el-button type="primary" @click="handleClick2">change</el-button>
      <!-- 点击后还是小黄 -->
    </template>
    
    <script>
      import { ref, toRef, reactive } from 'vue';
      export default {
        setup() {
          let obj = { name: '小黄' };
          const state1 = ref(obj.name); // 通过ref转换
          const state2 = toRef(obj, 'name'); // 通过toRef转换
          
          const handleClick1 = () => {
            state1.value = '小红';
            console.log('obj:', obj); // obj:小黄
            console.log('ref', state1); // ref:小红
          };
          
          const handleClick2 = () => {
            state2.value = '小红';
            console.log('obj:', obj); // obj:小红
            console.log('toRef', state2); // toRef:小红
          };
          return { state1, state2, handleClick1, handleClick2 };
        },
      };
    </script>

    小结

    • ref 是对原数据的拷贝,响应式数据对象值改变后会同步更新视图,不会影响到原始值。
    • toRef 是对原数据的引用,响应式数据对象值改变后不会改变视图,会影响到原始值。

    isRef

    判断是否是ref对象,内部是判断数据对象上是否包含__v_isRef属性且值为true。
        setup() {
          const one = ref(0);
          const two = 0;
          const third = reactive({
            data: '',
          });
          let four = toRef(third, 'data');
          const { data } = toRefs(third);
          
          console.log(isRef(one)); // true
          console.log(isRef(data)); // true
          console.log(isRef(four)); // true
          console.log(isRef(two)); // false
          console.log(isRef(third)); // false
        }

    unref

    如果参数为ref,则返回内部原始值,否则返回参数本身。内部是val = isRef(val) ? val.value : val的语法糖。
        setup() {
          const hello = ref('hello');
          console.log(hello); // { __v_isRef: true,value: "hello"... }
          const newHello = unref(hello);
          console.log(newHello); // hello
        }

    watch, watchEffect

    watch

    watch侦听器,监听数据变化

    用法和vue2有些区别

    语法为:watch(source, callback, options)

    • source:用于指定监听的依赖对象,可以是表达式,getter函数或者包含上述两种类型的数组(如果要监听多个值)
    • callback:依赖对象变化后执行的回调函数,带有2个参数:newValoldVal。如果要监听多个数据每个参数可以是数组 [newVal1, newVal2, ... newValN][oldVal1, oldVal2, ... oldValN]
    • options:可选参数,用于配置watch的类型,可以配置的属性有 immediate(立即触发回调函数)、deep(深度监听)
          let title = ref('Create');
          let num = ref(0);
          const state = reactive<State>({
            nums: 0,
            list: [],
          });
          
          // 监听ref
          watch(title, (newValue, oldValue) => {
             /* ... */
          });
    
          // 监听reactive
          watch(
            // getter
            () => state.list.length,
            // callback
            (v = 0) => {
              state.nums = v;
            },
             // watch Options
            { immediate: true }
          );
          
          // 监听多个ref
          watch([title, num], ([newTitle, newNum], [oldTitle, oldNum]) => {
            /* ... */
          });      
          
          // 监听reactive多个值
          watch([() => state.list, () => state.nums], ([newList, newNums], [oldList, oldvNums]) => {
            /* ... */
          });
    我们可以向上面一样将多个值的监听拆成多个对单个值监听的watch。这有助于我们组织代码并创建具有不同选项的观察者;watch方法会返回一个stop()方法,若想要停止监听,便可直接执行该stop函数
    watchEffect
    立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更是重新运行该函数.
    <template>
      <div class="contain">
        <el-button type="primary" @click="numadd">add</el-button>
        <span>{{ num }}</span>
      </div>
    </template>
    
    <script lang="ts">
      import { ref, watchEffect } from 'vue';
      export default {
        setup() {
          const num = ref(1);
          const numadd = () => {
            num.value++;
          };
    
          watchEffect(() => {
            console.log(num.value); // 1,2,3...
          });
          return { numadd, num };
        },
      };
    </script>

    可以看到在组件初始化的时候该回调函数立即执行了一次,同时开始自动检测回调函数里头依赖的值,并在依赖关系发生改变时自动触发这个回调函数,这样我们就不必手动传入依赖特意去监听某个值了

    computed

    传入一个getter函数,返回一个默认不可手动修改的ref对象.
        setup() {
          let title = ref('Create');
          const vTitle = computed(() => '-' + title.value + '-');
          
          function handleClick() {
            if (title.value === 'Create') {
              title.value = 'Reset';
            } else {
              title.value = 'Create';
            }
          }
          }

    反转字符串:

    setup() {
        const state = reactive({
          value: '',
          rvalue: computed(() =>
            state.value
              .split('')
              .reverse()
              .join('')
          )
        })
        return toRefs(state)
      }

    provide, inject

    provide()inject()用来实现多级嵌套组件之间的数据传递,父组件或祖先组件使用 provide()向下传递数据,子组件或子孙组件使用inject()来接收数据
    // 父组件
    <script>
    import {provide} from 'vue'
    export default {
        setup() {
            const obj = ref('johnYu')
            // 向子孙组件传递数据provide(名称,数据)
            provide('name', obj)
        }
    }
    </script>
    
    // 孙组件
    <script>
    import {inject} from 'vue'
    export default {
        setup() {    
            // 接收父组件传递过来的数据inject(名称)
            const name = inject('name') // johnYu
            return {name}
        }
    }
    </script>

    getCurrentInstance

    getCurrentInstance方法用于获取当前组件实例,仅在setup和生命周期中起作用
    import { getCurrentInstance, onBeforeUnmount } from 'vue';
    
    const instance = getCurrentInstance();
    // 判断当前组件实例是否存在
    if (instance) {
        onBeforeUnmount(() => {
            /* ... */
         });
     }
    通过instance中的ctx属性可以获得当前上下文,通过这个属性可以使用组件实例中的各种全局变量和属性

    $Refs

    为了获得对模板中元素或组件实例的引用,我们可以同样使用ref并从setup()返回它
    <template>
      <div ref="root">This is a root element</div>
    </template>
    
    <script>
      import { ref, onMounted } from 'vue'
    
      export default {
        setup() {
            // 获取渲染上下文的引用
          const root = ref(null)
    
          onMounted(() => {
            // 仅在初次渲染后才能获得目标元素
            console.log(root.value) // <div>This is a root element</div>
          })
    
          return {
            root
          }
        }
      }
    </script>

    .sync

    在vue2.0中使用.sync实现prop的双向数据绑定,在vue3中将它合并到了v-model

    vue2.0

        <el-pagination
          :current-page.sync="currentPage1"
        >
        </el-pagination>

    vue3.0

        <el-pagination
          v-model:current-page="currentPage1"
        >
        </el-pagination>

    v-slot

    Child.vue

    <template>
      <div class="child">
        <h3>具名插槽</h3>
        <slot name="one" />
        <h3>作用域插槽</h3>
        <slot :data="list" />
        <h3>具名作用域插槽</h3>
        <slot name="two" :data="list" />
      </div>
    </template>
    
    <script>
    export default {
      data: function() {
        return {
          list: ['zhangsan', 'lisi']
        }
      }
    }
    </script>

    vue2用法

    <template>
      <div>
        <child>
          <div slot="one">
            <span>菜单</span>
          </div>
          <div slot-scope="user">
            <ul>
              <li v-for="(item, index) in user.data" :key="index">{{ item }}</li>
            </ul>
          </div>
          <div slot="two" slot-scope="user">
            <div>{{ user.data }}</div>
          </div>
        </child>
      </div>
    </template>

    vue3用法

    新指令v-slot统一slotslot-scope单一指令语法。速记v-slot可以潜在地统一作用域和普通插槽的用法。
    <template>
      <div>
        <child>
          <template v-slot:one>
            <div><span>菜单</span></div>
          </template>
          <template v-slot="user">
            <ul>
              <li v-for="(item, index) in user.data" :key="index">{{ item }}</li>
            </ul>
          </template>
          <template v-slot:two="user">
            <div>{{ user.data }}</div>
          </template>
          <!-- 简写 -->
          <template #two="user">
          <div>{{ user.data }}</div>
          </template>
        </child>
      </div>
    </template>

    Composition API 结合vuex4, Vue Router 4

    createStore,useStore,useRouter,useRoute

    vuex4中通过createStore创建Vuex实例,useStore可以获取实例,作用等同于vue2.0中的this.$store;

    Vue Router 4 useRouter可以获取路由器,用来进行路由的跳转,作用等同于vue2.0的this.$router,useRoute就是钩子函数相当于vue2.0的this.$route

    store/index.ts

    import {createStore} from 'vuex';
    const store = createStore({
      state: {
        user: null,
      },
      mutations: {
        setUser(state, user) {
          state.user = user;
        }
      },
      actions: {},
      modules: {}
    });

    router/index.ts

    import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
    import { scrollBehavior } from './scrollBehaviour.ts';
    
    const routes: Array<RouteRecordRaw> = [
      {
        path: '/',
        name: 'Home',
        component: () => import('/@/views/home.vue') // vite.config.vue中配置alias
      }
    ];
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
      strict: true,
      scrollBehavior: scrollBehavior,
    });
    
    export default router;

    main.ts

    import { createApp } from 'vue';
    import App from './App.vue';
    import router from './router';
    import store from './store';
    import { getTime } from '/@/utils'
    
    const app = createApp(App);
    app.config.globalProperties.$getTime = getTime // vue3配置全局变量,取代vue2的Vue.prototype
    app.use(store).use(router)
    app.mount('#app');

    App.vue

    import { reactive } from "vue";
    import { useRouter } from "vue-router";
    import { useStore } from "vuex";
    import { ElMessage } from 'element-plus';
    export default {
      name: "App",
      setup() {
        const store = useStore();
        const router = useRouter();
        // 用户名和密码
        const Form = reactive({
          username: "johnYu",
          password: "123456",
        });
        // 登录
        function handelLogin() {
          store.commit("setUser", {
            username: Form.username,
            password: Form.password,
          });
          ElMessage({
            type: 'success',
            message: '登陆成功',
            duration: 1500,
          });
          // 跳转到首页
          router.push({
             name: 'Home',
             params: {
               username: Form.username
             },
          });
        }
        return {
          Form,
          handelLogin
          };
      }

    home.vue

      import { useRouter, useRoute } from 'vue-router';
      import Breadcrumb from '/@/components/Breadcrumb.vue';
    
      export default defineComponent({
        name: 'Home',
        components: {
          Breadcrumb,
        },
        setup() {
          const route = useRoute();
          // 接收参数
          const username = route.params.username;
          return {username}
        }
        })

    导航守卫

    由于使用 Composition API 的原因,setup函数里面分别使用onBeforeRouteLeaveonBeforeRouteUpdate 两个新增的 API 代替vue2.0中的beforeRouteLeavebeforeRouteUpdate
      import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
       setup() {
          onBeforeRouteUpdate((to) => {
            if (to.name === 'Home'){
                /* ... */
            }
          });
       }

    useLink

    useLink它提供与router-linkv-slot API 相同的访问权限,将RouterLink的内部行为公开为Composition API函数,用于暴露底层的定制能力
    <template>
      <div ref="root">This is a root element</div>
    </template>
    
    <script>
      import { computed } from 'vue';
      import { RouterLink, useLink } from 'vue-router';
    
      export default {
        name: 'AppLink',
    
        props: {
          ...RouterLink.props,
          inactiveClass: String,
        },
    
        setup(props) {
          const { route, href, isActive, isExactActive, navigate } = useLink(props);
          const isExternalLink = computed(
            () => typeof props.to === 'string' && props.to.startsWith('http')
          );
    
          return { isExternalLink, href, navigate, isActive };
        },
      };
    </script>

    插槽 prop 的对象包含下面几个属性:

    1. href:解析后的 URL。将会作为一个 a 元素的 href attribute。
    2. route:解析后的规范化的地址。
    3. navigate:触发导航的函数。会在必要时自动阻止事件,和 router-link 同理。
    4. isActive:如果需要应用激活的 class 则为 true。允许应用一个任意的 class。
    5. isExactActive:如果需要应用精确激活的 class 则为 true。允许应用一个任意的 class。

    扩展

    样式 scoped

    vue2

    /* 深度选择器 */
    /*方式一:*/
    >>> .foo{ }
    /*方式二:*/
    /deep/ .foo{ }
    /*方式三*/
    ::v-deep .foo{ }

    vue3

    /* 深度选择器 */
    ::v-deep(.foo) {}

    .env环境扩展

    vite中的.env文件变量名一定要以VITE_前缀

    .env文件

    VITE_USE_MOCK = true

    使用:

    import.meta.env.VITE_APP_CONTEXT

    使用Composition API替换mixin

    众所周知使用mixin的时候当我们一个组件混入大量不同的mixin的时候,会存在两个非常明显的问题:命名冲突和数据来源不清晰。
    • 每个mixin都可以定义自己的propsdata,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突。
    • 另外对组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰。

    以这个经典的Vue 2组件为例,它定义了一个"计数器"功能:

    //counter.js
    export default {
      data() {
        return {
          count: 0
        };
      },
      methods: {
        increment() {
          this.count++;
        }
      }
    }

    用法如下:

    <template>
      <div>
        {{ count }}
        <el-button @click="increment()">add</el-button>
      </div>
    </template>
    
    <script>
    import counter from './mixins/counter'
    import getTime from './mixins/getTime'
    
    export default {
      mixins: [counter,getTime]
    }
    </script>

    假设这边我们引用了counter和getTime两个mixin,则无法确认count和increment()方法来源,并且两个mixin中可能会出现重复命名的概率

    下面是使用Composition API定义的完全相同的组件:

    // counter.ts
    import { ref } from 'vue';
    
    export default function () {
        const count = ref(0);
        function increment() {
            count.value++;
        }
        return { count, increment };
    }
    
    <template>
      <div>
        {{ count }}
        <el-button @click="increment()">add</el-button>
      </div>
    </template>
    
    <script lang="ts">
      import { defineComponent } from 'vue';
      import counter from '/@/composables/counter';
    
      export default defineComponent({
        setup() {
          const { count, increment } = counter();
          return {
            count,
            increment,
          };
        },
      });
    </script>

    总结

    使用Composition API可以清晰的看到数据来源,即使去编写更多的hook函数,也不会出现命名冲突的问题。🚄

    Composition API 除了在逻辑复用方面有优势,也会有更好的类型支持,因为它们都是一些函数,在调用函数时,自然所有的类型就被推导出来了,不像 Options API 所有的东西使用 this。另外,Composition API 对 tree-shaking 友好,代码也更容易压缩。vue3的Composition API会将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

    参考文章 📜

    ❤️ 快速使用Vue3最新的15个常用API

    ❤️ vue 3.x 如何有惊无险地快速入门

    扩展 🏆

    如果你觉得本文对你有帮助,可以查看我的其他文章❤️:

    👍 10个简单的技巧让你的 vue.js 代码更优雅🍊

    👍 零距离接触websocket🚀

    👍 Web开发应了解的5种设计模式

    👍 Web开发应该知道的数据结构

    查看原文

    赞 35 收藏 25 评论 0

    三余 发布了文章 · 2020-07-20

    🍊计算机网络知识大汇总(建议精读)

    简介🍊

    很多人都知道学习和理解HTTP协议的重要性及必要性,但HTTP相关知识对计算机基础较差,尤其是我这种没有计算机基础的人来说更是晦涩难懂,乘着最近有空闲时间,开始恶补HTTP相关基础知识,我们一起来看看学好计算机网络的相关知识能为你带来些什么:

    ◆对于后端开发同学,你能够打造性能更好的HTTP服务

    ◆对于前端开发同学 ,你能够好得使用HTTP的特性帮助你进行开发

    ◆能够帮助前后端更好得协作

    最近总结了几条关于计算机网络相关的知识点,大家一起看一下吧:

    正文🍊

    1.比较http 0.9和http 1.0😀

    1. http0.9只是一个简单的协议,只有一个GET方法,没有首部,目标用来获取HTML。
    2. HTTP1.0协议大量内容:首部,响应码,重定向,错误,条件请求,内容编码等。

    http0.9流程:

    客户端,构建请求,通过DNS查询IP地址,三次握手建立TCP连接,客户端发起请求,服务器响应,四次挥手,断开TCP连接。(与服务器只有一个来回)

    http1.0流程:

    客户端,构建请求,通过DNS查询IP地址,三次握手建立TCP连接,客户端发起请求,服务器响应,四次挥手,断开TCP连接。(与服务器有两个来回)

    因为不足缺陷,就有了http1.1。

    2.关于http1.1以及http2😁

    http1.1中浏览器再也不用为每个请求重新发起TCP连接了,增加内容有:缓存相关首部的扩展,OPTIONS方法,Upgrade首部,Range请求,压缩和传输编码,管道化等。但还是满足不了现在的web发展需求,so,就有了http.2版本。

    http2解决了(管道化特性可以让客户端一次发送所有的请求,但是有些问题阻碍了管道化的发展,即是某个请求花了很长时间,那么队头阻塞会影响其他请求。)http中的队头阻塞问题。

    使用http2会比http1.1在使用TCP时,用户体验的感知多数延迟的效果有了量化的改善,以及提升了TCP连接的利用率(并行的实现机制不依赖与服务器建立多个连接)

    所以需要学习http2,了解更过的内容来掌握计算机网络。

    对于http2,你可以来运行一个http2的服务器,获取并安装一个http2的web服务器,下载并安装一张TLS证书,让浏览器和服务器通过http2来连接。(从数字证书认证机构申请一张证书)。

    了解http2的协议,先让我们了解一下web页面的请求,就是用户在浏览器中呈现的效果,发生了些什么呢?

    资源获取的步骤:

    把待请求URL放入队列,判断URL是否已在请求队列,否的话就结束,是的话就判断请求域名是否DNS缓存中,没有的话就解析域名,有的话就到指定域名的TCP连接是否开启,没有的话就开启TCP连接,进行HTTPS请求,初始化并完成TLS协议握手,向页面对应的URL发送请求。

    接收响应以及页面渲染步骤:

    接收请求,判断是否HTML页面,是就解析HTML,对页面引用资源排优先级,添加引用资源到请求队列。(如果页面上的关键资源已经接收到,就开始渲染页面),判断是否有还要继续接收资源,继续解析渲染,直到结束。

    3.HTTP的几种请求方法用途😂

    第一种GET方法:发送一个请求来获取服务器上的某一些资源。

    第二种POST方法:向URL指定的资源提交数据或附加新的数据。

    第三种PUT方法:跟POST方法一样,可以向服务器提交数据,但是它们之间也所有不同,PUT指定了资源在服务器的位置,而POST没有哦。

    第四种HEAD方法:指请求页面的首部。

    第五种DELETE方法:删除服务器上的某资源。第六种OPTIONS方法:它用于获取当前URL所支持的方法,如果请求成功,在Allow的头包含类似GET,POST等的信息。

    第七种TARCE方法:用于激发一个远程的,应用层的请求消息回路。

    第八种CONNECT方法:把请求连接转换到TCP/TP通道。

    4.从浏览器地址栏输入url到显示页面的步骤🤣

    简单说说,浏览器根据请求的url交给dns域名解析,查找真正的ip地址,向服务器发起请求;服务器交给后台处理后,返回数据,浏览器会接收到文件数据,比如,html,js,css,图像等;然后浏览器会对加载到的资源进行语法解析,建立相应的内部数据结构;载入解析到得资源文件,渲染页面,完成显示页面效果。

    🙅不够清楚明白码?

    那就再次详细一下,咳咳,从浏览器接收url,开始进行网络请求线程,发出一个完整的HTTP请求,从服务器端接收请求到对应的后台接收到请求,然后是后台和前台的http交互;其中的缓存问题(http的缓存),浏览器接收到http数据包后的解析流程,css的可视化格式模型,js引擎解析过程等;其他呈现页面效果。

    🙅:这里就需要你对浏览器内核的理解:

    其中主要的渲染引擎和JS引擎,这里了解一下你对浏览器内核的理解。

    1. 渲染引擎,是负责取得网页的内容,整理信息,以及计算网页的显示方式,然后输出到显示器上。
    2. JS引擎是用于解析和执行javascript来实现网页的动态效果。
    浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。其实最开始渲染引擎和JS引擎是没有区分明确的,不过后来JS引擎越来越独立,so,内核就倾向于渲染引擎。

    对于资源请求/获取,资源响应/页面渲染,会给网络带宽和设备资源带来压力,这个时候就会考虑到web的性能优化。

    5.web的性能优化😃

    其中里面的性能关键

    什么是数据包数据包(IP数据包),指封装在固定结构的一系列字节,它定义了数据包的长度,传输的细节,以及其他与TCP相关的信息。

    延迟:指IP数据包从一个网络端点到另一个网络端点所花费的时间。(所花费时间在于往返时延,是延迟的时间的两倍)

    带宽:只要带宽没有饱和,两个网络端点的连接会一次处理尽可能多的数据量(所以带宽可能会成为性能的瓶颈)

    建立连接时间:在客户端和服务器之间建立连接往返数据(三次握手)

    TCP三次握手过程:客户端向服务器发起一个SYN包,服务器端返回对应的SYN的ACK响应以及新的SYN包,然后客户端返回对应的ACK。(在客户端和服务器之间建立正常的TCP网络连接时,客户端首先发出一个SYN消息,服务器使用SYN+ACK应答表示接收了这个消息,最后客户端再以ACK消息响应。)

    SYN是同步序列编号,是TCP/IP建立连接时使用的握手信息。ACK是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。在TCP/IP协议中,如果接收方成功的接收到数据,那么会回复一个ACK数据。通过ACK信号有自己固定的格式,长度大小,由接收方回复给发送方。

    详解三次握手:

    第一次握手,建立连接时,客户端发送SYN包到服务器,并进入SYN_SENT状态,等待服务器确认,其中SYN就是同步序列编号。

    第二次握手,服务器收到SYN包,必须确认客户的SYN,同时自己也发送一个SYN包,即是SYN+ACK包,此时服务器进入SYN_RECV状态。

    第三次握手,客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

    完成三次握手,客户端与服务器开始传送数据。

    TLS协商时间(TLS会造成额外的往返传输)

    1. 客户端发起https连接,需要进行传输层安全协议协商
    2. TLS用来取代安全套接层SSL

    除了网络,还有页面内容本身或服务器性能,如首字节时间TTFB,内容下载时间,开始渲染时间,文档加载完成的时间等。

    那么什么是TTFB,它是指客户端从开始定位到web页面,至接收到主体页面响应的第一字节所耗费的时间。它是测量:从浏览器发起请求至收到其第一字节之间的耗时。

    内容下载时间是等同于被请求资源的最后字节到达时间。

    开始渲染时间,从客户看到空白页面的时长。

    5.1web性能优化技术(减少客户端网络延迟和优化页面渲染性能来提升web性能)

    优化技术:

    • DNS查询优化
    • 客户端缓存
    • 优化TCP连接
    • 避免重定向
    • 网络边缘的缓存
    • 条件缓存
    • 压缩和代码极简化
    • 图片优化

    6. http1.1😄

    • 改进持久连接和CDN域名的分片机制
    • 不成熟的http管道化
    • 提供虚拟主机支持
    • 对动态生成的内容完美支持
    • 引入cookie以及安全机制

    对于http1的问题,迎来了http2。其中http1的问题:

    队头阻塞,大多数情况下,浏览器会希望同时获取许多资源,但http1未提供机制来同时请求这些资源,如果仅是使用一个连接,需要发起请求,等待响应,然后才能发起下一个请求。

    在http1中要给特性为管道化,可以允许一次发送一组请求,但是需要按照发送顺序依次接收响应。所以在请求应答过程中,如发生什么情况,剩下的工作都会被阻塞,这就是“队头阻塞”(阻塞在那次请求应答发生错误),阻碍网络传输和web页面的渲染,指导失去响应。

    低效的TCP利用,TCP协议作为最可靠的协议之一,其核心是拥塞窗口。

    拥塞窗口,是卫星通信在因特网中防止通信拥塞的一种措施,它是在发端采用了一种“拥塞避免”算法和“慢速启动”算法相结合的机制。“拥塞窗口”就是“拥塞避免”的窗口,它是一个装在发送端的可滑动窗口,窗口的大小是不超过接收端确认通知的窗口。

    拥塞窗口指在接收方确认数据包之前,发送方可以发送的TCP包的数据。(如拥塞窗口指定为1的情况,那么发送方就发出1哥数据包之后,只有接收方确认了那个发出的数据包,才能发送下一个)

    拥塞控制能防止过多的数据注入到网络中,用于避免网络过载,TCP中可以通过慢启动探索当前连接对应拥塞窗口的合适大小。即发送者发送数据的时候并非一开始注入大量数据到网络中,而是发送一个数据包进行测试,当得到确认回复后,额外发送一个未确认包。这意味着得到一个确认回复,可以发送两个数据包,得到两个确认回复,可以发送四个数据包,以几何形式增长很快到达协议规定的拥塞窗口大小(发包数上限),这时候连接进入拥塞避免阶段,这种机制需要往返几次才能得知最佳拥塞窗口大小,但往返几次所需的时间成本不可忽略。

    • 拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。如果再考虑到接收方的接收能力,那么发送窗口还可能小于拥塞窗口。
    • 发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减少一些,以减少注入到网络中的分组数。
    tcp中的慢启动概念,是用来探索当前连接对应拥塞窗口的合适大小。用来弄清楚新连接当前的网络情况。“慢速启动”是在连接建立后,每收到一个来自收端的确认,就控制窗口增加一个段值大小,当窗口值达到“慢速启动”的限值后,慢速启动便停止工作,避免了网络发生拥塞。

    TCP传输控制协议的设计思路是,对假设情况很保守情况下,能够公平对待同一网络的不同流量的应用,它的避免拥塞机制被设计城即使在最差的网络情况下也可以起作用。

    臃肿的消息首部,HTTP/1.1能压缩请求内容,但是消息首部却不能压缩。它可能占据请求的绝大部分(也可能是全部)也是比较常见了。(在这里如果能压缩请求首部,把😙请求变得更小,就能够缓解带宽压力了,降低系统的总负载)

    受限的优先级设置,即如果浏览器针对指定域名开启多个socket请求,若web页面某些资源会比另外一些资源重要,会加重资源的排队效应,会延迟请求其他的资源,优先级高的资源先获取,优先级低的资源会在资源高的资源处理完成,(在处理过程中,浏览器不会发起新的资源请求)等待高的完成后再发起请求,(这就会让总的页面下载时间延长)。

    在请求优先级高的资源的时间区间内浏览器并不会发起优先级较低的新请求

    小结:HTTP1.1慢启动影响资源首次加载速度,TCP建立连接后,会开始请求传输,开始比较慢,然后不断加快,为了防止出现网络拥堵,会让页面的首次渲染时间变长。开始多个tcp,如出现网络下降,无法识别资源的优先级,会出现竞态问题。

    7.如何进行网站性能优化😅

    1. 内容方面,减少Http请求(合并文件,css精灵,inline Image),减少DNS查询(DNS缓存,将资源分布到合适的数量的主机名),减少DOM元素的数量。
    2. Cookie方面,可以减少Cookie的大小。
    3. css方面,将样式表放到页面顶部;不使用css表达式;使用<link>不使用@import;可将css从外部引入;压缩css。
    4. JavaScript方面,将脚本放到页面底部;将JavaScript从外部引入;压缩JavaScript,删除不需要的脚本,减少DOM的访问。
    5. 图片方面,可优化css精灵,不要再HTML中拉伸图片,优化图片(压缩)。

    8.http状态码以及含义😆

    1. 对于1xx的状态码,为信息状态码,100 为继续,表示确认,成功返回具体参数信息。
    2. 对于2xx的状态码,200 表示正常返回信息,201表示请求成功并且服务器创建了新的资源,202表示服务器已接受请求,但尚未处理。
    3. 对于3xx,重定向,301表示,请求的网页已永久移动到新位置,302表示,临时性重定向,303表示临时性重定向,且总是使用 GET 请求新的 URI。304表示,自从上次请求后,请求的网页未修改过。
    4. 对于4xx,客户端错误,404,服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求,401,请求未授权,403,禁止访问,404,找不到如何与 URI 相匹配的资源。
    5. 对于5xx,服务器错误,500,最常见的服务器端错误,503,服务器端暂时无法处理请求,可能是过载或维护。

    9.http-数据压缩😉

    数据压缩,在浏览器中发送请求时会带着Content-Encoding: gzip,里面时浏览器支持的压缩格式列表,有多种如,gzip,deflate,br等。这样服务器就可以从中选择一个压缩算法,放进Content-Encoding响应头里,再把原数据压缩后发给浏览器。

    10.http-分块传输😊

    分块传输,就是将传输的文件分解成多个小块,然后分发给浏览器,浏览器收到后再重新组装复原。

    每个分开包含两个部分,分块长度和分块数据(长度头和数据块),长度头以CRLF结尾的一行明文,数据块紧跟在长度头后面,也是用CRLF结尾,最后用一个长度为0的块表示结束。

    在响应报文里用头字段Transfer-Encoding:chunked表示报文里的body部分不是一次性发送过来的,而是分成了许多块逐个发送的。

    在Transfer-Encoding:chunked和Content-Length中,这两个字段是互斥的。

    一个响应报文的传输长度要么已知,要么长度未知(chunked)。

    Content-Length: 299

    11.http-范围请求😋

    断点续传
    < 要实现该功能需要制定下载的实体范围,这种制定范围发送请求叫做范围请求。

    Accept-Ranges:服务器使用http响应头Accept-Ranges标识自身支持范围请求,字段的具体值用于定义范围请求的单位。

    语法

    Accept-Ranges: bytes,范围请求的单位是 bytes (字节)
    Accept-Ranges: none,不支持任何范围请求单位

    范围请求时用于不需要全部数据,只需要其中的部分请求时,可以使用范围请求,允许客户端在请求头里使用专用字段来表示只获取文件的一部分。

    Range的格式,请求头Range是HTTP范围请求的专用字段,格式是“bytes=x-y”,以字节为单位的数据范围。

    1. “0-”表示从文档起点开始到文档结束的整个文件。
    2. “100-”表示从第100哥字节开始到文档末尾。
    3. “-10”表示从文档末尾倒数的第10个字节开始。

    示例:

    执行范围时会使用头部字段 Range 来指定资源 byte 的范围。
    Range格式:5001-10000字节
    Range : byte = 5001-10000
    5000之后的
    Range : byte = 5001-
    0-3000字节,5001-10000字节
    Range : byte=-3000,5001-10000

    上图表示服务器收到Range字段后,检测范围合法性,范围越界,就会返回状态码416,如你的文件只有1000个字节,但请求范围在20000-3000,就会导致这个状态码的出现。

    如果成功读取文件,范围正确,返回状态码“206”。服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小。

    最后是发送数据,直接把片段用TCP发给客户端,一个范围请求就算是处理完了。

    格式是“bytes x-y/length”,与Range头区别在没有“=”

    Content-Range: bytes 0-4395719/4395720

    12.http-多段数据😎

    多段数据,就是在Range头里使用多个“x-y",一次性获取多个片段数据。使用一种特殊的MIME类型:“multipart/byteranges”,用来表示响应报文包含了多个范围时使用。多重范围请求 响应会在头部 Content-Type 表明 multipart-byteranges。

    多段数据图:分隔标记boundary来区分不同的分段

    13.说一说cookies,sessionStorage 和 localStorage 的区别?😍

    • cookie是网站用来标识用户身份而存储在用户本地终端上的数据
    • cookie数据始终在同源的http请求中携带,即使是不需要的情况,so,会在浏览器和服务器间来回传递
    • sessionStorage和localStorage不会自动把数据发送给服务器,仅仅在本地保存
    存储的大小

    cookie的数据大小不能超过4k;sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或者更大。

    有限期时间
    1. localStorage存储持久数据,浏览器关闭后数据不会丢失,除了主动删除数据
    2. sessionStorage数据在当前浏览器窗口关闭后自动删除
    3. 设置得cookie过期时间之前都有效,就算窗口或者是浏览器关闭

    14.为什么说利用多个域名来存储网站资源会更有效?😘

    因为CDN缓存更方便;突破浏览器并发限制;节约cookie带宽;节约主域名得连接数,优化页面响应速度;防止不必要得安全性问题。

    15.http2.0的内容🥰

    http2是超文本传输协议的第二版,相比http1协议的文本传输格式,http2是以二进制的格式进行数据传输的,具有更小的传输体积以及负载。

    http2.0分层,分帧层(http2多路复用能力的核心部分),数据或http层(包含传统上被认为是 HTTP 及其关联数据的部分)。

    HTTP2.0:

    • 多路复用机制,引入了二进制的分帧层机制来实现多路复用。(分帧层是基于帧的二进制协议。这方便了机器解析。请求和响应交织在一起。)
    • 可以设置请求的优先级(客户端的分帧层对分割块标上请求的优先级)。
    • 头部压缩 请求头压缩,增加传输效率。
    HTTP/2较HTTP/1.1优化亮点
    • 多路复用的流
    • 头部压缩
    • 资源优先级和依赖设置
    • 服务器推送
    • 流量控制
    • 重置消息

    多路复用的实现:

    在单个域名下仍可以建立一个TCP管道,使用一个TCP长连接,下载整个资源页面,只需要一次慢启动,并且避免了竞态,浏览器发起请求,分帧层会对每个请求进行分割,将同一个请求的分割块打上相同的id编号,然后通过协议栈将所有的分割体发送给服务器,然后通过服务器的分帧层根据id编号进行请求组装,服务器的分帧层将回应数据分割按同一个回应体进行ID分割回应给客户端,客户端拼装回应。

    对于http2中的帧(frame),http1不是基于帧(frame)的,是文本分隔的。

    GET/HTTP/1.1 <crlf>

    这样,对于http1的请求或者是响应可能有的问题:

    1. 一次只能处理一个请求或者是响应,完成之前是不能停止解析的。
    2. 无法预判解析需要多少内层。

    HTTP/1 的请求和响应报文,是由起始行、首部和正文组成,换行符分隔;HTTP/2是将请求和响应数据分割成更小的帧,采用二进制编码,易于解析的。

    参考图片:

    帧结构总结 所有的帧都包含一个9 byte的帧头 + 可边长的正文不同。根据帧的类型不同,正文部分的结构也不一样。

    帧头:

    16.http2-幕后😗

    http2作为一个二进制协议,拥有包含轻量型,安全和快速在内的所有优势,保留了原始的http协议语义,对于http2更改了在系统之间传输数据的方式。

    二进制分帧层(binary framing layer),所有通信都在单个TCP连接上执行,该连接在整个对话期间一直处于打开状态,主要是二进制协议将通信分解为帧的方式,这些帧交织在客户端与服务器之间的双向逻辑流中。

    HTTP/2 连接的拓扑结构(展示了一个用于建立多个流的连接)

    在流 1 中,发送了一条请求消息,并返回了相应的响应消息。

    HTTP/2 帧结构

    前9个字节对于每个帧是一致的。解析时只需要读取这些字节,就可以准确地知道在整个帧中期望的字节数。

    帧首部字段表格:

    备注:流Id是用来标识帧所属的流。流看作在连接上的一系列帧,它们构成了单独的 HTTP 请求和响应。

    对于http1 的请求和响应都分成消息首部和消息体两部分;http2 从上面一张图可以知道,http2的请求和响应分成HEADERS 帧和 DATA 帧。

    比较一下:👇

    http2的一个重要特性是基于流的流量控制。提供了客户端调整传输速度的能力。其中WINDOW_UPDATE 帧用来指示流量控制信息。

    有了多路复用,客户端可以一次发出多有资源的请求,不用像http1那样,发出对新对象请求之前,需要等待前一个响应完成。所以浏览器失去了在Http1中的默认资源请求优先级策略。

    17.浏览器生成http请求消息😙

    http的头字段

    头字段类型含义
    Date表示请求和响应生成的日期
    Pragma表示数据是否允许缓存的通信选项
    Cache-Control控制缓存的相关信息
    Connection设置发送响应之后TCP连接是否继续保持的通信选项
    Transfer-Encoding表示消息主体的编码格式
    Via记录途中经过的代理和网关
    Authorization身份认证数据
    From请求发送者的邮件地址
    Referer当通过点击超级链接进入下一个页面时,在这里会记录下上一个页面的URI
    User-Agent客户端软件的名称和版本号等相关信息
    Accept客户端可支持的数据类型,以MIME类型来表示
    Accept-Charset客户端可支持的字符集
    Accept-Language客户端可支持的语言
    Host接收请求的服务器ip地址和端口号
    Range当需要只获取部分数据而不是全部数据时,可通过这个字段指定要获取的数据范围
    Location表示信息的准确位置
    Server服务器程序的名称和版本号等相关信息
    Allow表示指定的URI支持
    Content-Encoding当消息体经过压缩等编码处理时,表示其编码格式
    Content-Length表示消息体的长度
    Content-Type表示消息体的数据类型,以MIME规格定义的数据类型来表示
    Expires表示消息体的有效期
    Last-Modified数据的最后更新日期
    Content-Language表示消息体的语言
    Content-Location表示消息体在服务器上的位置
    Content-Range当仅请求部分数据时,表示消息体包含的数据范围

    HTTP消息示例:

    1. HTTP,超文本传送协议。
    2. 协议,通信操作的规则定义称为协议。
    3. URI,统一资源标识符。
    4. 1 条请求消息中只能写 1 个 URI。如果需要获取多个文件,必须对每个文件单独发送 1 条请求。

    IP 的基本思路

    Ip地址的表示方法

    IP地址的结构-子网掩码表示网络号与主机号之间的边界。

    解析器的调用方法

    DNS服务器的基本工作

    DNS 服务器之间的查询操作

    数据通过类似管道的结构来流动

    18.了解网络基础知识🙂

    • 物理层
    • 数据链路层
    • 网络层
    • 传输层
    • 会话层
    • 表示层
    • 应用层

    计算机网络,可以将规模分WAN,Wide Area Network广域网,和LAN局域网。通过电脑连接交换机再到路由器的连接。

    你知道计算机与网络都经历了怎么样的一个发展过程吗?
    1. 批处理就是指事先将用户程序和数据装入卡带或磁带,由计算机按照一定的顺序读取,使用户所要执行的这些程序和数据能够一并批量得到处理的方式。

    1. 分时系统,是指多个终端与同一个计算机连接,允许多个用户同时使用一台计算机的系统。

    1. 计算机网络

    TCP/IP的机制是什么,TCP/IP通信协议的统称,学习这个有人一定🙅不了解什么是协议。

    但我们在接触到程序时,常常听到协议如IP,TCP,HTTP等协议。记住TCP/IP就是IP,TCP,HTTP等协议的集合。协议就是计算机与计算机之间通过网络实现通信时需要达成的一种的“约定”。这些协议就是让不同厂商的设备,不同的CPU和不同的操作系统组成的计算机之间进行通信。

    就是两台计算机之间都能支持相同的协议,并遵循才能实现相互通信。

    分组交换协议

    分组交换就是将大数据分割成一个一个叫做包的较小单位进行传输的方法。

    分层模块

    了解OSI参考模型

    OSI将分为易于理解的7层:1.物理层,2.数据链路层,3.网络层,4.传输层,5.会话层,6.表示层,7.应用层。

    应用层:是对特定应用的协议。

    表示层:设备固有数据格式和网络标准数据格式的转换。

    会话层:通信管理。

    负责建立和断开通信连接。传输层:管理两个节点之间的数据传输。

    网络层:地址管理与路由选择。

    数据链路层:互连设备之间传送和识别数据帧。

    物理层:以“0”,“1”代表电压的高低,灯光的闪灭。

    如何模块化通信传输

    网络构成要素

    网卡:

    什么是网关,它是OSI参考模型中负责将从传输层到应用层的数据进行转换和转发的设备。

    代理服务:

    19.有哪些渲染优化呢?😝

    第一,我们可以禁止使用iframe,第二,可以禁止使用gif图片来实现loading效果,降低CPU的消耗,来提升渲染性能,第三,使用CSS3代码来代替JS动画。

    对于一些小图标,可以使用base64位编码,以减少网络请求,但不建议大图使用,因为比较耗费CPU,小图标优势在于,可以减少HTTP请求,避免文件跨域,修改及时生效。

    页面头部的style和script会阻塞页面,在Renderer进程中的JS线程和渲染线程是互斥的。

    20.学习TCP和IP的基础知识🤤

    TCP/IP协议族市一组协议的集合,也称为互联网协议族。

    20世纪60年代后半叶,应DoD要求,美国开始进行通信技术相关的演技,ARPANET的诞生,开发分组交互技术,在1975年,TCP/IP的诞生。1983年,ARPANET决定正式启用TCP/IP为通信协议。

    TCP/IP与OSI参考模型

    对于OSI七层模型太细了,而互联网协议族TCP/IP模型划分为四层。

    TCP/IP模型(应用层,传输层,互联网层,网络接口层)-应用层,传输层,网络层,链路层。

    传输层就是可以让应用程序之间实现通信。

    在其中TCP是一种面向有连接的传输层协议,保证两端通信主机之间的通信可达。UDP是一种面向无连接的传输层协议,so,UDP用于分组数据较少或者多播,广播通信以及视频通信等领域。

    应用层

    21.面试题:TCP/IP市如何在媒介上进行传输的呢?😑

    在不同层次的协议✍

    数据包首部:

    以太网包首部:IP包首部,TCP包首部,数据

    IP包首部:TCP包首部,数据

    TCP包首部:数据

    每个分层中,都会对所发送的数据附加一个首部,它包含了该层中必要的信息。(发送的目标地址,协议相关的信息等)
    • 包是全能性术语
    • 帧是数据链路层中包的单位
    • 数据包,IP和UDP等网络层以上的分层中包的单位
    • 段,表示TCP数据流中的信息
    • 消息,应用协议中数据的单位

    数据包的首部,明确表明了协议应该如何读取数据。掌握数据包首部,通常,为协议提供的信息为包首部,所要发送的内容为数据。

    发送数据包,TCP/IP通信流程:🤔

    1. 应用程序处理,发送通信开始TCP/IP通信,应用程序会进行编码处理,编码相当于OSI中的表示层功能。
    2. TCP模块的处理,TCP负责建立连接,发送数据以及断开连接,TCP提供将应用层发来的数据顺利发送至对端的可靠传输。在应用层数据的前端附加一个TCP首部,它包含源端口号和目标端口号,序号以及校验和(用来判断数据是否被破坏)然后附加一个TCP首部的包再发给IP。
    3. IP模块的处理,在TCP首部的前端加上自己的IP首部,它包含接收端IP地址和发送端IP地址。若不知道接收端的MAC地址,可以用ARP查找,只要知道对端MAC地址,就可以将MAC以及IP地址交给以太网的驱动程序,来实现数据传输。
    4. 网络接口的处理,从IP传过来的IP包,然后附加上以太网首部并进行发送处理,以太网首部包含接收端的MAC地址,发送端的MAC的地址,以及标志以太网类型的以太网数据的协议。
    数据包,经过以太网的数据链路时,大致上附加了以太网包首部,IP包首部,TCP包首部或者UDP包,以及应用自己的包首部和数据,最后包追加了包尾。

    分层中包的结构

    数据包接收流程🙄
    1. 网络接口的处理,主机收到以太网包后,从以太网的包首部找到MAC地址判断是否为发给自己的,若不是就丢弃,如果是,就查找以太网包首部中的类型域从而确定以太网协议所传送过来的数据类型。
    2. 通过IP模块处理,然后TCP模块处理(需要判断是否被破坏),检查是否按照序号接收数据。当数据接收完毕后,会发送“确认回执”给发送端。注意,这里的回执信息未能达到发送端,那么发送端会认为没有收到而一直反复发送。
    3. 应用程序的处理,接收端应用程序会直接接收发送端发送的数据信息。

    22.了解一下http-http3.0😶

    在http2.0中,TCP管道传输途中也会导致丢包问题,造成队头阻塞(在http2.0中的TCP建立连接三次握手,和HTTPS的TSL连接也会耗费较多时间)

    其实多路复用技术可以只通过一个TCP连接就可以传输所有的请求数据。

    http3中弄了一个基于UDP协议的QUIC协议,QUIC虽说基于UDP,但是在基础上添加了很多功能。QUIC(快速UDP网络连接)是一种实验性的网络传输协议,由Google开发,该协议旨在使网页传输更快。

    对于在http中的缺点就是延迟,浏览器的阻塞,在对同一域名,同时只能连接4个,超过了浏览器的最大连接限数时,后面的请求就会被阻塞;DNS的查询就是将域名解析为IP,来向目标服务器的IP建立连接,其中通过DNS缓存可以达到减少时间的作用;建立连接,HTTP是基于tcp协议的,三次握手,每次连接都无法复用,so,会每次请求都要三次握手和慢启动,都会影响导致延迟。(慢启动对大量小文件请求影响较大)

    http处于计算机网络中的应用层,建立在TCP协议之上。(掌握了解tcp建立连接的3次握手和断开连接的4次挥手和每次建立连接带来的RTT延迟时间)。

    相对于HTTP1.0使用了header里的if-modified-since,expires来做缓存判断,在HTTP1.1中引入了entity tag,if-unmodified-since,if-match,if-none-match等更多可供选择的缓存头来控制缓存策略。

    http1.0传输数据时,每次都要重新建立连接,增加延迟,http1.1加入了keep-alive可以复用部分连接,但在域名分片等情况下仍要连接夺冠时连接,耗费资源,以及给服务器带来性能压力。

    http1.1尝试使用pipeling来解决问题,就是浏览器可以一次性发出多个请求,在同一个域名下,同一条TCP连接,但对于pipeling要求返回是按照顺序的,即(如果前面有个请求很耗时的话,后面的请求即使服务器已经处理完,任会等待前面的请求处理完才开始按序返回。)

    在http1.x中,Header携带内容过大,增加了传输的成本,在传输的内容都是明文,在一定程度上无法保证其数据的安全性。(在http1.x问题的出现,有了SPDY协议,用于解决http/1.1效率不高的问题,降低延迟,压缩Header等)

    HTTP2主要解决用户和网站只用一个连接(同域名下所有通信都只用单个连接完成,单个连接可以承载任意数量的双向数据流,数据流是以消息的形式发送,消息由一个或多个帧组成)。

    so,http采用二进制格式传输数据,不像http1.x的文本格式。(二进制:http2将请求和响应数据分割成帧,并且它们采用二进制的编码),对于HTTP2的概念:(流,消息,帧)

    1. 流,它是连接中的一个虚拟信道;
    2. 消息,它是HTTP消息,请求,以及响应;
    3. 帧,它是HTTP2.0通信的最小单位。
    多个帧可以乱序发送,可根据帧首部的标识流进行重新组装。

    对于http2,同一域名下只需要使用一个TCP连接,那么当出现丢包时,会导致整个TCP都要开始等待重传。对于http1.1来说,可以开启多个TCP连接,出现这种情况指挥影响一个连接(或者部分),其余的TCP连接正常传输。

    HTTP/2 对首部采取了压缩策略,为了减少资源消耗并提升性能。(因为在http1中,在header携带cookie下,可能每次都要重复传输数据)

    so,有了QUIC协议,整合了TCP,TLS,和HTTP/2的优点,并加以优化。那么QUIC是啥,它是用来替代TCP,SSL/TLS的传输层协议,在传输层之上还有应用层。

    注意,它是一个基于UDP协议的QUIC协议,使用在http3上。

    QUIC 新功能

    HTTPS 的一次完全握手的连接过程

    QUIC可以解决传输单个数据流可以保证有序的交付,并且不会影响其他的数据流。(解决http2问题)

    表示在QUIC连接中,一个连接上的多个stream,如其中

    stream1,stream2,stream3,stream4,其中stream2丢失(quic packet),其余UDP到达,应用层直接读取。--- 无需等待,不存在TCP队头阻塞,丢失的包需要重新传即可。

    补充:

    1. TCP是基于IP和端口去识别连接的;
    2. QUIC是通过ID的方式去识别连接的

    对于QUIC的包都是经过认证的,除了个别,so,这样,通过加密认证的报文,就可以降低安全风险。

    HTTP2-TLS,TCP,IP

    小结QUIC特点:(基于UDP)--- http3-QUIC,UDP,IP

    1. 多路数据流
    2. TLS
    3. 有序交付
    4. 快速握手
    5. 可靠性

    23.网络中的UDP😛

    UPD面向报文的协议,就是UDP只是报文的搬运工,不会对报文进行任何拆分和拼接操作,在发送端,应用层将数据传给传输层的UDP协议,UDP会给数据加一个UDP头标识下是UUDP协议,然后传给网络层。

    接收端,网络层将数据传给传输层,UDP只去除IP报文头就传给应用层,不会任何拼接操作。UDP是无连接,通信不需要建立和断开连接,UDP是不可靠的,不关心数据的安全等问题,UDP是没有拥塞控制,在网络条件不好的情况下可能会导致丢包。

    传输:UDP 支持一对一,一对多,多对多,多对一的的传输方式, UDP 提供了单播,多播,广播的功能。

    24.网络中的TCP😜

    UDP没有TCP那么复杂,UDP头部开销小,但是TCP头部比UDP头部复杂得多,UDP头部只有8字节,相比TCP的至少20字节要少很多。

    Sequence number

    这个序号保证了TCP传输的报文都是有序的,对端可以通过序号顺序的拼接报文

    Window Size

    表示窗口大小,还能接收多少字节的数据

    Acknowledgement Number

    表示上一个序号的数据已经接收到,接收端期望接收的下一个字节的编号是多少

    标识符

    当ACK=1,表示确认号字段有效

    当SYN=1,ACK=0时,表示当前报文段是一个连接请求报文

    当SYN=1,ACK=1时,表示当前报文段是一个同意建立连接的应答报文

    当FIN=1,表示此报文段是一个释放连接的请求报文

    性能指标 RTT

    表示发送端发送数据到接收到对端数据所需的往返时间

    小结
    1. TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议
    2. UDP(User Data Protocol,用户数据报协议)是面向非连接的协议。

    25.建立连接三次握手😕

    建立连接开始时,两端都是CLOSED状态,通信开始前,双方都会创建 TCB,后进入 LISTEN 状态,开始等待客户端发送数据。

    第一次握手

    客户端向服务器端发送连接请求报文段,请求发送后,客户端进入SYN-SENT 状态。

    第二次握手

    服务端收到连接请求报文段后,发送完成后便进入 SYN-RECEIVED 状态。

    第三次握手

    客户端收到连接同意的应答后,要向服务端发送一个确认报文。客户端发完这个报文段后便进入ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED状态,此时连接建立成功。

    有人问了,两次握手就可以建立连接了,为啥要第三次呢?

    因为防止失效的连接请求报文段被服务器端接收,从而导致错误。

    26.http请求码有哪些?🤑

    100为继续,一般发送post请求时,已经发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息;201,请求成功并且服务器创建了新的资源;202,服务器已接受请求,但未处理。

    301,请求的网页已经永久移动到新的位置;302,临时性重定向;303,临时性重定向,且总是使用GET请求新的URI;304,自从上次请求后,请求的网页未修改过。

    404,服务器无法理解请求;401,请求未授权;403,禁止访问。

    27.面试时,简单说说TCP传输的三次握手四次挥手😲

    传输,为了准确无误地把数据传输给目标,TCP协议采用了三次握手策略,用TCP协议把数据包送出去后,会向对方确认是否成功达到,发送端发送一个带SYN标志的数据包给到对方,接收端收到后,会回传一个带有SYN/ACK标志的数据包表示传送到达的确认信息,然后发送端也再次回传一个带有ACK标志的数据包,表示“握手”结束了。

    握手过程中使用的标志:SYN和ACK

    断开一个TCP连接需要四次挥手:

    第一次挥手

    主动关闭的一方,发送一个FIN(上述讲过---当FIN=1,表示此报文段是一个释放连接的请求报文),传送数据,用来告诉对方(被动关闭方),说不会再给你发送数据了。---主动关闭的一方可以接受数据。

    第二次挥手

    被动关闭方 收到 FIN 包,发送 ACK 给对方,确认序号。

    第三次挥手

    被动关闭方 发送一个 FIN,关闭方,说我不会再给你发数据了。(你不给我发送数据,我也不给你发送数据了)

    第四次挥手

    主动关闭一方收到 FIN ,发送要给 ACK ,用来确认序号

    28.常说的HTTPS🙁

    其实HTTP协议时承载于TCP协议之上的,再HTTP和TCP之间添加一个安全协议层,SSL或者TSL(ssl/tls协议传输,包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配,refer传递等),则就是常说的HTTPS。

    29.GET和POST的区别,何时使用POST?😖

    1. GET是用于信息获取,使用URL传递参数,发送信息的数量有限;
    2. POST是用于修改服务器上的资源;
    3. 一般使用POST,当无法使用缓存文件,向服务器发送大量的数据,发送未知的字符

    30.面试问,HTTP协议的主要特点😨

    1. 简单快速
    2. 灵活
    3. 无连接
    4. 无状态

    31.面试问,说说HTTP报文的组成部分😟

    HTTP报文的组成部分包含:请求报文和响应报文

    请求报文:有请求行,请求头, 空行,请求体

    响应报文:有状态行,响应头,空行,响应体

    请求报文包含:

    1.请求方法,2.请求URL,3.HTTP协议以及版本,4.报文头,5.报文体

    • 请求行,有请求方法,请求URL,http协议以及版本;
    • 请求头,一堆键值对
    • 空行,当服务器在解析请求头的时候,遇到了空行,表明后面的内容是请求体
    • 请求体,请求数据
    响应报文包含:

    1.报文协议以及版本,2,状态码以及状态描述,3,响应头,4,响应体

    • 状态行:http协议和版本,状态码以及状态描述
    • 响应头
    • 空行
    • 响应体

    32.面试时问,知道哪些HTTP方法😤

    1. GET方法获取资源
    2. POST方法传输资源
    3. PUT方法更新资源
    4. DELETE方法删除资源
    5. HEAD方法获得报文首部

    接口需要符合resultful规范

    33.持久链接😢

    在http1.0中,客户端每隔很短时间会对服务器发出请求,查看是否有新的数据,只要轮询足够快,就可以造成交互实时进行,但这个做法,会对两端造成大量的性能浪费。

    对于http1.1中的长连接,使用connection:keep-alive进行长连接,客户端只请求一次,但是服务器会将继续保持连接,再次请求时,避免了重新建立连接。

    注意,keep-alive不会永久保持连接,只有保持一个时间段。

    34.安全问题:CSRF和XSS😭

    CSRF的基本概念,攻击原理,防御措施

    CSRF(Cross-site request forgery):跨站请求伪造

    理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。

    以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。

    CSRF的原理:(要完成一次CSRF攻击)

    • 登录受信任网站A,并在本地生成Cookie。
    • 在不登出A的情况下,访问危险网站B。

    XSS的基本概念,跨域脚本攻击。

    xss是一种发生在web前端的漏洞,所以其危害的对象也主要是前端用户。

    跨域脚本攻击是,恶意攻击者往web页面里插入恶意的script代码,在浏览器中运行script代码,达到恶意攻击用户的目的。

    so,实现xss攻击具备2个条件,第一需要向web页面注入恶意的代码,第二,这些恶意代码被浏览器成功的执行。

    CSRF和XSS的区别:

    1. CSRF需要登录,获取COOKIE,利用网站本身的漏洞,去请求网站的api
    2. XSS,不需要登录,向网站注入JS代码,执行JS里的代码,篡改网站的内容

    35.从一个HTTP请求来看网络分层原理

    一个HTTP请求的分层解析流程:

    TCP,它是面向连接的,可靠的,基于字节流的传输层通信协议。

    特点:

    • 基于连接,数据传输之前需要建立连接
    • 全双工的,双向传输
    • 字节流,不限制数据大小,打包成报文段,保证有序接收,重复报文自动丢弃
    • 流量缓冲,解决双方处理能力的不匹配
    • 可靠的传输服务,保证可达,丢包时通过重发机制实现可靠性
    • 拥塞控制,防止网络出现恶性拥塞

    TCP连接,源地址,源端口,目的地址,目的端口

    从TCP-IP协议底层

    滑动窗口协议与累计确认(延时ACK)

    滑动窗口大小同通过tcp三次握手和对端协商,且受网络状况影响

    36.HTTPS安全加密通道原理分析

    什么是HTTPS协议,由于HTTP天生“明文”的特点,整个传输过程完全透明,任何人都能够在链路中截获,修改或者伪造请求、响应报文,数据不具有可信性。

    使用HTTPS时,所有的HTTP请求和响应发送到网络前,都要进行加密。

    https = http + ssl/tls
    对称加密:加密 解密使用同一密钥
    非对称加密:公钥-随意分发,私钥-服务器自己保持
    公钥加密的数据,只能通过私钥解密
    私钥加密的数据,只能公钥能解密

    加密算法:

    对称密钥加密算法,编,解码使用相同密钥的算法

    非对称密钥加密算法,一个公钥,一个私钥,两个密钥是不同的,公钥可以公开给如何人使用,私钥是严格保密的。

    加密通道的建立:

    数字证书的申请和验证

    如何申请:

    • 生成自己的公钥和私钥,服务器自己保留私钥
    • 向CA机构提交公钥,公司,域名信息等待认证
    • CA机构通过线上,线下多种途径验证你提交信息的真实性,合法性
    • 信息审核通过,CA机构则会向你签发认证的数字证书,包含了公钥,组织信息,CA信息,有效时间,证书序列号,同时生成了一个签名
    • 签名步骤:hash(用于申请证书所提交的明文信息)= 信息摘要
    • CA再使用CA机构的私钥对信息摘要进行加密,密文就是证书的数字签名

    37.https的对称加密,非对称加密,混合加密,CA认证😨

    HTTPS ,超文本传输安全协议,目标是安全的HTTP通道,应用是安全数据传输。HTTP协议虽然使用广,但是存在不小的安全缺陷,主要是数据的明文传送和消息完整性检测的缺乏。

    HTTPS协议是由HTTP加上TLS/SSL协议构建的可进行加密传输,身份认证的网络协议。

    通过, 数字证书,加密算法,非对称密钥 等技术完成互联网数据传输加密,实现互联网传输安全保护。

    HTTPS主要特性:

    1. 数据保密性
    2. 数据完整性
    3. 身份校验安全性

    客户端和服务器端在传输数据之前,会通过基于证书对双方进行身份认证。客户端发起SSL握手消息给服务端要求连接,服务端将证书发送给客户端。客户端检查服务器端证书,确认是否由自己信任的证书签发机构签发,如果不是,将是否继续通讯的决定权交给用户选择,如果检查无误或者用户选择继续,则客户端认可服务端的身份。

    服务端要求客户端发送证书,并检查是否通过验证,失败则关闭连接,认证成功,从客户端证书中获得客户端的公钥。

    HTTP原理

    客户端的浏览器首先要通过网络与服务器建立连接,该连接时通过TCP来完成的,一般TCP连接的端口号是80,建立连接后,客户端发送一个请求给服务器端;服务器端接收到请求后,给予相应的响应信息。

    HTTPS原理

    客户端将它所支持的算法列表和一个用作产生密钥的随机数发送给服务器,服务器从算法列表中选择一种加密算法,并将它和一份包含服务器公用密钥的证书发送给客户端,该证书还包含了用于认证目的的服务器标识,服务器同时还提供了一个用作产生密钥的随机数。

    客户端对服务器的证书进行验证,并抽取服务器的公用密钥,再产生一个称作pre_master_secret的随机密码串,并使用服务器的公用密钥对其进行加密,并将加密后的信息发送给服务器。

    客户端与服务器端根据pre_master_secret以及客户端与服务器的随机数独立计算出加密和MAC密钥。

    混合加密

    在传输数据中使用对称加密,但对称加密的密钥采用非对称加密来传输,混合加密比较安全,但无法知道数据是否被篡改

    CA认证

    CA认证, 即是电子认证服务,指电子签名相关各方提供真实性,可靠性验证的活动。

    特性:参阅百度百科—简介,点击进入

    38.https对比http🥶

    http传输方式:明文传输,网站或相关服务与用户之间的数据交互无加密,容易被监听,篡改。

    https传输方式:在HTTP加入了SSL层,用于数据传输加密。

    http身份认证:无任何身份认证,用户无法通过http辨认出网站的真实身份。

    https身份认证:经过CA多重认证,包含域名管理权限认证等。

    http成本:无任何使用成本,所有网站默认是http模式。

    https需要成本,需要申请SSL证书来实现https。

    http连接端口:80端口。https连接端口:443端口。

    39.证书如何安全传输,被掉包了怎么办?😳

    40.http3中QUIC😵

    QUIC是谷歌制定的一种基于UDP的低时延的互联网传输层协议。

    1、避免前序包阻塞;2、零RTT建连;3、FEC前向纠错

    HTTP 的历史

    HTTP/2 和 HTTP/3 建立连接的差别

    TCP/建立连接与QUIC建立连接

    队头阻塞/多路复用

    HTTP/1.1 提出了 Pipelining 技术,允许一个 TCP 连接同时发送多个请求

    请求与响应,与,Pipelining

    http/1.1队头阻塞


    HTTP/2 的多路复用解决了队头阻塞问题

    拥塞控制:

    • 慢启动
    • 拥塞避免
    • 快速重传
    • 快速恢复

    41.HTTP 协议入门

    HTTP 基于TCP/IP 协议的应用层协议,不涉及数据包packet传输,主要客户端和服务器之间的通信格式,默认使用80端口。TCP连接建立后,客户端向服务器请求网页,协议规定,服务器只能回应HTML格式的字符串,不能回应别的格式。

    http1.0可以传输文字,传输图像,视频,二进制文件;除了GET方法,还有POST,HEAD等;每次通信都需要 头信息HTTP header,状态码,多字符集支持,缓存,权限等。

    字段:ontent-Type 字段头信息必须是 ASCII 码,后面的数据可以是任何格式,字段值:

    text/plain
    text/html
    text/css
    image/jpeg
    image/png
    image/svg+xml
    audio/mp4
    video/mp4
    application/javascript
    application/pdf
    application/zip
    application/atom+xml

    客户端请求的时候,使用Accept字段,表示可以接受哪些数据格式。

    Accept: /

    Content-Encoding字段,表示数据的压缩方式

    Content-Encoding: gzip
    Content-Encoding: compress
    Content-Encoding: deflate

    客户端在请求时,用Accept-Encoding字段说明接受哪些压缩方法。

    Accept-Encoding: gzip, deflate

    http1.0就是每个TCP连接只能发送一个请求,发送完毕后就关闭,so,为解决问题,用了一个非标准Connection字段,Connection:keep-alive。

    HTTP/1.1引入了持久连接(persistent connection),TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。

    也不是永久性不关闭的,只要有一段时间没有活动,就会关闭TCP连接,一般对于同一个域名,大多数浏览器允许同时建立6个持久连接。

    1.1 版引入了管道机制(pipelining),同一个TCP连接里,可以同时发送多个请求。但是还是按照顺序,一个请求回应后,再回应另一个请求。(但也减少不小的消耗时间)。

    使用分块传输编码,只要请求或回应的头信息有Transfer-Encoding字段

    Transfer-Encoding: chunked

    什么是多工?双向,实时的通信就叫 多工。

    HTTP2 复用TCP连接,在一个连接里,两端都可以同时发送多个请求或响应,而且不用按照顺序一一对应,避免了“队头堵塞”。

    http2引入了头信息压缩机,头信息使用gzip或compress压缩后再发送,客户端和服务器同时维护一张头信息表,所有字段存在这个表里,生成一个索引号,以后就只发送索引号,这样就提高速度了。

    HTTP/2允许服务器未经请求,主动向客户端发送资源(服务器推送)

    42.什么是cookie呢🥴

    cookie是某网站为了辨别用户身份,进行session跟踪而存储在用户本地终端的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。

    1. 存储在用户本地终端上的数据
    2. 用来辨别用户身份
    3. 保存在用户本地终端

    cookie是一些数据,存储在你电脑上的文本文件中,当web服务器向浏览器发送web页面时,在连接关闭后,服务端不会记录用户的信息,cookie的作用就是解决如何记录客户端的用户信息。

    场景:当用户访问web页面,用户信息记录在cookie中,当用户下一次访问页面后,可以在cookie中读取用户访问记录。

    cookie是以键值对形式存储的,当浏览器从服务器上请求web页面,该页面的cookie会被添加到请求中,服务端通过这种方式用来获取用户信息。

    可以使用JavaScript来创建,读取,修改,删除cookie

    使用document.cookie属性来创建,读取以及删除cookie

    创建:

    document.cookie = "username = dadaqianduan";

    给cookie添加一个过期时间:

    document.cookie = "username = dadaqianduan; expires=xxxxxx";

    默认情况下,cookie属于当前页面:

    document.cookie = "username = dadaqianduan; expires= ; path=/";

    读取cookie

    var x = document.cookie;

    修改cookie

    document.cookie = "username = dada; expires=xxx; path=/";

    删除cookie, 把设置时间的expires 参数改为以前的时间即可。

    document.cookie = "username = ; expires= xxx";

    为什么会有cookie呢?因为http请求时无协议的,http1.x,无状态协议,客户端同一个请求发送多次,服务端并不能识别是不是同一个客户端发送,为了解决无状态,就有了cookie。

    cookies是服务器暂存放在你的电脑里的资料,以.txt格式的文本文件,好让服务器用来辨认你的计算机,当你在浏览网站时,web服务器会发送一个小小的资料放在你的计算机上。

    当你下一次访问同一个网站,web浏览器会先看看有没有它上次留下来的cookies资料,有的话就输出特定的内容给你。

    cookie原理

    浏览器第一次请求服务器,服务器响应请求中携带cookie,给浏览器,浏览器第二次请求,携带cookie,给服务器,服务器根据cookie辨别用户,也可以修改cookie内容。

    domain时.baidu.com的cookie绑定到了域名商。跨域的域名不能写入在cookies文件里

    cookie的属性有哪些

    Name, Value, Domain, Path, Expires/Max-Age, Size, HttpOnly, Secure, SameSite

    掌握面试中的HttpOnly,这个属性设置为true,就不能通过js脚本获取cookie的指,能有效防止xss的攻击。

    Cookie中的HttpOnly和Secure中:

    标记为Secure的Cookie只能被https协议加密过的请求发送给服务端。但也无法保证其安全保障。

    如果cookie中设置了HttpOnly属性,通过js脚本将无法读取到cookie信息,有效防止xss的攻击,窃取cookie内容,增加了cookie的安全性,但是重要信息还是不要存储在cookie中。

    因为xss为跨站脚本攻击,是web程序常见的漏洞,属于被动式且用于客户端的攻击方式

    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

    SameSite

    SameSite Cookie允许服务器要求某个cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。

    示例:

    Set-Cookie: key=value; SameSite=Strict

    SameSite有三个值:

    None: 浏览器在同站请求,跨站请求下继续发送cookies,不区分大小写。(所有三方的请求都会携带cookie)

    Strict: 浏览器将只在访问相同站点时发送cookie。(所有三方的链接都不会携带cookie)

    Lax: Same-site cookies 将会为一些跨站子请求保留,如图片加载或者frames的调用,但只有当用户从外部站点导航到URL时才会发送。(只有同步且是get请求才可携带cookie)

    在https协议中,才能通过js去设置secure类型的cookie,在http协议的网页中是无法设置secure类型cookie的。默认情况,https协议还是http协议的请求,cookie都会被发送到服务端。

    43.什么是token呢?🤬

    token的出现,是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示。token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,第一登录时,服务器生成一个token,将此token返回给客户端,客户端带上这个token,无需再次带上用户名和密码了。

    token的出现减轻了服务器的压力,减少频繁地数据库查询。

    token的优点
    • 无状态,可扩展
    • 安全性
    • 多平台跨域
    • 基于标准

    基于Token的身份验证的过程

    浏览器,输入userName, Password,到mysql,校验成功 生成token,将token返回给客户端,当客户端发起请求时,每次访问api都携带token到服务器端,经过过滤器,校验token,校验成功后返回请求数据,校验失败后返回错误码。

    44.cookie,session,token😷

    cookie,记录访问过的网站或正在访问的网站,对于HTTP 协议是无状态的,服务器不知道浏览器上一次访问做了什么,也无法对用户会话进行跟踪连接,所以,cookie是由服务器发送到客户端浏览器的一段文本文件,包含了网站访问活动信息。Cookie 存放在客户端,用来保存客户端会话信息;由于存储在客户端,它的安全性不能完全保证。

    session表示是c/s架构中服务器和客户端一次会话的过程,用来保存认证用户信息。session是一种HTTP存储机制,提供持久机制。Session存放在服务器端,用户验证客户端信息。由于存储在服务器,安全性有一定的保证。

    token是一种认证方式(是“令牌”的意思,主要是用于身份的验证方式。)

    45.跨域🤒


    网页的URL的协议、域名、端口有一个不同,就算是跨域了

    跨域:JSONP

    46.http中的字段🤠

    1. accept,数据格式,请求accept,响应,content-type,表示收到的数据格式
    2. accept,压缩方式,请求accept-encoding,响应,content-encoding,采用什么样的压缩方式
    3. accept,支持语言,请求accept-language,响应content-language
    4. accept,字符集,请求accept-charset,响应content-type,指定字符集
    5. accept,范围请求,请求if-range和range,响应accept-anges和content-range
    6. cookie,请求时传递给服务端的cookie信息
    7. set-cookie,响应报文首部设置要传递给客户端的cookie信息
    8. allow,支持什么HTTP方法
    9. last-modified,资源的最后修改时间
    10. expires,设置资源缓存的失败日期
    11. content-language,实体的资源语言
    12. content-encoding,实体的编码格式
    13. content-length,实体主体部分的大小单位是字节
    14. content-range,返回的实体的哪些范围
    15. content-type,哪些类型
    16. accept-ranges,处理的范围请求
    17. age,告诉客户端服务器在多久前创建了响应
    18. vary,代理服务器的缓存信息
    19. location,用于指定重定向后的URI
    20. If-Match,值是资源的唯一标识
    21. User-Agent,将创建请求的浏览器和用户代理名称等信息传递给服务器
    22. Transfer-Encoding,传输报文的主体编码方式
    23. connection,管理持久连接,keep-alive , close
    24. Cache-Control,控制浏览器的强缓存

    47.如果面试问HTTP报文结构是什么,你能回答上来不?

    对于 TCP 而言

    起始行 + 头部 + 空行 + 实体
    1. 请求报文
    GET /home HTTP/1.1
    1. 响应报文
    HTTP/1.1 200 OK

    空行是用来分开头部和实体。

    48.如果面试问HTTP请求方法有哪些,你能回答上来不?🤥

    1. GET方法,用来获取资源
    2. POST方法,用来提交数据
    3. PUT方法,用来修改数据
    4. DELETE方法,用来删除资源
    5. OPTIONS方法,用来跨域请求
    6. HEAD方法,用来获取资源的元信息
    7. CONNECT方法,用来建立连接,用于代理服务器

    49.如果面试问,你对URI是如何理解的,你能回答上来不?🤫

    URL统一资源定位符,URI,统一资源标识符。URI用于区分网络上不同的资源。

    URI包含了URN和URL。

    URL的结构:

    协议名,登录主机的用户信息,主机名和端口,请求路径,查询参数,URI上定位资源内的一个锚点。

    50.如果面试问,你对HTTP状态码的了解有多少,你能回答上来不?

    了解一些特定的HTTP状态码:

    51.如果面试问,说说HTTP特点以及缺点,你能回答上来不?

    特点是:

    1. 灵活可扩展
    2. 可靠传输
    3. 无状态等

    缺点是:

    1. 无状态
    2. 明文传输
    3. 队头阻塞问题

    52.如果面试问,说说你对Accept字段的理解,你能回答上来不?

    • 数据格式
    • 压缩方式
    • 支持语言
    • 字符集

    53.如果面试问,什么是队头阻塞问题,你能回答上来不?🤭

    TCP中是报文,HTTP是请求。

    对于解决HTTP的队头阻塞问题是:并发连接和域名分片。

    54.如果面试问,说说你对HTTP代理的理解,你能回答上来不?

    🧐代理服务器功能:1,负载均衡,2,保障安全(利用心跳机制监控服务器,一旦发现故障机就将其踢出集群。),3,缓存代理。

    理解代理缓存:

    • 由一个代理服务器下载的页面存储;
    • 一个代理服务器为多个用户提供一条通道;
    • 缓冲的代理允许一个代理服务器减少对同一个网站的同样页面的请求次数
    • 一旦代理服务器的一个用户请求了某个页面,代理服务器就保存该页面以服务于它的其他用户的同样的请求
    • 代理缓存,这种处理减少了用户等待页面显示的时间

    缓存的作用:

    代理服务器或客户端本地磁盘内保存的资源副本,利用缓存可减少对源服务器的访问,可以节省通信流量和通信时间。

    示例:

    Cache-Control: max-age=300;

    表示时间间隔,再次请求的时间间隔300s内,就在缓存中获取,否则就在服务器中

    Cache-Control:

    • public 表示响应可被任何中间节点缓存
    • private 表示中间节点不允许缓存
    • no-cache 表示不使用Cache-Control的缓存控制方式做前置验证
    • no-store 表示真正的不缓存任何东西
    • max-age 表示当前资源的有效时间

    强缓存:浏览器直接从本地存储中获取数据,不与服务器进行交互

    协商缓存:浏览器发送请求到服务器,浏览器判断是否可使用本地缓存

    学习了解强缓存👍:

    强缓存主要学习expires和cache-control

    cache-control该字段:max-age,s-maxage,public,private,no-cache,no-store。

    cache-control: public, max-age=3600, s-maxage=3600
    • 表示资源过了多少秒之后变为无效
    • s-maxage 的优先级高于 max-age
    • 在代理服务器中,只有 s-maxage 起作用

    public 和 private

    • public 表示该资源可以被所有客户端和代理服务器缓存
    • private 表示该资源仅能客户端缓存当浏览器去请求某个文件的时候,服务端就在response header里做了缓存的配置:
    表现为:respone header 的cache-control


    学习了解✍协商缓存:

    response header里面的设置

    etag: 'xxxx-xxx
    last-modified: xx, 24 Dec xxx xxx:xx:xx GMT

    55.如果面试问,HTTP/2,你能回答上来不?🤓

    HTTP/2采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。

    服务器推送

    56.B/S 结构定义😈

    浏览器-服务器结构,B/S结构,客户端不需要安装专门的软件,只需要浏览器即可,浏览器通过web服务器与数据库进行交互,可以方便的在不同平台下工作。

    B/S结构简化了客户端的工作,它是随着Internet技术兴起而产生的,对C/S技术的改进,但该结构下服务器端的工作较重,对服务器的性能要求更高。

    57.URI统一资源标识符👿

    统一资源标识符是一个用于标识某一互联网资源名称的字符串。该标识允许用户对网络中的资源通过特定的协议进行交互操作。URI常见形式为统一资源定位符(URL),URN为统一资源名称。用于在特定的命令空间资源的标识,以补充网址。

    58.HTTP 协议👹

    HTTP超文本传输协议是互联网上应用最为广泛的一种网络协议。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符来标识

    HTTP 协议主要特点

    59.数据链路🔪-数据链路层

    数据链路层:以太网,无线LAN,PPP。。。(无线,光纤。。。)
    • 数据链路的知识对了解TCP/IP与网络起到重要的作用
    • 数据链路层的协议定义了通过通信媒介互连的设备传输的规范
    • 物理层面是将实际的通信媒介如电压的高低,电波的强弱等信号与二进制01进行转换
    • 数据链路层处理的数据是一种集合为“帧”的块
    • WLAN,无线局域网
    • PPP,点对点协议,即是1对1连接计算机的协议
    • ATM,异步传输方式
    数据链路是让互联网计算机之间相互通信的一种协议,通信手段
    • MAC地址用于识别数据链路中互连的节点

    • 无线通信是使用电磁波,红外线,激光等方式进行传播数据。一般在办公室的局域网范围内组成的较高速的连接称为无线局域网。
    • IP-VPN,在IP网络上建立VPN,网络服务商提供一种在IP网络商使用MPLS技术构建VPN的服务。

    60.TCP和UDP的区别

    TCP是一个面向连接,可靠,基于字节流的传输层协议。

    UDP是一个面向无连接的传输层协议。

    TCP是面向连接的,客户端和服务器端的连接,双方互相通信之前,TCP需要三次握手建立连接,而UDP没有建立连接的过程

    tcp是面向字节流,udp是面向报文的。UDP的数据传输是基于数据报的,TCP继承了IP层的特性,TCP为了维护状态,将一个个IP包变成了字节流。

    TCP报文格式图:

    • 序号:Seq序号,占32位,标识从TCP源端口向目的端口发送的字节流,发起方发送数据时,对此进行标记
    • 确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1
    • 标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等
    1. URG,紧急指有效
    2. ACK,确认序号有效
    3. RST,重置连接
    4. SYN,发起一个新连接
    5. FIN,释放一个连接
    6. PSH,接收方应该尽快将这个报文交给应用层

    61.三次握手建立连接

    TCP 的三次握手的过程:

    由图可知都处于closed状态,服务器开始监听某个端口进入listen状态,客户端发起请求,发送SYN,seq=x,然后状态变为syn-sent状态。

    服务器端接收到返回syn和ack,seq=x,ack =x+1,然后状态变成syn-rcvd状态。

    客户端收到后,发送ack,seq=x+1,ack=y+1给服务器端,状态变为established,服务器收到后,状态变成established。

    在连接过程中,需要对端确认的,需要消耗TCP报文的序列号。SYN消耗一个序列号而ACK不需要。

    对于连接四次握手多余,二次握手,会带来资源的浪费,当遇到丢包,重传,连接关闭后,丢包到达服务端,就默认建立连接,可客户端以及关闭,所以三次握手就可以了。

    62.四次挥手断开连接

    TCP 四次挥手的过程

    三次挥手,当服务器将ack和fin合并为一次挥手,会导致长时间的延迟,以至于客户端误认为fin没有到达客户端,让客户端不断重发fin。

    63.TCP 滑动窗口

    TCP 滑动窗口:

    1. 发送窗口

    1. 接收窗口

    64.TCP 的拥塞控制?

    TCP连接,拥塞控制:

    1. 拥塞窗口(Congestion Window,cwnd)
    2. 慢启动阈值(Slow Start Threshold,ssthresh)
    了解TCP/IP协议四层
    1. 应用层决定了向用户提供应用服务时通信的活动。
    2. 传输层对上层应用层,提供处于网络连接中两台计算机之间的数据传输。
    3. 网络层用来处理在网络上流动的数据包。
    4. 链路层,用来处理连接网络的硬件部分。
    • HTTP协议的职责,生成对目标web服务器的HTTP请求报文
    • tcp协议的职责,为了方便通信,将HTTP请求报文分割成报文段
    • IP协议的职责,搜索对方的地址,一边中转一边传送
    • TCP协议的职责,从对方那里接收到的报文段,重组到达的报文段,按序号以原来的顺序重组请求报文

    65.了解一下DNSDNS是域名解析系统,它的作用非常简单,就是根据域名查出对应的IP地址。

    • 从根域名服务器查到顶级域名服务器的NS记录和A记录,IP地址
    • 从顶级域名服务器查到次级域名服务器的NS记录和A记录,IP地址
    • 从次级域名服务器查出主机名的IP地址

    参考文章🍊

    详解浏览器分段请求基础——Range,助你了解断点续传基础

    HTTP/2协议“多路复用”实现原理

    协议学习——HTTP2帧结构总结

    HTTP/2 幕后原理

    (建议收藏)TCP协议灵魂之问,巩固你的网路底层基础

    https(https://baike.baidu.com/item/https/285356?fr=aladdin))

    HTTP 协议入门

    HTTP cookies

    🍊计算机网络知识大汇总(建议精读)

    ShutdownHTTP系列-HTTP报文篇

    (建议精读)HTTP灵魂之问,巩固你的 HTTP 知识体系

    《网络是怎样连接的》

    《图解TCP/IP》

    查看原文

    赞 3 收藏 2 评论 0

    三余 关注了用户 · 2020-07-16

    城南 @xiarichanming

    关注 529

    三余 关注了用户 · 2020-07-16

    siwuxie @siwuxie

    404

    关注 43

    三余 关注了用户 · 2020-07-16

    题叶 @jiyinyiyong

    ClojureScript 爱好者.

    关注 1979

    三余 关注了用户 · 2020-07-13

    playboy5566 @playboy5566

    一个一直在坑里面的前端小学生

    关注 1139

    三余 关注了用户 · 2020-07-13

    前端小智 @minnanitkong

    我不是什么大牛,我其实想做的就是一个传播者。内容可能过于基础,但对于刚入门的人来说或许是一个窗口,一个解惑之窗。我要先坚持分享20年,大家来一起见证吧。

    关注 9778

    认证与成就

    • 获得 114 次点赞
    • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

    擅长技能
    编辑

    (゚∀゚ )
    暂时没有

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2020-05-08
    个人主页被 2.8k 人浏览