0%

状态码作用:

状态码的职责是当客户端向服务器发送请求时,描述返回的请求结果,借助状态码,用户可以知道服务器时正常处理了请求,还是出现了错误。

状态码类别:

类别 原因短语
1xx Informational(信息状态码) 接收的请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要进行附加操作以完成请求
4xx Client Error(客户端错误状态码) 服务器无法处理请求
5xx Server Error(服务器错误状态码) 服务器处理请求出错

2xx成功

200 OK

表示从客户端发来的请求在服务器端被正常处理了

在响应报文内,随状态码一起返回的信息会因方法的不同而发生改变。比如,使用Get方法,对应请求支援的实体会作为响应返回;而使用HEAD方法时,对应请求资源的实体首部不随报文实体作为响应返回。(即在响应中只返回首部,不返回实体主体部分)

204 No Content

该状态码代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。另外,不允许返回任何实体的主体。比如,当从浏览器中发出请求处理后,返回204响应,那么浏览器显示的页面不发生更新。

一般在只需要从客户端往服务器发送消息,而对客户端不需要发送新消息内容的情况下使用

206 Partial Content

该状态码表示客户端进行范围请求,而服务器成功执行了这部分的GET请求。响应报文中包含由Content-Range指定范围的实体内容

3xx重定向

3xx响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。

301 Moved Permanently

永久性重定向。该状态码表示请求的资源已被分配了新的URI,以后应该使用资源现在所指的URI,也就是说,如果已经把资源对应的URI保存为书签,这时应该按Location首部字段提示的URI重新保存。

eg:

http://example.com/sample

当指定资源路径的最后忘记添加“/“,就会产生301状态码

302 Found

临时性重定向,该状态码表示请求的资源已被分配了新的URI,希望用户(本次)能使用新的URI访问。和301 Moved Permanently状态码相似,但302状态码代表的资源不是被永久移动,只是临时性质。换句话说,已移动的资源对应的URI将来还有可能发生改变。用户把URI保存成书签,但不会像301状态码出现时那样去更新书签,而是仍旧保留返回的302状态码的页面对应的URI。

303 See Other

该状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源。303状态码和302Found状态码有着相同功能。但303状态码明确表示客户端应当采用GET方法获取资源,这点与302状态码有区别。比如,当使用POST方法访问CGI程序,其执行后的处理结果是希望客户端能以GET方法重定向到另一个URI上去时,返回303状态码。

当301,302,303返回响应码时,几乎所有浏览器都会把POST改为GET,并删除请求报文的主体,之后请求会自动再次发送。

301,302标准是禁止将POST改为GET,但实际上大家都这么做。

304 Not Modified

该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况,304状态码返回时,不包括任何响应的主体部分。304和重定向没有关系。

307 Temporary Redirect

临时重定向。该状态码与302 Found有着相同的含义。尽管302标准禁止POST变为GET,但实际使用时大家并不遵守。307会按照浏览器标准,不会从POST变成GET。但是,对于处理响应时的行为,每种浏览器有可能出现不同情况。

4xx客户端错误

4xx的响应结果表明客户端时发生错误的原因所在

400 Bad Request

该状态码表示请求报文中存在语法错误,当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像200 Ok一样对待状态码

401 Unauthorized

该状态码表示发送的请求需要有通过HTTP认证(BASIC认证、DIGEST认证)的认证信息。另外若之前已进行过1次请求,则表示用户认证失败。

返回含有401的响应必须包含一个适用于被请求资源的WWW-Authenticate首部用于质询用户信息,当浏览器初次接收到401响应,会弹出认证用的对话窗口。

403 Forbidden

该状态码表明对请求资源的访问被服务器拒绝了。服务端没有给出拒绝的详细理由,但如果做说明,可以在实体的主体部分对原因进行描述。

未获得文件系统的访问授权,访问权限出现某些问题(从未授权的源IP地址试图访问)等列举的情况都可能是发生403的原因。

404 Not Found

该状态码表明服务器上无法找到请求的资源。除此之外,也可以在服务端拒绝请求且不想说理由时使用。

5xx服务器错误

5xx的响应结果表明服务器本身发生错误

500 Internal Server Error

表明服务器端在执行请求时发生了错误,也有可能时Web应用存在的bug或某些临时的故障

503 Service Unavailable

表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入Retry-After首部字段再返回给客户端。

状态码和状况不一致:

不少返回的状态码响应都是错误的,但是用户可能察觉不到这点,比如Web应用程序内部发生错误,状态码依然返回200 OK。

Accept

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8

Accept首部字段通知服务器,用户代理能够处理的媒体类型及媒体类型的相对优先级。可用type/subtype这种形式,一次指定多种媒体类型。

文本文件:text/html,text/plain,text/css…

application/xhtml+xml,application/xml…

图片文件:image/jpg,image/gif…

视频文件:video/mpeg,video/quicktime…

应用程序使用的二进制文件:application/octet-stream,application/zip…

若想给显示的媒体类型增加优先级,使用q=来额外表示权重值,用分号(;)进行分隔,权重值q范围是0~1,(可精确到小数点后3位,且1为最大值。不指定权重值q时,默认权重为q=1.0

当服务器提供多种内容时,将首先返回权重值最高的媒体类型。

Accept-Charset

Accept-Charset:iso-8859-5,unicode-1-1;q=0.8

Accept-Charset首部字段可用来通知服务器用户代理支持的字符集及字符集的相对优先顺序。另外,可一次性指定多种字符集。与各首部字段Accept相同的是可用权重q值来表示相对优先级

该首部字段用于内容协商机制的服务器驱动协商。

Accept-Encoding

Accept-Encoding:gzip,deflate

Accept-Encoding 首部字段用来告知服务器用户代理支持的内容编码及内容编码的优先级顺序。可一次性指定多种内容编码。

gzip:由文件压缩程序gzip(GNU zip)生成的编码格式,采用LZ77及32为循环冗余校验

compress:由UNIX文件压缩程序compress生成的编码格式,采用的算法LZW

deflate:组合使用zlib格式及由deflate压缩算法生成的编码格式

identity:不执行压缩或不会变化的默认编码格式

采用权值q来表示相对优先级,这点与首部字段Accept相同。可用(*)作为通配符,指定任意编码格式

Accept-Language

告知服务器用户代理能够处理的自然语言集,以及自然语言集的相对优先级。可一次指定多种自然语言集

按权重q表示相对优先级。

Authorization

首部字段Authorization用来告知服务器,用户代理的认证信息,通常想要通过服务器认证的用户代理会在接收待返回的401状态码后,把首部字段Authorization加入到请求中。共用缓存在接收到含有Authorization首部字段的请求时的操作处理会略有差异。

Expect

Expect:100-continue

客户端使用首部字段Expect来告知服务器,期望出现的某种特定行为,因服务器无法理解客户端的期望做出回应而发生错误时,会返回417Expectation Failed

客户端可以利用该首部字段,写明所期望的扩展。虽然HTTP/1.1规范只定义100-continue(状态码100Continue之意)

From

首部字段From用来告知服务器使用用户代理的用户的电子邮件地址。通常使用目的是为了显示搜索引擎用户代理的负责人的电子邮件联系方式。使用代理时,尽可能包含From首部字段

Host

首部字段Host会告知服务器,请求的资源所处的互联网主机名和端口号。Host首部字段再HTTP/1.1规范内是唯一一个必须被包含在请求内的首部字段。

请求被发送到服务器时,请求中的主机名会用IP地址直接替换解决,但如果这时相同的IP地址下部署运行着多个域名,那么服务器就无法理解是哪个域名对应的请求。因此,就要使用首部字段Host来明确指出请求的主机名,若服务器未设定主机名,那直接发送空值。

Host:

If-Match

if-xxx为条件请求,服务器接收到附带条件请求后,只有判断指定条件为真,才执行请求。

首部字段If-Match,属于附带条件之一,它会告知服务器匹配资源所用的实体标记(ETag)值,这时的服务器无法使用弱ETag值,服务器对比If-Match的字段值与资源的ETag值,仅当两者一致时才执行请求,否则,返回状态码412 Precondition Failed

还可以使用(*)指定If-Match的字段值,针对这种情况,服务器会忽略ETag的值,只要资源存在就处理请求。

If-Modified-Since

If-Modified-Since: Thu,15 Apr 2004 00:00:00 GMT

首部字段If-Modified-Since属于附带条件之一,它会告知服务器若If-Modified-Since字段值早于资源的更新时间,则希望能处理该请求,而在If-Modified-Since字段值的日期时间后,如果请求的资源都没有更新,返回状态码304Not Modified的响应

If-Modified-Since用于确认代理或客户端拥有的本地资源的有效性,获取资源的更新时间可通过确认首部字段Last-Modified确定

If-None-Match

与If-Match作用相反,用于指定If-None-Match字段值的实体标记(ETag)值与请求资源得到ETag不一致时会告知服务器处理该请求。

在GET或HEAD方法中使用首部字段If-None-Match可获取最新的源,因此这与使用首部字段If-Modified-Since有些类似。

If-Range

If-Range字段值若是和ETag值或更新的日期时间匹配一致,那么就作为范围请求处理,若不一致返回全部资源

如果不使用If-Range,而是使用If-Match,服务器端的资源如果更新了,那客户端持有的资源中的一部分也会随之无效,服务器端就会以402 Precondition Failed返回,催促客户端再次发请求,与使用If-Range相比,就要花费两倍的功夫。

If-Unmodified-Since

与If-Modified-Since作用相反,它的作用是告知服务器,指定的资源只有在字段值内指定的日期时间之后未发生更新得情况下,才能处理请求,如果在指定日期时间后发生了更新,则以状态码412 Precondition Failed作为响应返回

Max-Forwards

通过TRACE方法或OPTIONS方法,发送包含首部字段Max-Forwards的请求时,该字段以十进制整数的形式指定可经过的服务器最大数目。服务器在往下一个服务器转发请求前,Max-Forwards的值减1后重新复制,当服务器收到Max-Forwards值为0的请求时则不再进行转发,而是直接返回响应。

如果代理服务器由于某些原因导致请求转发失败,客户端也就等不到服务器返回的响应。

开发环境中使用source map

当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。例如,如果将三个源文件(a.js, b.jsc.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js。你可能需要准确地知道错误来自于哪个源文件,所以这种提示这通常不会提供太多帮助。

为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。

source map 有许多 可用选项,请务必仔细阅读它们,以便可以根据需要进行配置。

对于本指南,我们将使用 inline-source-map 选项,这有助于解释说明示例意图(此配置仅用于示例,不要用于生产环境):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const path=require('path')
const HtmlWebpackPlugin=require("html-webpack-plugin")
module.exports={
entry:{
index: './src/index.js',
print:'./src/print.js'

},
output:{
filename:'[name]bundle.js',
path:path.resolve(__dirname,'dist'),
clean:true,
publicPath:'/',
},
module:{
rules:[
{
test: /\.css$/i,
use:['style-loader','css-loader'],
},
{
test:/\.(png|jpg|jpeg|svg|gif)$/i,
type:'asset/resource'

},

]
},
devtool:'inline-source-map',
devServer:{
static:'./dist',
},
plugins:[
new HtmlWebpackPlugin({
title:'Development',
})

],
mode:'development'

}

选择开发工具

webpack 提供几种可选方式,帮助你在代码发生变化后自动编译代码:

  1. webpack’s Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

多数场景中,你可能需要使用 webpack-dev-server,但是不妨探讨一下以上的所有选项。

使用watch mode

你可以指示 webpack “watch” 依赖图中所有文件的更改。如果其中一个文件被更新,代码将被重新编译,所以你不必再去手动运行整个构建。

我们添加一个用于启动 webpack watch mode 的 npm scripts:

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "watch": "webpack --watch",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^4.5.0",
"webpack": "^5.4.0",
"webpack-cli": "^4.2.0"
},
"dependencies": {
"lodash": "^4.17.20"
}
}

现在,你可以在命令行中运行 npm run watch,然后就会看到 webpack 是如何编译代码。

唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。

webpack-dev-server

webpack-dev-server 为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。设置如下:

1
npm install --save-dev webpack-dev-server

修改配置文件,告知 dev server,从什么位置查找文件:

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
print: './src/print.js',
},
devtool: 'inline-source-map',
+ devServer: {
+ static: './dist',
+ },
plugins: [
new HtmlWebpackPlugin({
title: 'Development',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};

以上配置告知 webpack-dev-server,将 dist 目录下的文件 serve 到 localhost:8080 下。(译注:serve,将资源作为 server 的可访问文件)

我们添加一个可以直接运行 dev server 的 script:

package.json

1
2
3
4
5
6
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
+ "start": "webpack serve --open",
"build": "webpack"
},

现在,在命令行中运行 npm start,我们会看到浏览器自动加载页面。如果你更改任何源文件并保存它们,web server 将在编译代码后自动重新加载

webpack-dev-middleware

webpack-dev-middleware 是一个封装器(wrapper),它可以把 webpack 处理过的文件发送到一个 server。webpack-dev-server 在内部使用了它,然而它也可以作为一个单独的 package 来使用,以便根据需求进行更多自定义设置。下面是一个 webpack-dev-middleware 配合 express server 的示例。

首先,安装 expresswebpack-dev-middleware

1
npm install --save-dev express webpack-dev-middleware

调整webpack.config,js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
print: './src/print.js',
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Development',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
+ publicPath: '/',
},
};

我们将会在 server 脚本使用 publicPath,以确保文件资源能够正确地 serve 在 http://localhost:3000 下,稍后我们会指定 port number(端口号)。接下来是设置自定义 express server:

1
2
3
4
5
6
7
8
9
10
 webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
+ |- server.js
|- /dist
|- /src
|- index.js
|- print.js
|- /node_modules

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);

// 将文件 serve 到 port 3000。
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});

现在,添加一个 npm script,以使我们更方便地运行 server:

package.json

1
2
3
4
5
6
7
 "scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"start": "webpack serve --open",
+ "server": "node server.js",
"build": "webpack"
},

现在,在 terminal(终端) 中执行 npm run server

在src文件夹中新创建一个utils文件夹,创建index.js来实现axios实例,并配置拦截器

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import axios from 'axios'
import {ElLoading,ElMessage} from 'element-plus'
import router from "../router"
import store from "../store"
import CHAT from "../client"
//const pendingMap=new Map();
//axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
//创建一个axios实例
var instance=axios.create({
baseURL:"http://127.0.0.1:3007",
timeout: 10000,//设置超时
headers:{
'Content-Type':'application/x-www-form-urlencoded',
}

})
let loading;
//多次请求时
let requestCount=0;
//显示Loading
const showLoading=()=>{
if(requestCount===0&&!loading){//第一次发送请求并且没有loading加载loaing
loading=ElLoading.service({
text:'Loading',
background:'rgba(0,0,0,0.7)',
spinner:'el-icon-loading',
})
}
requestCount++;//多次请求
}
//隐藏loading
const hideLoading=()=>{
requestCount--;
if(requestCount===0){
loading.close()//直到请求都结束Loading才关闭
}
}
//请求拦截器
instance.interceptors.request.use((config)=>{
showLoading()
//每次发送请求前判断是否存在token如果存在则在header加上token
const token=window.localStorage.getItem('token');
token&&(config.headers.Authorization=token)
return config;
},(error)=>{
Promise.reject(error);
})

//响应拦截器
instance.interceptors.response.use((response)=>{
hideLoading()
//响应成功
// console.log('拦截器报错')
// console.log(response)
const status=response.data.status;
if(status!=1){
switch(status){
case 0: //响应成功后如果是登录成功有token把token存储在本地
if(response.data.token!=undefined)window.localStorage.setItem('token',response.data.token);
break;
case 200://获取用户信息成功后存储在localStorage里和store
console.log(response.data);
store.commit("saveUserInfo",(response.data).data);
window.localStorage.setItem('userInfo',JSON.stringify((response.data).data));
break;
case 401://登录过期跳转到登录页面
case 201://退出登录清空token跳转登录页面
window.localStorage.removeItem('token');
window.localStorage.removeItem('userInfo')
CHAT.logout();
router.push("/login");
}
if(response.data.message)ElMessage.success(response.data.message)
return Promise.resolve(response);
}
else {
ElMessage.error(response.data.message);
return Promise.reject(response);
}

},(error)=>{
console.log(error);
//响应错误
if(error.response&&error.response.status){
return Promise.reject(error)
}
return Promise.reject(error);

})
export default instance;

request.js

request.js中使用axios实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import instance from "./index"
const axios=({
method,
url,
data,
config
})=>{
method=method.toLowerCase();
if(method=='post'){
return instance.post(url,data,{...config})
}else if(method=='get'){
return instance.get(url,{
params:data,
...config
})
}else if(method=='delete'){
return instance.delete(url,{
params:data,
...config
})
}else if(method=='put'){
return instance.put(url,data,{...config})
}else{
console.log('未知的方法'+method)
return false
}
}
export default axios

api.js

api.js用来封装各种类型的请求

默认情况下,axios 将 JavaScript 对象序列化为JSON. 要改为以格式发送数据application/x-www-form-urlencoded,我用的是qs.stringfy将数据转换,其他方法可以参考官网

[]: https://axios-http.com/docs/urlencoded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import axios from "./request"
import qs from "qs"
export const login=(data)=>{
return axios({
url:'/api/login',
method:'post',
data:qs.stringify(data),

})
}
export const register=(data)=>{
return axios({
url:'/api/register',
method:'post',
data:qs.stringify(data)
})
}
export const logout=()=>{
return axios({
url:'/api/logout',
method:'post',
})
}
export const getUserInfo=()=>{
return axios({
url:'/my/getUserInfo',
method:'get',


})
}
export const updatePassword=(data)=>{
return axios({
url:'/my/updatePassword',
method:'post',
data:qs.stringify(data)
})
}

打包样式资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const {resolve} =require('path')
module.exports={
entry:'./src/index.js',
//输出
output:{
//输出文件名
filename:'built.js',
//输出路径,_dirname node.js的变量,代表当前文件的目录绝对路径
path:resolve(__dirname,'build')

},
//loader配置
module:{
rules:[
//匹配哪些文件
{test:/\.css$/,
//使用哪些loader
use:[
//use数组中loader执行顺序从右到左,从下到上一次执行
//创建style标签,将js中的样式资源插入进行,添加到head中生效
'style-loader',
//将css文件变成commonjs模块加载到js中,里面内容是样式字符串
'css-loader'
]},
{
test:/\.less$/,
use:[
'style-loader',
'css-loader',
'less-loader',//将less文件编译成css文件
]
}

]
},
plugins:[

],
mode:'development'

}

打包html资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const {resolve} =require('path')
const HtmlWebpackPlugin=require('html-webpack-plugin')
module.exports={
entry:'./src/index.js',
//输出
output:{
//输出文件名
filename:'built.js',
//输出路径,_dirname node.js的变量,代表当前文件的目录绝对路径
path:resolve(__dirname,'build')

},
//loader配置
module:{
rules:[
//匹配哪些文件
{test:/\.css$/,
//使用哪些loader
use:[
//use数组中loader执行顺序从右到左,从下到上一次执行
//创建style标签,将js中的样式资源插入进行,添加到head中生效
'style-loader',
//将css文件变成commonjs模块加载到js中,里面内容是样式字符串
'css-loader'
]},
{
test:/\.less$/,
use:[
'style-loader',
'css-loader',
'less-loader',//将less文件编译成css文件
]
}

]
},
plugins:[
//html-webpack-plugin默认会创建一个空的HTML,自动引入打包输出的所有资源(js/css)
//需求:需要有结构的HTML文件
new HtmlWebpackPlugin({
//模板比对'src/index.html'文件,并自动引入打包输出的所有资源(js/css)
template:'./src/index.html'
})
],
mode:'development'
}

打包图片

html-loader 以相同的方式处理 <img src="./my-image.png" />。需要npm i html-wepack-plugin

[]: https://webpack.docschina.org/loaders/html-loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//处理图片
{
test:/\.(png|svg|jpg|jpeg|gif)$/i,
type:'asset/resource',

},
//将html导出为字符串
{
test:/\.html$/i,
loader:"html-loader",
},
//处理字体
{
test:/\.(woff|woff2|eot|ttf|otf)$/i,
type:'asset/resource'
},

打包xml,csv文件

可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loaderxml-loader。让我们处理加载这三类文件:

1
npm install --save-dev csv-loader xml-loader
1
2
3
4
5
6
7
8
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},

打包toml,yaml,json5

通过使用 自定义 parser 替代特定的 webpack loader,可以将任何 tomlyamljson5 文件作为 JSON 模块导入。

1
npm install toml yamljs json5 --save-dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 const toml=require('toml')
const yaml=require('yamljs')
const json5=require('json5')
...
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},

清理/dist文件

在每次构建前清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项实现这个需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
index: './src/index.js',
print: './src/print.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
+ clean: true,
},
};

Entry:

入口(Entry)指示Webpack以哪个文件为入口起点开始打包

Output:

输出(output)指示Webpack打包后的资源bundle输出到哪里,以及如何命名

Loader:

Loader让Webpack能够去处理那些非JavaScript文件(webpack自身只理解JavaScript)

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

Plugins:

插件(Plugins)可以用于执行范围更广的任务,插件的范围包括从打包优化到压缩,一直到重新定义环境中的变量等

Mode:

模式(Mode)指示Webpack使用相应模式的配置

选项 描述 特点
development 会将process.env.NODE_ENV的值设为development,启用NamedChunksPlugin和NamedModulesPlugin 能让代码在本地调试运行的环境
production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin 能让代码优化上线运行的环境

运行指令

开发环境

webpack ./src/index.js -o ./build/build.js –mode=development:webpack会以./src/index.js为入口打包文件,打包后输出到./build/build.js,整体打包环境,是开发环境

生成环境

webpack ./src/index.js-o ./build/build.js –mode=production,webpack会以./src/index.js为入口打包文件,打包后输出到./build/build.js,整体打包环境,是生产环境

结论

  • webpack能处理js/json资源,不能处理css/img等其他资源
  • 生产环境和开发环境将ES6模块化编译成浏览器能识别的模块化
  • 生产环境比开发环境多一个压缩的js代码

HTTP报文首部

HTTP请求报文由请求行(方法,URI,HTTP版本),HTTP首部字段等部分构成。

HTTP响应报文由状态行(HTTP版本,状态码(数字和原因短语))HTTP首部字段3部分构成。

4种HTTP首部字段类型

通用首部字段:请求报文和响应报文都会使用的首部

请求首部字段:从客户端向服务端发送报文时使用的首部,补充了请求的附加内容,客户端信息,响应内容相关优先级等信息

响应首部字段:从服务器端向客户端返回响应报文时使用的首部。补充了响应的附加内容,也会要求客户端附加额外的内容信息

实体首部字段:针对请求报文和响应报文的实体部分使用的首部。补充了资源内容更新时间等与实体有关的信息

HTTP/1.1通用首部字段

Cache-Control

通过指定首部字段Cache-Control的指令,就能操作缓存的工作机制。

Cache-Control:private,max-age=0,no-cache

缓存请求指令:

指令 参数 说明
no-cache 强制向源服务器再次验证
no-store 不缓存请求或响应的任何内容
max-age=([秒]) 必需 响应的最大Age值
max-stale=([秒]) 可省略 接收已过期的响应
min-fresh=[秒] 必需 期望在指定时间内的响应仍有效
no-transform 代理不接更改媒体类型
only-if-cached 从缓存中获取资源
cache-extension 新指令标记(token)

缓存响应指令

指令 参数 说明
public 可向任意方提供响应的缓存
private 可省略 仅向特定用户返回响应
no-cache 可省略 缓存前必须先确认其有效性
no-store 不缓存请求或响应的任何内容
no-transform 代理不接更改媒体类型
must-revalidate 可缓存但必须再向服务器进行确认
proxy-revalidate 要求中间缓存服务器对缓存的响应有效性再进行确认
max-age=[秒] 必需 响应的最大Age值
s-maxage=[秒] 必需 公共缓存服务器响应的最大Age值
cache-extension - 新指令标记(token)

no-cache指令

Cache-Control:no-cache

使用no-cache指令的目的是为了防止从缓存中返回过期的资源,缓存会向源服务器进行有效期的确认后处理资源

客户端:发送的请求中包含no-cache指令,则表示客户端将不会接收缓存过的响应。于是“中间”缓存服务器必须把客户端请求转发给源服务器

服务器:如果服务器返回的响应中包含no-cache指令,那么缓存服务器不能对资源进行缓存,源服务器以后也不再对缓存服务器请求中提出的资源有效性进行确认,且禁止其对响应资源进行缓存操作

Cache-Control:no-cache=Location

由服务器返回的响应中,若报文首部字段Cache-Control中对no-cache字段名具体指定参数值,那么客户端在接收到这个被指定参数值的首部字段对应的响应报文后,就不能使用缓存。相反,无参数值的首部字段可以使用缓存。只能在响应指令中指定该参数

no-store指令

Cache-Control:no-store

当使用no-store指令时,暗示请求(和对应的响应)或响应中包含机密信息,因此该指令规定缓存不能在本地存储请求或响应的任何一部分

s-maxage指令

Cache-Control:s-maxage=604800(单位:秒)

s-maxage指令的功能和max-age指令相同,不同点在于s-maxage指令只适用于供多位用户使用的公共缓存服务器,对于向同一用户重复返回响应的服务器来说,这个指令没有任何作用。

当使用s-maxage指令后,则直接忽略对Expires首部字段及max-age指令的处理

max-age指令

Cache-Control:max-age=604800(单位:秒)

客户端:当客户端发送的请求中包含max-age指令时,如果判定缓存时间的数值比·1指定时间的数值更小,那么客户端就接收缓存的资源。当指定的max-age=0,那么缓存服务器通常将请求转发给源服务器

服务端:服务器返回的响应中包含max-age指令,缓存服务器将不对资源的有效性再作确认,而max-zge数值代表资源保存为缓存的最长时间

⚠应用HTTP/1.1版本的缓存服务器在遇到同时存在Expires首部字段的情况时,会优先处理max-age指令,而忽略Expires首部字段,而HTTP/1.0版本的缓存服务器则相反,max-age指令被忽略

min-fresh指令

Cache-Control:min-fresh=60

min-fresh指令要求缓存服务器返回至少还未过指定时间的缓存资源。

max-stale指令

Cache-Control:max-stale=3600

使用max-stale指示缓存资源,即使过期也照常接收

如果指令未指定参数值,那么无论过多久,客户端都会接收响应;如果指令中指定了具体数值,那么即使过期,只要仍处于max-stale指定的时间内仍旧会被客户端接收

only-if-cached指令

Cache-Control:only-if-cached

表示客户端仅在缓存服务器本地缓存目标资源的情况下才要求其返回。该指令要求服务器不重新加载响应,也不再次确认资源有效性。若请求缓存服务器的本地缓存无响应,则返回状态码504 Gateway Timeout

must-revalidate指令

Cache-Control:must-revalidate

代理向源服务器再次验证即将返回的响应缓存目前是否仍然有效。如果代理无法连通服务器再次活期有效资源,缓存必须给客户端一条504状态码

使用must-revalidate指令忽略请求的max-stale指令,即使首部使用了max-stale,也不会有效果

proxy-revalidate指令

Cache-Control:proxy-revalidate

proxy-revalidate要求所有缓存服务器在接收到客户端带有该指令的请求返回响应之前,必须再次验证缓存的有效性

no-transform指令

Cache-Control:no-transform

无论·是在请求还是响应中使用,缓存都不能改变实体主体的媒体类型,这样可防止代理压缩图片等类似操作

Cache-Control扩展

cache-control token

Cache-Control:private,community=”UCI”

通过cache-extension标记可以扩展Cache-Control首部字段内的指令。如例子,Cache-Control首部字段本身没有community这个指令。借助extensions tokens实现了该指令的添加。如果缓存服务器无法理解这个新指令就会直接忽略

Connection

作用:

  • 控制不再转发给代理的首部字段
  • 管理持久连接

Connection:不再转发的首部字段名

控制不再转发给代理的首部字段

在客户端发送请求和服务器返回响应内,使用Connection首部字段,可控制不再转发给代理的首部字段

管理持久连接

Connection:close

HTTP/1.1版本的默认连接是持久连接,为此,客户端会在持久连接上连续发送请求。当服务器端想明确断开连接时,指定Connection首部字段为Close

HTTP/1.1之前的版本默认连接是非持久连接。因此,如果想要在旧版本的HTTP协议上维持持续连接,则需要指定Connection为Kepp-Alive

Pragma

Pragma是HTTP/1.1之前版本的历史遗留字段,仅作为与HTTP/1.0的向后兼容而定义

Pragma:no-cache

该首部字段属于通用首部字段,但只用在客户端发送请求中。客户端要求所有的中间服务器不返回缓存的资源。所有中间服务器如果都能以HTTP/1.1为基准,那直接采用Cache-Control:no-cache指定缓存的处理方式是最为理想的。但要整体掌握中间服务器使用的HTTP协议版本不现实,发送请求一般同时包含两个首部字段

Cache-Control:no-cache

Pragma:no-cache

Trailer

首部字段Trailer会事先说明在报文主体后记录了哪些首部字段。该首部字段可应用在HTTP/1.1版本分块传输编码时

Trailer:Expires

—(报文主体)—

Expires:Tue,28 Sep 2004 23:59:59 GMT

Transfer-Encoding

规定了传输报文主体时采用的编码方式

HTTP/1.1的传输编码方式仅对分块传输编码有效

Upgrade

Upgrade用于检测HTTP协议及其他协议是否可使用更高的版本进行同心,其参数值用来指定一个完全不同的通信协议

1
2
3
4
5
6
7
8
客户端=》服务器
GET/index.html HTTP/1.1
Upgrade:TLS/1.0
Connection:Upgrade
服务器=》客户端
HTTP/1.1 101 Switching Protocols
Upgrade:TLS/1.0,HTTP/1.1
Connection:Upgrade

Connection的值被指定为Upgrade,Upgrade的首部字段产生作用的Upgrade对象仅限于客户端和邻接服务器之间。因此使用首部字段Upgrade时需要额外指定Connection为Upgrade

Via

使用Via是为了追踪客户端和服务器之间的请求和响应报文的传输路径

报文经过代理或网关时,会现在首部字段Via中附加该服务器的信息,然后再进行转发。Via不仅用于追踪报文的转发,还可避免请求回环的发生,所以必须在经过代理时附加该首部字段内容

Warning

HTTP/1.1的Warning首部是从HTTP/1.0的响应首部(Retry-After)演变过来的,该首部会告知用户一些与缓存相关的问题的警告

&eg:

Warning:113 gw.hackr.jp:8080 “Heuristic expiration” Tue,03 Jul=>2012 05:09:44 GMT

Warning:【警告码】【警告主机:端口号】“【警告内容】”(【日期时间】)

HTTP/1.1警告码

警告码 警告内容 说明
110 Response is stale(响应已过期) 代理返回已过期的资源
111 Revalidation failed(再验证失败) 代理验证资源有效性时失败(服务器无法到达等原因
112 Disconnection operation(断开连接操作) 代理与互联网连接故意切断
113 Heuristic expiration(试探性过期) 响应的使用期超过24小时(有效缓存的设定时间大于24小时的情况下)
199 Miscellaneous warning(杂项警告) 任意警告内容
214 Transformation applied(使用了转换) 代理对内容编码或媒体类型等执行了某些处理时
299 Miscellaneous persistent warning(持久杂项警告) 任意警告内容

01背包:有限物品数量

二维01背包问题,两个for循环可以交换顺序,内层for循环方向可以顺序循环

一维01背包问题:因为物品数量有限,内层for循环不能重复取,必须倒序,两个for循环可以交换顺序

完全背包问题:物品可以重复取

二维:for循环可以交换顺序,内层for循环必须顺序,因为物品可以重复取

一维:for循环可以交换顺序,内层for循环必须顺序,因为物品可以重复取

求方法数:dp[j]+=dp[j-nums[i]]

一维:求组合数:先遍历物品再遍历背包

求排列数:先遍历背包再遍历物品

不同身份认证方案:

服务端渲染推荐使用Session认证机制

前后端分离推荐使用JWT认证机制

Session认证机制

Cookie是存储在用户浏览器中一段不超过4kb的字符串,它由一个名称(Name),y一个值(Value)和几个用于控制Cookie有效期,安全性,使用范围的可选属性组成

不同域名下的Cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。

Cookie的几大特性:

  • 自动发送

  • 域名独立

  • 过期时限

  • 4kb限制

浏览器可以设置不接受Cookie,也可以设置不向服务器发送Cookie,window.navigator.cookieEnabled属性返回一个布尔值,表示浏览器是否打开cookie功能

两个域名只要域名相同和端口相同就可以共享Cookie,也就是说http://example.com设置的Cookie可以被https://example.com读取

Cookie在身份认证中的作用:

客户端第一次请求服务器时,服务器通过响应头Set-Cookie的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中,随后,当客户端浏览器每次请求服务器时浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器Cookie:foo=bar,服务器即可验证客户端身份

Cookie的属性

Expires,Max-Age:

Expires属性指定一个具体的到齐时间,到了指定时间后,浏览器就不再保留这个Cookie,它的值时UTC格式,可以使用Date.prototype.toUTCString()进行格式转换

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

如果不设置该属性,或者设置为null,Cookie只在当前会话有效,浏览器窗口一旦关闭,当前Session结束,该Cookie被删除,另外,浏览器根据本地时间,觉得Cookie是否过期,由于本地时间不精确,没有办法保证Cookie一旦会在服务器指定时间过期

Max-Age属性指定从现在开始Cookie存在的描述,即过了这个时间后,浏览器就不再保留这个Cookie,如果同时指定Expires和Max-Age,那么Max-Age的值优先生效

Domain,Path

Domain属性指定浏览器发出Http请求时,哪些域名要附带这个Cookie,如果没有指定该属性,浏览器会默认将其设为当前URL的一级域名,比如www.example.com会设为example.com,而且以后如果访问example.com的任何子域名,HTTP请求也会带上这个Cookie,如果服务器在Set-Cookie字段指定的域名不属于当前域名,浏览器会拒绝这个Cookie

Path属性指定指定浏览器发出HTTP请求时哪些路径要附带这个Cookie,只要浏览器发现,Path属性是HTTP请求路径的开头一部分,就会在头信息里面带上这个Cookie,比如PATH属性时/,那么请求/docs路径也会包含该Cookie,当然,前提是域名必须一致

Secure,HttpOnly

Secure属性指定浏览器在加密协议HTTPS下,才能将这个Cookie发送到服务器,另一方面,如果当前协议是HTTP,浏览器会自动忽略服务器发来的Secure属性,该属性只是一个开关,不需要指定值,如果通信时HTTS协议,开关自动打开

HttpOnly属性指定该Cookie无法通过JavaScript脚本拿到,主要是Document.cookie属性,XMLHttpRequest对象和Request API都拿不到该属性,这样就防止了该Cookie被脚本读到,只有浏览器发出HTTP请求时才会带上该Cookie

Cookie具有不安全性

由于Cookie是存储在浏览器中的,因此浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性,因此Cookie不能存放重要隐私数据

Session工作原理:

  1. 客户端把用户ID和密码等登陆信息放入报文的实体部分,通常是以POST请求发送给服务器,而这时使用HTTPS通信来进行HTML表单画面的显示和用户输入数据的发送
  2. 服务器会发放用以识别用户的Session ID,通过验证从客户端送过来的登录信息进行身份认证,人后把用户认证状态与Session ID绑定后记录在服务器端,向客户端返回响应时,会在首部字段Set-Cookie内写入Sessin ID,为避免SessionId被盗,可在Cookie中加入httponly属性
  3. 客户端接收到从服务器发来的Session ID后,会将其作为Cookie保存哎本地,下次向服务器发送请求时,浏览器自动发送Cookie,服务器通过验证接收到的Session ID识别用户和其认证状态

Session认证需要配合Cookie实现,由于Cookie默认不支持跨域访问,所以当涉及前端跨域请求后端接口时需要做额外配置。因此当前端请求后端接口不涉及跨域请求时推荐使用Session身份认证机制,否则使用JWT认证

流程:

  • 浏览器登录发送账号密码,服务端查询用户库,校验用户
  • 服务端把用户登录状态存为Session,生成一个sessionId
  • 通过登录把接口返回,把sessionId set到cookie上
  • 此后浏览器再请求业务接口,sessionId随cookie带上
  • 服务端查sessionId检验session
  • 成功后正常做业务处理,返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const express=require('express');
const session=require('express-session');
const cors=require('cors');
const bodyParser=require('body-parser')
//var formParser=require('express-formidable')
const app=express();
app.use(cors());
app.use(
session({
secret:'xiaoqi',
resave:false,
saveUninitialized:true
})
)
//托管静态页面
app.use(express.static('./pages'));
//自定义解析Post提交过来的表单数据
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
//登录API接口
app.post('/api/login',(req,res)=>{
//判断用户提交的登录信息是否正确
console.log(req.body)
if(req.body.username!=="admin"||req.body.password!=="888888"){
return res.send({status:1,msg:'登录失败'})
}
//登录成功则把信息存储在Session中
console.log(req.body);
req.session.user=req.body;//用户登录信息
req.session.islogin=true;//用户登录状态
res.send({status:0,msg:'登录成功'})

})
//获取用户姓名的接口
app.get('/api/username',(req,res)=>{
//从Session中获取用户姓名响应给客户端
if(!req.session.islogin){
return res.send({status:1,msg:'fail'})
}
res.send({
status:0,
msg:'success',
username:req.session.user.username,
})

})
//退出登录的接口
app.post('/api/logout',(req,res)=>{
//清空当前客户端的session信息
req.session.destroy()
res.send({
status:0,
msg:'退出登录成功'
})

})
app.listen(80,()=>{
console.log('运行在http://127.0.0.1')
})


JWT认证机制:

工作原理

jwt

jwt组成部分:

头部.有效荷载.签名

Header.PayLoad.Signature

头部由两部分组成:令牌类型即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA

例如:

1
2
3
4
5
{
"alg":"HS256",
"typ":"JWT"

}

这个JSON被Base64Url编码形成JWT第一部分

有效载荷:

包含声明,声明式关于实体(通常是用户)和附加数据的陈述,

身份认证中:

当用户使用凭据成功登录后,将返回一个JSON Web Token,由于token是凭据,因此要小心出现安全问题,通常,不应该将令牌保留超过所需的时间,也不应该将敏感数据存储在浏览器存储中,token在Authorization标头中发送,则跨域资源共享不会成为问题,因为它不使用cookie

客户端收到服务器返回的JWT后,通常会将它存储在localStorage或者sessionStorage中,此后客户端每次与服务端通信,都要带上这个JWT的字符串,进行身份认证,推荐把JWT放在Http请求头的Authorization字段中

Authorization:Bearer

为什么使用JWT:

应用程序可以使用访问access token去访问受保护的资源,比如一个接口

在Express中使用JWT,express-jwt会自动把JWT的payload部分赋值于req.user,方便逻辑部分调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const express=require('express');
const app=express();
const cors=require('cors');
//导入用于生成jwt字符串的包
const jwt=require('jsonwebtoken');
//导入用于将客户端发送过来的JWT字符串解析还原成JSON对象的包
const expressJWT=require("express-jwt");
//秘钥的本质是字符串
const secretkey='xiaochunzuishuai^_^';
//以/api/开头的不用权限,配置成功了express-jwt这个中间件,就可以把解析出来的用户信息挂载到req.user上
app.use(expressJWT({
secret:secretkey,
algorithms: ['HS256'],
}).unless({path:[/^\/api\//]}))
const bodyParser=require('body-parser')
app.use(cors());

//托管静态页面
app.use(express.static('./pages'));
//自定义解析Post提交过来的表单数据
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
//登录API接口
app.post('/api/login',(req,res)=>{
//判断用户提交的登录信息是否正确
const userInfo=req.body;
if(userInfo.username!=="admin"||userInfo.password!=="888888"){
return res.send({status:1,msg:'登录失败'})
}
//登录成功生成JWT字符串,通过token属性响应给客户端
res.send({
satus:0,
msg:'success',
//不要把密码加密到token字符串中
token:jwt.sign({username:userInfo.username},secretkey,{expiresIn:'100s'}),
data:req.user
})
})
//有权限的接口,配置成功了express-jwt这个中间件,有权限的接口就可以通过req.user获取解析出来的用户信息
app.get('/admin/getInfo',(req,res)=>{
res.send({
status:0,
msg:'success',
data:req.user
})

})
//退出登录的接口
app.post('/api/logout',(req,res)=>{
res.send({
status:0,
msg:'退出登录成功'
})

})
//配置全局错误处理中间件
app.use((err,req,res,next)=>{
//token解析失败导致的错误,Token过期或不合法
if(err.name==='UnauthorizedError'){
return res.send({status:401,message:'无效的token'})
}
//其他原因导致的错误
res.send({status:500,message:'未知错误'})

})
app.listen(80,()=>{
console.log('运行在http://127.0.0.1')
})

Web开发模式:

服务端渲染的Web开发模式

服务器发送给客户端的HTML页面是通在服务器通过字符串的拼接动态生成的。因此客户端不需要通过Ajax技术额外请求页面的数据

优点:

  1. 前端耗时少,因为服务端通过动态生成HTML内容,浏览器只需要直接渲染页面即可,尤其是移动端,更省电
  2. 有利于SEO,服务器响应的是完整的HTML页面,所以爬虫更容易获取信息,有利于SEO

缺点:

  1. 占用服务器资源,如果请求较多,会对服务器造成一定压力
  2. 不利于前后端分离,开发效率低。使用服务端渲染,则无法进行分工合作,尤其对于前端复杂的项目,不利于羡慕高效开发

前后端分离的Web开发模式:

依赖于Ajax技术,后端负责接口开发,前端使用Ajav=x调用接口的开发模式

优点:

  1. 开发体验好,前端专注于UI开发,后端专注于接口开发
  2. 用户体验好,Ajax技术的应用,提高用户的体验,轻松实现局部页面刷新
  3. 减轻服务端的渲染压力,页面最终是在浏览器中生成的

缺点:

不利于SEO,完整的HTML页面需要在客户端动态拼接,所有爬虫无法提取页面有效信息,解决方法:利用Vue,React等前端框架的SSR技术解决

如何选择Web开发模式:

  • 看业务场景
  • 主要功能是展示页面且没有复杂交互并且需要良好的SEO,用服务端渲染
  • 后台管理项目等交互性强用前后端分离
  • 一般两者都会一起用