跳到主要内容

6 篇博文 含有标签「docusaurus」

查看所有标签

· 阅读需 5 分钟
Li Dongze
  • 当我们习惯在node中编写代码的方式后,再回到html,css,js的编写中会感到各种不适应
  • 通过构建工具能将使用ESM编写规范的代码转换为旧的JS语法,这样可以使得所有的浏览器都支持代码

Webpack

  • 使用步骤:

    1. 初始化项目yarn init -y
    2. 安装依赖webpackwebpack-cli
    3. 在项目中创建src目录,然后编写代码(index.js)
    4. 执行yarn webpack来对代码进行打包(打包后观察 dist 目录)
  • 配置文件(webpack.config.js)

    const path = require("path")
    module.exports = {
    mode: "production", // 设置打包的模式,production表示生产模式,development表示开发模式
    entry: "./src/index.js", // 用来指定打包时的主文件,默认 ./src/index.js
    output: {
    filename: '', // 打包后的文件名
    clear: true
    },
    module: {
    rules: [
    {
    test: /\.css$/i,
    use: ["style-loader", "css-loader"]
    }
    ]
    }
    }

loader
  • webpack默认情况下,只会处理js文件,如果我们希望它可以处理其他类型的文件,则要为其引入loader

css为例

  • 使用css-loader可以处理js中的样式

  • 使用步骤

    1. 安装:yarn add css-loader -D

    2. 配置:

      //  在webpack.config.js中配置
      module:{
      rules:[
      {
      test:/\.css$/i,
      use: ["style-loader","css-loader"] // loader的执行顺序时从后往前执行
      },
      {
      test:/\.jpg$/i,
      type:"asset/resource" // 图片直接资源类型的数据,可以通过指定type来处理
      }
      ]
      }
bable
  • 在编写js代码时,经常需要使用一些js中的新特性,而新特性在旧的浏览器中兼容性并不好。此时就导致我们无法使用一些新的特性。

  • 但是我们现在希望能够使用新的特性,我们可以采用折中的方案。依然使用新特性编写代码,但是代码编写完成时我们可以通过一些工具将新代码转换为旧代码。

  • babel就是这样一个工具,可以将新的js语法转换为旧的js,以提高代码的兼容性。

  • 我们如果希望在webpack支持babel,则需要向webpack中引入babel的loader

  • 使用步骤

    1. 安装 npm install -D babel-loader @babel/core @babel/preset-env

    2. 配置:

      module: {
      rules: [
      {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
      loader: 'babel-loader',
      options: {
      presets: ['@babel/preset-env']
      }
      }
      }
      ]
      }
    3. package.json 中设置兼容列表

      "browserslist": [
      "defaults"
      ]

      https://github.com/browserslist/browserslist

插件(plugin)
  • 插件用来为webpack来扩展功能

  • html-webpack-plugin

    • 这个插件可以在打包代码后,自动在打包目录生成html页面

    • 使用步骤:

      1. 安装依赖
      npm install --save-dev html-webpack-plugin
      1. 配置插件
      plugins: [
      new HTMLPlugin({
      // title: "Hello Webpack",
      template: "./src/index.html"
      })
      ]
开发服务器(webpack-dev-server)
- 安装:
- `yarn add -D webpack-dev-server`
- 启动:`yarn webpack serve --open`
  • devtool:"inline-source-map"配置源码的映射

Vite

  • Vite也是前端的构建工具

  • 相较于web pack,Vite采用了不同的运行方式

    • 开发时不对代码打包,而是直接采用ESM的方式来运行项目
    • 在部署项目时,再对项目进行打包
  • 除了速度快之外,Vite使用起来也更加方便

  • 基本使用:

    1. 安装开发依赖 vite

    2. vite的源码目录就是项目根目录

    3. 开发命令:

      vite 启动开发服务器

      vite build 打包代码

      vite preview 预览打包后代码

  • 使用命令构建

    npm create vite@latest
    yarn create vite
    pnpm create vite
  • 配置文件:vite.config.js

  • 格式:

    import { defineConfig } from "vite"
    import legacy from "@vitejs/plugin-legacy"

    export default defineConfig({
    plugins: [
    legacy({
    targets: ["defaults"]
    })
    ]
    })

· 阅读需 41 分钟
Li Dongze

安装:

1、官网直接安装:

(http://nodejs.p2hp.com/)

2、使用nvm工具安装:

Releases · coreybutler/nvm-windows (github.com)

nvm -v      #查看nvm的版本
nvm list #查看当前系统安装Node.js版本
nvm install 版本 #安装指定版本的Node

#配置nvm镜像服务器
nvm node_mirror https://npmmirror.com/mirrors/node/

#安装最新版
nvm install latest

#安装长期维护版
nvm install lts

nvm use 版本 #指定要使用node的版本

同步和异步

进程和线程:

-进程(厂房): 程序运行的环境 -线程(工人): 实际进行运算的东西

同步:

  • 通常代码都是自上向下执行
  • 前面的代码不执行的话,后面的代码也不会执行
  • 同步的代码执行会出现阻塞的情况
  • 一行代码执行慢会影响整个程序的执行
function sum(a, b) {
const begin = Date.now()
while (Date.now() - begin < 10000) {}
return a + b
}
console.log(1111);
const result = sum(123, 456)
console.log(result);
console.log(2222);

//代码会先输出1111,等待十秒后依次输出578 2222

解决同步问题:

​ Java,Python 通过多线程来解决

​ Node.js 通过异步来解决

异步:

  • 需要通过回调函数来返回结果
  • 不会阻塞其他代码的执行

异步的问题:

  • 代码的可读性差

  • 无法通过return来设置返回值,可以通过回调函数来实现,复杂的回调函数会出现 " 回调地狱 "

Promise:

promise可以存储异步调用的数据,可以帮助结局异步中回调函数的问题

有一套特殊的存储数据的方式,可以存储异步调用的结果

创建Promise:
//创建Promise时,构造函数中需要一个函数作为参数
/*Promise构造函数的回调函数,会在创建Promise时调用,调用时会有两个参数传递进去*/
const promise = new Promise((resolve, reject)=>{
//resolve 和 reject是两个函数,通过它们可以向Promise中存储数据
})

//resolve 在执行正常时存储数据
//reject 在执行错误时存储数据

通过函数向Promise中添加数据,好处是可以添加异步调用的数据

从Promise中读取数据:

可以通过Promise的实例方法then来读取Promise中存储的数据

then需要两个回调函数作为参数,回调函数用来获取Promise中的数据

  • 通过resolve存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码
  • 通过reject存储的数据或者出现异常时,会调用第二个函数返回,可以在第二个函数中编写处理异常的代码
const promise = new Promise((resolve,reject) => {
resolve('Test')
})

const p1 = promise.then(result => {
console.log(result)
},reason => {
console.log('错误')
})
Promise中维护的两个隐藏属性:
  1. PromiseResult:

    ​ - 用来存储数据

  2. PromiseState:

    ​ - 用来记录Promise的状态(三种状态)

    • pending(进行中)

    • fulfilled(完成)通过resolve存储数据时

    • rejected(拒绝,出错了)出错或者通过reject存储数据时

    State只能修改一次,修改后永远不会再变

流程:

当Promise创建时,PromiseState初始值变为pending

​ 通过resolve存储数据时,PromiseState变为fulfilled,PromiseResult变为存储的数据

​ 通过reject存储数据或出错时时,PromiseState变为rejected,PromiseResult变为存储的数据或者异常对象

当我们通过then读取数据时,相当于为Peomise设置了回调函数

​ 如果PeomiseState变为fulfilled,则调用then的第一个回调函数来返回数据

​ 如果PeomiseState变为rejected,则调用then的第二个回调函数来返回数据

catch():

catch()的用法与then类似,但是只需要一个回调函数作为参数,该回调函数只会在Promise被拒绝时调用

catch()相当于then(null,reason =>{})

then(null,reason =>{})

catch()就是一个专门处理Promise异常的方法

finally():

无论时正常处理数据还是出现异常,finally总会执行

finally()的回调函数中不会接受到数据

finally()通常用来编写一些无论成功与否都要执行的代码

promise中的then() catch()都会返回一个新的Promise,finally()的返回值则不会存储到新的promise里

Promise中的静态方法:

resolve()

Promise.resolve(10)     //创建一个立即执行的Promise
//等价于
new Promise((resolve,reject) => {
resolve(10)
})

reject()

Promise.reject('错误')        //创建一个立刻拒绝的Peomise

Promise.all([...]

Promise.all([...])      //同时返回多个Promise的执行结果

Promise.race([...]

Promise.race([...])     
//race会返回首先执行完的Promise,而忽略其他未执行完的Promise

Promise.any([...]

any会race类似,但是它只会返回第一个成功的Promise,如果所有的Promise都失败才会返回一个错误信息。

宏任务队列、微任务队列:

  • 任务队列是将要执行的代码
  • 当调用栈中的代码执行完毕后,队列中的代码才会按次序依次进入栈中执行
  • 在JS中任务队列有俩种:
    • 宏任务队列:大部分代码都在宏任务队列中排队
    • 微任务队列:Promise的回调函数(then、catch、finally)
  • 整个流程
    1. 执行调用栈中的代码
    2. 执行微任务队列中的所有任务
    3. 执行宏任务队列中的所有任务
queueMicrotask():
  • ​ 向微任务队列中添加任务

自定义Promise:


const PROMISE_STATE = {
PENDING: 0,
FULFILLED: 1,
REJECTED: 2
}

class MyPromise {

// 创建一个变量存储Promise的结果
#result

// 创建变量记录Promise的状态
#state = PROMISE_STATE.PENDING

//创建一个变量存储回调函数,回调函数可能有多个,使用数组来存储回调函数
#callbacks = []

constructor(executor) {
// 接收一个执行器作为参数
executor(this.#resolve.bind(this), this.#reject.bind(this)) // 调用回调函数
}

#resolve(value) {
// 禁止值被重复秀发i
// 若state不为pending,说明值已经被修改
if (this.#state !== PROMISE_STATE.PENDING) return


this.#result = value
this.#state = PROMISE_STATE.FULFILLED

//当resolve执行时,说明数据已经进来了,需要调用then的回调函数
queueMicrotask(()=>{
// 调用callbacks中的所有函数
this.#callbacks.forEach(cb => {
cb()
})
})
}


#reject(reason) {

}

// 添加读取数据的then方法
then(onFulfilled, onRejected) {
/*
then中回调函数的返回值,会成为新的Promise中的数据
*/

return new MyPromise((resolve,reject)=>{
if(this.#state === PROMISE_STATE.PENDING){
// 进入判断说明数据还没进入Promise,将回调函数设置为callback
this.#callbacks.push(()=>{
resolve(onFulfilled(this.#result))
})
}else if (this.#state === PROMISE_STATE.FULFILLED) {
queueMicrotask(()=>{
resolve(onFulfilled(this.#result))
})
}
})


}
}

const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('孙悟空')
}, 1000);

})

console.log(mp);

const p = mp.then((result) => {
console.log('读取数据1', result);
return '猪八戒'
}).then(r=>{
console.log('读取数据2',r);
return '沙和尚'
}).then(r=>{
console.log('读取数据3',r);
})

输出结果:

MyPromise {#resolve: ƒ, #reject: ƒ, #result: undefined, #state: 0, #callbacks: Array(0)}
读取数据1 孙悟空
读取数据2 猪八戒
读取数据3 沙和尚

async和await:

通过async可以快速创建异步函数,异步函数的返回值会自动封装到一个Peomise中返回

async function fn(){
return 10
}

let result = fn()

fn().then(r=>{
console.log(r)
})

当通过await去调用异步函数时,他会停止代码的运作,知道异步代码执行有结果时才将结果返回

function sum(a,b){
return new Promise(resolve => {
setTimeout(() => {
resolve(a + b)
}, 2000);
})
}

async function fn(){
let result = await sum(123,456)
console.log(result);
}

fn()
//结果返回 579

//若不加await,result是一个Promise对象

await只能用于async声明的异步函数中,或es模块的顶级作用域中

  • await阻塞的只是异步函数内部的代码,不会影响外部代码
  • 若async声明的函数中没有写await,那么它里面就会依次执行
  • 当我们使用await调用函数后,当前函数后面所有的代码会在当前函数执行完毕后,放入微任务队列中

模块化

早期的网页中,没有一个实质的模块化规范,实现模块化的方式时最原始的通过Script标签来引入多个js文件,于是会出现一些问题

  1. 无法选择要引入模块的哪些内容

  2. 在复杂的模块场景下容易出错

    ...

在Node.js中,默认支持的模块化规范是CommonJS,在CommonJS中一个js文件就是一个模块

CommonJS规范:
  • 引入模块
    • 使用require('模块的路径')函数来引入模块
    • 引入自定义模块时
      • 模块要以 ./ 或 ../ 开头
require('./m1.js')

在定义模块时,模块中的内容默认是不被外部看到的,但可以通过exports来设置向外部暴露的内容

//访问exports的方式有两种
1. exprots
2. module.exports
exports === module.exprots true
//当我们在其他模块中引入当前模块时,require函数返回的就是exports

//可以将希望暴露给外部模块的内容设置为exports的属性,可以通过exports一个一个导出值
exports.a = '孙悟空'
exports.b = '猪八戒'

//也可以它通过module.exports同时导出多个值
module.exports = {
a:'猪八戒',
b:[1,3,5,7,9],
c:()=>{
console.log(123)
}
}
  • 引入核心模块时
    • 直接写核心模块名字即可
    • 也可以在核心模块前添加 node:
require('path')
require('node:path')

默认情况下,Node.js会将以下内容视为CommonJS模块:

  1. 使用.cjs为扩展名的文件
  2. 当前的package.json的type属性为commonjs时,扩展名为.js的文件
  3. 当前的package.json不包含type属性时,扩展名为.js的文件
  4. 文件的扩展名是mjs、cjs、json、node、js以外的值时(type不是module时)

所有的CommonJS的模块都会被包装到一个函数中

(function(exports, require, module, __filename, __dirname) {
// 模块代码会被放到这里
});
ES模块化:

默认情况下,node.js中的模块化标准时CommonJS,若想使用ES的模块化,可以采用以下两种方案:

  1. 使用mjs作为拓展名
  2. 修改package.json将模块化规范设置为ES模块
{
"type": "module"
}

// 当前项目下所有的js文件都默认为ES module

导入es模块不能省略拓展名

import './m4.mjs'
  • 通过ES模块化导入的内容都是常量
  • ES模块都是在严格模式下运行的
  • ES模块化在浏览器中同样支持,但是通常我们不会直接使用,通常都会结合打包工具使用

核心模块

  • 核心模块是node中自带的模块,可以再node中直接使用
  • window是浏览器的宿主对象,在node中是没有的
  • global是node中的全局对象,作用类似于window
  • ES标准下,全局对象的标准名应该是globalThis
process:
  • 表示当前的node进程
  • 通过该对象可以获取进程的信息,或者对进程做各种操作
  • 如何使用
    1. process是一个全局变量,可以直接使用
    2. 有哪些属性方法:
      1. process.exit()
        • 结束当前进程,终止node
      2. process.nextTick(callback[, ...args])
        • 将函数插入到tick队列中
        • tick中的代码会再下一次事件循环之前执行,就是说会在微任务队列和宏任务队列中任务之前执行
        • 代码的执行顺序:调用栈 -> tick队列 -> 微任务队列 -> 宏任务队列
path:
  • 表示的路径

  • 通过path可以用来获取各种路径

  • 要使用path需要先对其进行引入

  • 方法:

    • path.resolve([...paths])

      • 用来生成一个绝对路径

      • 若直接调用resolve,则返回当前的工作目录

      • const path = require('node:path')
        const p = path.resolve()
        console.log(p);

        //输出
        c:\Users\LDZ\Desktop\Node
      • 注意,我们通过不同的方式执行代码时,他的工作目录是有可能发生变化的

      • 如果将一个相对路径作为参数,则resolve会自动将其转换为绝对路径,此时根据工作目录的不同,它产生的绝对路径也不同,一般会将一个绝对路径作为第一个参数,一个相对路径作为第二个参数,这样它会自动计算出最终的路径

最终形态

const path = require('node:path')
const result = path.resolve(__dirname,'./hello.js')
console.log(result);

//这样不会因为调用方式的改变而发生变化

//在使用路径时,尽量通过path.resolve()来生产路径
fs:

File System,帮助node来操作磁盘中的文件

  • 文件操作就是所谓的 I/O 操作

  • 使用fs模块也需要引入

  • const fs = require('node:fs')
  1. fs.readFileSync()

    • const fs = require('node:fs')
      const path = require('node:path')

      const buf = fs.readFileSync(path.resolve(__dirname,'./hello.txt'))
      console.log(buf.toString());
    • fs.readFileSync()同步读取文件的方法,会诸塞后面代码的执行

    • 当我们通过fs模块读取磁盘中的数据时,读取道德数据总会以Buffer(缓冲区)对象的形式返回

    • Buffer(19) [84, 111, 100, 97, 121, 32, 105, 115, 32, 97, 32, 110, 105, 99, 101, 32, 100, 97, 121, buffer: ArrayBuffer(8192), byteLength: 19, byteOffset: 704, length: 19, Symbol(Symbol.toStringTag): 'Uint8Array']
  2. fs.readFile()

    • 异步读取文件的方法

    • 需要传入回调函数

    • const fs = require('node:fs')
      const path = require('node:path')

      fs.readFile(path.resolve(__dirname,'./hello.txt'),(error,buffer)=>{
      if(error){
      console.log('出现错误');
      }else{
      console.log(buffer.toString());
      }
      })
    • 不会阻塞后续代码的执行

Promise版本的fs的方法:
const path = require('node:path')
const fs = require('node:fs/promises')
const { buffer } = require('stream/consumers')

fs.readFile(path.resolve(__dirname,'./hello.txt')).then(buffer=>{
console.log(buffer.toString());
}).catch(e=>{
console.log('出现错误');
})

或者async,await语法糖的方式

(async ()=>{
try{
const buffer = await fs.readFile(path.resolve(__dirname,'./hello.txt'))
console.log(buffer.toString());
}catch(e){
console.log('出错误了');
}
})()
  • fs.readFile() 读取文件
  • fs.appendFile() 创建新文件,或将数据添加到已有文件中

S

  • fs.mkdir() 创建目录

    • const fs = require('node:fs/promises')
      const path = require('node:path')
      fs.mkdir(path.resolve(__dirname,'./hello')).then(r=>{
      console.log('Success');
      }).catch(err => {
      console.log('创建失败');
      })
    • mkdir可以接受一个配置对象作为第二个参数,通过该对象可以对方法的功能进行配置
  • fs.rmdir() 删除目录

    • const fs = require('node:fs/promises')
      const path = require('node:path')
      fs.rmdir(path.resolve(__dirname,'./hello')).then(r=>{
      console.log('Success');
      }).catch(err => {
      console.log('删除失败');
      })
  • fs.rm() 删除文件

  • fs.rename() 重命名

  • fs.copyFile() 复制文件

包管理器

packfage.josn
  • 是包的描述文件
  • node中通过该文件对项目进行描述
  • 每个node项目必须有package.json
    • scripts:
      • 可以自定义一些命令
      • 定义以后可以直接通过npm来执行这些命令
      • start 和 test 可以直接通过npm start 和 npm test执行
      • 其他命令需要通过npm run xxx 执行
npm命令
npm init        #初始化项目,创建package.json
npm init -y #初始化项目,创建package.json(所有值都采用默认值)
npm install/可简写'i' 包名 #将指定包下载到当前项目
#install发生了
1.将包下载到当前项目的node_modeules目录下
2.会在package.json中的"dependencies"属性中添加一个新属性
3.会自动添加package-lock.json文件(帮助加速npm下载,不用去动它)

npm install '包名' -g #全局安装,将包安装到计算机中,全局安装的通常都是一些工具
npm uninstall '包名' #卸载
npm镜像:

npm的服务器位于国外,有时访问速度会比较慢,可以通过配置国内镜像来解决该问题,配置

1.安装cnpm(不推荐使用,容易出问题)

npm install -g cnpm --registry=https://registry.npmmirror.com

2.彻底修改npm仓库地址

npm set registry https://registry.npmmirror.com

还原到原版仓库

npm config delete registry
Yarn:

在新版本的node中,corepack中已经包含了yarn,可以通过启用corepack的方式使yarn启用。首先执行以下命令启用corepack:

corepack enable

查看yarn版本

yarn -v

若出现报错

无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本

#管理员打开终端输入
set-ExecutionPolicy RemoteSigned

切换yarn版本,最新版:

corepack prepare yarn@stable --activate

切换为1.x.x的版本:

corepack prepare yarn@1 --activate

Yarn命令

yarn init (初始化,创建package.json)

yarn add xxx(添加依赖)

yarn add xxx -D(添加开发依赖)

yarn remove xxx(移除包)

yarn(自动安装依赖)

yarn run(执行自定义脚本)

yarn <指令>(执行自定义脚本)

yarn global add(全局安装)

yarn global remove(全局移除)

yarn global bin(全局安装目录)

HTTP协议:

  1. 当在浏览器中输入地址之后发生了什么

    https://www.lilichao.com/

    https:// #协议名
    www.lilichao.com #域名 网络中存在着无数个服务器,每一个服务器都有它自己的唯一标识,这个标识被称为ip地址192.168.1.17 但是ip地址不方便记忆

    1.DNS解析,获取网站的ip地址
    2.浏览器需要与服务器建立连接(tcp/ip) (三次握手)
    3.向服务器发送请求(http请求)
    4.服务器处理请求,并返回相应(http协议)
    5.浏览器将响应的页面渲染
    6.断开和服务器的连接(四次挥手)
  2. 客户端如何与服务器建立(断开)连接

    • 通过三次握手和四次挥手
      • 三次握手是客户端向服务器建立连接的过程
        1. 客户端向服务器发送连接请求 SYN
        2. 服务器收到连接请求,向客户端返回信息 SYN ACK
        3. 客户端向服务器发送同意连接的信息 ACK
      • 四次挥手
        1. 客户端向服务器发送请求,通知服务器数据发送完毕,请求断开链接 FIN
        2. 服务器向客户端返回数据,表示知道了 ACK
        3. 服务器向客户端返回数据,表示收完了可以断开连接 FIN ACK
        4. 客户端向服务器发数据,可以断开连接 ACK

请求和响应实际上就是一段数据,只是这段数据需要准寻一个特殊的格式,这个格式由HTTP协议来规定

TCP/IP 协议族
  • TCP/IP协议族中包含一组协议,这组协议规定了互联网中所有的通信的细节
  • 网络通信的过程由四层组成
    1. 应用层(软件的层面,浏览器,服务器都属于应用层)
    2. 传输层(负责对数据进行拆分,把大数据拆分为一个一个小包)
    3. 网络层(负责给数据包,添加信息)
    4. 数据链路层(传输信息)

HTTP协议就是应用层的协议,用来规定客户端和服务器间通信的报文格式

什么是报文

  • 浏览器和服务器之间通信是基于请求和响应的
  • 浏览器向服务器发送请求(request)
  • 服务器向浏览器返回响应(response)
  • 浏览器向服务器发送请求相当于给服务器写信,服务器向浏览器返回响应相当于服务器给浏览器回信,这个信在HTTP协议中被称为报文
请求报文(request)
  • 客户端发送给服务器的报文称为请求报文
  • 请求报文的格式如下:
    • 请求首行
    • 请求头
    • 空行
    • 请求体
请求首行

第一部分

GET /05_HTTP%E5%8D%8F%E8%AE%AE/http%E5%8D%8F%E8%AE%AE.html?username=1 HTTP/1.1
  • get请求主要是用来向服务器请求资源
  • post请求主要用来向服务器发送数据

第二部分

/target.html?username=sunwukong HTTP/1.1
?username=admin&password=123123
  • 表示请求资源的路径
    • ?后面的内容叫查询字符串
    • 查询字符串是一个名值对结构,一个名值对应一个值,使用 = 连接,多个名值对之间使用&分割
    • get请求通过查询字符串将数据发送给服务器
      • 由于查询字符串会在浏览器地址栏中明文显示,所以它安全性较差
      • url地址长度有限,所以get请求无法发送较大的数据
    • post请求通过请求体发送数据
      • 在chrome中通过载荷可以查看
      • post请求通过请求体发送数据,无法在地址栏中直接查看,所以安全性较好(并不表示post请求就一定安全,HTTP协议都不安全)
      • 请求体的大小没有限制,可以发送任意大小的数据

第三部分

HTTP/1.1    //表示协议的版本
请求头
  • 请求头也是名值对结构,用来告诉服务器我们浏览器的信息
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Host: 127.0.0.1:5500
If-Modified-Since: Tue, 10 Jan 2023 17:07:46 GMT
If-None-Match: W/"12d-1859ca6f731"
Referer: http://127.0.0.1:5500/05_HTTP%E5%8D%8F%E8%AE%AE/http%E5%8D%8F%E8%AE%AE.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
  • Accept 浏览器可以接收的文件的类型
  • Accept-Encoding 浏览器允许的压缩的编码
  • Accept-Language 客户端浏览器可以接受的语言
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 
#用户代理,用来描述浏览器信息的字符串
空行
  • 用来分不分请求头和请求体
请求体
  • post请求通过请求体来发送数据
响应报文
HTTP/1.1 304 Not Modified       #响应首行   304为响应状态码   Not对响应状态码的描述
Vary: Origin
Access-Control-Allow-Credentials: true
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 10 Jan 2023 17:07:46 GMT
ETag: W/"12d-1859ca6f731"
Content-Type: text/html; charset=UTF-8
Content-Length: 1794
Date: Tue, 10 Jan 2023 18:20:35 GMT
Connection: keep-alive
Keep-Alive: timeout=5
  • 响应首行

    • 响应状态码的描述
      • 1xx 请求处理中
      • 2xx 表示成功
      • 3xx 表示请求的重定向
      • 4xx 表示客户端错误
      • 5xx 表示服务器的错误
  • 响应头

    • 响应头也是一个一个名值对的结构,用来告诉浏览器响应的信息

    • Content-Type: text/html; charset=UTF-8  #用来描述响应体的类型
      Content-Length: 1794 #用来描述响应体的大小
  • 空行

    • 用来分隔响应头和响应体
  • 响应体

    • 响应体就是服务器返回给客户端的内容

    • 网页、css、js、图片这些资源会作为响应报文中的响应体发送

    • <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      </head>
      <body>
      <h1>请求已经提交</h1>
      </body>
      </html>

Express

  • express是node中服务器软件,通过express可以快速在node中搭建一个web服务器
使用步骤:
1. 创建并初始化项目
1.使用yarn初始化
yarn init -y

2.使用npm初始化
npm init -y
2. 安装express
1.使用yarn安装
yarn add express

2.使用npm安装
npm i express
3. 创建 index.js 并编写代码
  //引入express
const express = require('express')

//获取服务器实例(对象)
const app = express()

//启动服务器
//app.listen(端口号)
app.listen(3000,()=>{
console.log('服务器已启动')
})
路由
  • 若希望服务器正常访问,则还需要为服务器设置路由,路由可以根据不同的请求方式和请求地址来处理用户的请求 app.METHOD(...)
get请求
app.get('/',(request,response,next)=>{
console.log('被访问');
})
// '/'表示根目录
  • 路由的回调函数执行时,会收到三个参数(request,response,next)

  • 在路由中应该做两件事

    1. 读取用户的请求(request)
    2. 根据用户的请求返回响应(resopnse)
  • response.sendStatus(404)     // 向客户端发送响应状态码
    response.status(200) // 用来设置响应状态码但是不发送
    response.send('This is my first server') // 设置并发送响应体

get发送请求的第二种方式

param
  app.get('/hello/:id/:name',(req,res)=>{
// 可以通过req.param属性来获取这些参数
console.log(req.params);
})
/*
/hello/:id/:name表示当用户访问 /hello/???/??? 时就会触发
在路径中以冒号命名的部分我们称为param,在get请求它可以被解析为请求参数
param传参一般不会传递特别复杂的参数
例如若访问 http://localhost:3000/hello/123/333 就会返回 {id: '123', name: '333'}
*/

post请求
  • 通过req.body来获取post请求的参数(请求体中的参数)

  • 默认情况下express不会自动解析请求体,需要通过中间件来为其增加功能

    • app.use(express.urlencoded())
      //或者
      app.use(express.urlencoded({extended:true}))
//  简单的登录例子
app.use(express.urlencoded())
app.post('/login',(req,res)=>{
console.log(req.body);
const username = req.body.username
const password = req.body.password
if(username === 'admin' && password === '123'){
res.send('<h1>登陆成功</h1>')
}else{
res.send('<h1>登陆失败</h1>')
}
})
启动服务器
//  启动服务器
// app.listen(端口号)
// 服务器启动后就可以通过3000端口来访问
// 协议名://ip地址:端口号/路径
// http://localhost:3000
// http://127.0.0.1:3000
app.listen(3000,()=>{
console.log('服务器已启动');
})
中间件
  • 在express中我们可以使用app.use()定义一个中间件,中间件的作用与路由很像
app.use('/',(req,res,next) => {
console.log('收到请求');
res.send('这是通过中间件返回的响应')
})

//next()不能再响应处理完毕后调用
静态资源(express.static)
  • 服务器中的代码对于外部来说是不可见的,所以我们写的html页面,浏览器无法直接访问,如果希望浏览器可以访问,则需要将页面所在的目录设置为静态资源目录
  • 设置static中间件后,浏览器访问时会自动去public目录寻找是否有匹配的静态资源
app.use(express.static('public'))   //此处设置public文件夹为静态资源目录

// 进入 http://localhost:3000 会默认访问 public 中的 index.html
nodemon
  • 代码修改后可以自动重启服务器

  • 安装nodemon模块

    • #全局安装
      1. npm i nodemon -g
      2. yarn global add nodemon #yarn进行全局安装时,默认yarn的目录不在环境变量中,需要手动改将路径添加到环境变量中


      #项目安装(-D为设置为开发依赖)
      1. npm i nodemon -D
      2. yarn add nodemon -D
  • 启动:

    • #全局安装时启动
      1. nodemon #运行index.js
      2. nodemon xxx #运行指定的js

      #项目安装时启动
      1. npx nodemon
      2. npx nodemon xxx
req.query
  • 该属性主要用于get()方法时传递参数使用,来获取查询字符串的数据
app.get('/xxx',(req,res)=>{
console.log(req.query.xxx)
})
req.body
  • 该属性主要用于post()方法时传递参数使用,注意,在默认情况下express不会自动解析请求体,需要通过中间件来为其增加功能
post登录

方案一

const express = require('express')
const path = require('path')
const app = express()

const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())

app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password

for(const user of USERS){
if(user.username === username){
// 用户存在
if(user.password === password){
res.send(`<h1>登陆成功 ${user.nickname}<h1>`)
return
}
}
}

res.send(`<h1>登陆失败<h1>`)
})

app.listen(3000,()=>{
console.log('Server is running');
})

方案二

const express = require('express')
const path = require('path')
const app = express()

const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())

app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password
const loginUser = USERS.find((item)=>{
return item.username === username && item.password === password
})
// console.log(loginUser);
if(loginUser){
res.send(`<h1>登陆成功 ${loginUser.nickname}<h1>`)
}else{
res.send(`<h1>登陆失败<h1>`)
}
})

app.listen(3000,()=>{
console.log('Server is running');
})
post注册
const express = require('express')
const path = require('path')
const app = express()

const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())

app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password

for(const user of USERS){
if(user.username === username){
// 用户存在
if(user.password === password){
res.send(`<h1>登陆成功 ${user.nickname}<h1>`)
return
}
}
}

res.send(`<h1>登陆失败<h1>`)

const loginUser = USERS.find((item)=>{
return item.username === username && item.password === password
})
// console.log(loginUser);
if(loginUser){
res.send(`<h1>登陆成功 ${loginUser.nickname}<h1>`)
}else{
res.send(`<h1>登陆失败<h1>`)
}
})

app.post('/register',(req,res)=>{
//获取用户输入的数据
const {username,password,repwd,nickname} = req.body
//验证用户名是否存在
const user = USERS.find(item => {
return item.username === username || item.nickname === nickname
})

console.log(user);

if(!user){
//进入判断说明用户不存在,可以注册
USERS.push({
username,
password,
nickname
})

res.send('<h1>注册成功</h1>')
}else{
res.send('注册失败')
}
})

app.listen(3000,()=>{
console.log('Server is running');
})
<body>
<h1>这是一个静态网页</h1>
<hr>
<h2>Regist</h2>
<form action="/register" method="post">
<div><input type="text" name="username" placeholder="用户名"></div>
<div><input type="password" name="password" placeholder="密码"></div>
<div><input type="password" name="repwd" placeholder="确认密码"></div>
<div><input type="text" name="nickname" placeholder="昵称"></div>
<div><input type="submit" value="注册"></div>
</form>
</body>

模板引擎

ejs
  • ejs是node中的一款模板引擎
  1. 安装ejs

    npm i ejs
  2. 配置express的模板引擎为ejs

    app.set('view engine','ejs')

注!模板引擎需要被express选然后才能使用

//  在路由中配置
res.render('students')
// res.render()用来渲染一个模板引擎,并将其返回给浏览器
// 可以将一个对象作为render的第二个参数传递,这样在模板中可以访问到对象的数据
  1. 配置模板路径

    app.set('文件夹名','文件夹路径')
    app.set('views',path.resolve(__dirname,'views'))
ejs语法
<%= %>  在ejs中输出内容时,会自动对字符串中的特殊符号进行转义,这样设计主要为了避免xss攻击
<%- %> 直接将内容输出,不会转义
<% %> 可以在其中直接编写js代码,js代码会在服务器中执行

ejs的注释方法

<%# console.log(123) %>

重定向 & res.redirect()

  • 重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由的路径一种重定向)

res.redirect()

  • res.redirect()函数重定向到具有指定状态的,从指定路径派生的URL,该状态为与HTTP状态码相对应的整数(正数)。默认状态为“找到302”。

Router

  • router是express创建的一个对象
  • router类似于一个中间件,所以你可以把在router实例化建立起的分支路由,作为一个文件引入到主文件中,用主文件中的保存变量作为一个参数给到app.use(),或者用于其他路由的.use()方法中。这样主文件中的app.use()就可以通过暴露出来的router进行路由衔接。
使用方法
  1. 创建routes文件夹并在该文件夹下创建你所需的路由,例如user.js 、goods.js等等

  2. 在相应文件中引入express和创建Router对象

    //  以user.js为例
    const express = require('express')
    const router = express.Router() // 创建router对象
    module.exports = router // 将router暴露出来
  3. 在index.js中引入路由并使路由生效

    1. 方法一
    const userRouter = require('./routes/user.js')
    app.use(userRouter) // 是userRouter生效


    2. 方法二
    app.use('/user','./routes/user')
    app.use('/goods','./routes/goods')
    // 这样引入生效可以不用再单独定义变量了
  4. 为防止不同路由中路径出现重复冲突的情况,可在使之生效时,在第一个参数填入不同路由的根路径

    app.use('/user',userRouter)
    app.use('/goods'goodsRouter)
    // 这样假如在访问不同 list路径时,/user/list 和 /goods/list 就不会冲突了
  5. 将配置路由编写在配置中间件代码下面时,上面的中间件路由可共享

    app.use(express.static(path.resolve(__dirname,'./public')))
    app.use(express.urlencoded({extended:true}))
    app.set("view engine",'ejs') // 配置express的模板引擎为ejs
    app.set("views", path.resolve(__dirname, "views"))

    // 使路由生效
    app.use(userRouter)
    // 上面所有的中间件userRouter可共享,不需再次引入
  • Cookie是HTTP协议中用来解决无状态问题的技术
  • Cookie本质就是一个头
    • 服务器以响应头的形式将Cookie发送给客户端,客户端收到后会将其存储,并在下次向服务器发送请求时将其传回,这样服务器就可以根据Cookie来识别客户端了
  1. 向客户端发送一个Cookie

res.cookie()

app.get('网络路径',(req,res)=>{
res.cookie('name','value')
req.send('Cookie以发送')
})
  1. 读取客户端发来的Cookie
  • 需要安装中间件来使得express可以解析Cookie

    1. 安装cookie-parser
    yarn add cookie-parser
    1. 引入
    const cookieParser = require('cookie-parser')
    1. 设置为中间件
    app.use(cookieParser())
  • Cookie是有有效期的,默认情况下有效期就是一次会话(一次打开到关闭浏览器的过程)

  • maxAge:设置Cookie的有效时间,单位:毫秒

Cookie一旦发送给浏览器就不能修改,但可以发送新的Cookie来替换旧的Cookie,来达到修改的目的

Cookie的不足
  1. cookie是由服务器创建,浏览器保存,每次浏览器访问服务器时都需要将cookie发回,导致不能在cookie中存放较多的数据
  2. cookie是直接存储在客户端,容易被篡改盗用
  3. cookie中一定不能存储敏感数据

Session

  • session 是服务器中的一个对象,这个对象用来存储用户的数据
  • 每一个session对象都有一个唯一的id,id会通过cookie的形式发送给客户端
  • 客户端每次访问时只需将存储又id的cookie发回即可获取它在服务器中存储的数据
  • 在express 可以通过 express-session 组件来实现session功能
使用方法
  1. 安装

    yarn add express-session
  2. 引入

    const session = require('express-session')
  3. 设置为中间件

    app.use(session({
    secret:'hello' // secret 是防止session不被盗取和篡改,可以随便设置内容
    }))
session的失效
  1. 浏览器的Cookie失效
  2. 服务器中的session对象没了
    • express-session默认是将session存储到内存中的,所有服务器一旦重启,session会自动重置

CSRF 攻击

  • 跨站请求伪造

· 阅读需 1 分钟
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

· 阅读需 1 分钟
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet