跳到主要内容

Node.js

安装:

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 攻击

  • 跨站请求伪造