什么是Node.js

446 阅读36分钟

什么是node.js

node.js是一个基于Chrome V8 引擎的JavaScript运行环境

注意:

  1. 浏览器是JavaScript 的前端运行环境

  2. Node,js 是 JavaScript 的后端运行环境

  3. Node.js 中无法调用 DOM 和 BOM等浏览器内置 API

    因为node.js是一个独立的运行环境,在Node.js运行环境中,它只提供与node相关的API,没有提供浏览器的那些

Node.js中的JavaScript运行环境

image.png image.png

Node.js 可以做什么

Node.js 作为一个JavaScript 的运行环境,仅仅提供了基础的功能和 API。然而,基于 Node.js 提供的这些基础功能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了 Node.js,可以让前端程序员胜任更多的工作和岗位:

  1. 基于Express 框架(www.expressjs.com.cn/),可以快速构建 Web 应用
  2. 基于 Electron 框架(electronjs.org/),可以构建跨平台的桌面应用
  3. 基于restify 框架(restify.com/),可以快速构建 API 接口项目
  4. 读写和操作数据库、创建实用的命令行工具辅助前端开发...

在node.js中运行js代码

  1. cd + js文件所在路径 (cd:切换)
  2. 再输入node + js文件名 image.png
  3. 或者直接在文件那里 按住shift + 鼠标右键,点击“在此处打开Powershell窗口 image.png

终端中的快捷键

在Windows的 powershell或cmd 终端中,我们可以通过如下快捷键,来提高终端的操作效率:

  1. 使用(向上箭头)键,可以快速定位到上一次执行的命令
  2. 使用 tab 键,能够快速补全路径
  3. 使用esc键,能够快速清空当前已输入的命令
  4. 输入 cls 命令,可以清空终端

fs文件系统模块

什么是fs文件系统模块

fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求例如:

  • fs.readFile()方法,用来读取指定文件中的内容
  • fs.writeFile() 方法,用来向指定的文件中写入内容
  • 如果要在JavaScript代码中,使用fs 模块来操作文件,则需要使用如下的方式先导入它: const fs = require('fs')

fs.readFile()的语法格式

fs.readFile(path[,options],callback)

中间被中括号包起来的参数,表示是可选参数项,其他没有中括号的表示为必选参数

  • 参数1:path 必选参数,字符串,表示文件的路径
  • 参数2:[,options] 可选参数,表示以什么编码格式来读取文件
  • 参数3:callback 必选参数,文件读取完成后,通过回调函数拿到读取的结果

fs.writeFile()的语法格式

fs.writeFile(file,data[,options],callback)

  • 参数1:file 必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
  • 参数2:data 必选参数,表示要写入的内容
  • 参数3:[,options] 可选参数,表示以什么编码格式来写入文件,默认值是utf8
  • 参数4:callback 必选参数,文件写入完成后的回调函数

路径动态拼接的问题

在使用 fs 模块操作文件时,如果提供的操作路径是以./或../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。

原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径

解决方案:在使用fs模块操作文件时,直接提供完整的路径(但移植性差,不利于维护?),不要提供./或./开头的相对路径,从而防止路径动态拼接的问题

image.png 完美方案:使用__dirname + '路径'

image.png

path路径模块

什么是path 路径模块

path 模块是 Node.is 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。 例如:

  • path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串
  • path.basename() 方法,用来从路径字符串中,将文件名解析出来

如果要在JavaScript代码中,使用 path 模块来处理路径,则需要使用如下的方式先导入它:

const path = require('path');

path.join()的语法格式

将多个路径片段拼接成一个完整的路径字符串

格式:

path.join([...paths])

参数:

...paths路径片段的序列

返回值:

演示:

const { log } = require('console');
const path = require('path');
//注意:'../'会抵消前面的路径
const pathStr = path.join('/a','/b/c','../','./d','e')
console.log(pathStr);//\a\b\d\e  抵消掉了/c

注意:

今后凡是涉及到路径拼接的操作,建议使用 path.join() 方法进行处理。不直接使用 + 进行字符串的拼接

image.png

path.basename()的语法格式

从一个文件路径中,获取到文件的名称部分

格式:

path.basename(path[,ext])

参数:

  • path必选参数,表示一个路径的字符串
  • ext可选参数,表示文件扩展名
  • 返回表示路径中的最后一部分

代码:

const path = require('path');
​
//定义文件存放路径
const fpath = '/a/b/c/index.html'
​
//打印出来有后缀
// const fulName = path.basename(fpath);
// console.log(fulName);//index.html
​
//不想要后缀:
const nameWithoutExt = path.basename(fpath,'.html');
console.log(nameWithoutExt);//index

path.extname()的语法格式

可以获取路径中的扩展名部分

格式:

path.extname(path)

参数:

path 必选参数,表示一个路径的字符串

返回:返回得到的扩展名字符串

代码:

const path = require('path');
​
//这是文件的存放路径
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath);
console.log(fext);//.html

案例:

注意:

// fs.writeFile()方法只能用来创建文件,不能用来创建路径

// 重复调用fswriteFile()写入同一个文件,新写入的内容会覆盖之前的旧内容

http模块

什么是http模块

回顾:什么是客户端、什么是服务器?

  • 在网络节点中,负责消费资源的电脑,叫做客户端;

  • 负责对外提供网络资源的电脑,叫做服务器 http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台Web 服务器,从而对外提供Web 源服务 如果要希望使用http 模块创建Web服务器,则需要先导入它:

    const http = require('http')

进一步理解 http 模块的作用

服务器和普通电脑的区别在于,服务器上安装了 web 服务器软件,例如: IIS、Apache 等。通过安装这些服务器软件就能把一台普通的电脑变成一台 web 服务器 在 Node.js 中,我们不需要使用 llS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 node.js 提供的http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务。

服务器相关的概念

1.IP 地址

IP 地址就是互联网上每台计算机的唯一地址,因此IP 地址具有唯一性。如果把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”,只有在知道对方IP 地址的前提下,才能与对应的电脑之间进行数据通信.IP 地址的格式:通常用“点分十进制”表示成(a.b.c.d) 的形式,其中a,b,c,d 都是0~255 之间的十进制整数。如:用点分十进表示的IP地址(192.168.1.1)

注意:
  • 互联网中每台 Web 服务器,都有自己的 IP 地址,例如: 可在 Windows 的终端中运行 ping www.baidu.com 命令,即可查看到百度服务器的IP 地址
  • 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入 127.0.0.1这IP 地址,就能把自己的电脑当做一台服务器进行访问了

2.域名和域名服务器

尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名 (Domain Name) 地址

IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器

注意:
  • 单纯使用地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便
  • 在开发测试期间,127.0.0.1 对应的域名是 localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。

3.端口号

  • 计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。

  • 同样的道理,在一台电脑中,可以运行成百上千个 web 服务。每台web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。 image.png

    注意
    • 每个端口号不能同时被多个 web 服务占用
    • 在实际应用中,URL中的 80 端口可以被省略

创建最基本的 web 服务器

1.创建 web 服务器的基本步骤

  1. 导入http 模块
  2. 创建web服务器实例
  3. 为服务器实例绑定 request 事件,监听客户端的请求
  4. 启动服务器
步骤一 - 导入http模块

const http = require('http');

步骤二 - 创建web服务器实例

调用http.createServer()方法,即可快速创建一个web服务器实例:

const server = http.createServer();

步骤三 - 为服务器实例绑定request事件

为服务器绑定request事件,即可监听客户端发送过来的网络请求:

//使用服务器实例的 .on() 方法,为服务器绑定一个request 事件
server.on('request',(req,res) => {
    //只要有客户端请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
    console.log('Someone visit our web server.')
})
步骤四 - 启动服务器

调用服务器实例的 .listen() 方法,即可启动当前的web服务器实例:

//调用 server.listen(端口号,cb回调) 方法,即可启动web服务器
server.listen(80,() => {
    console.log('http server running at http://127.0.0.1')
})

2.总步骤

// 1. 导入http 模块
const http = require('http');
// 2. 创建web服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 **request** 事件,监听客户端的请求
server.on('request',(req,res) => {
    console.log('Someone visit our web server');
})
// 4. 启动服务器
server.listen(80,() => {
    console.log('server running at http://127.0.0.1:80');//如果端口号是80 ,则":80"可以省略
});

3.req请求对象

只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数

如果想在事件处理函数中,访问与客户端相关数据属性,可以使用如下的方式

//req.url是客户端请求的URL地址

//req.method是客户端请求的method类型

const http = require('http');
const server = http.createServer();
server.on('request',(req) => {
  const url = req.url
  const method = req.method;
  const str = `Your request url is ${url},and request method is ${method}`;
  console.log(str);
})
server.listen(80, () => {
  console.log('server running at http://127.0.0.1');
})

4.res响应对象

在request 事件处理函数中,访问与服务器端相关数据属性,可以使用如下的方式

res.end()

server.on('request', (req, res) => {
    // res 是响应对象,它包含了与服务器相关的数据和属性,例如:
    // 要发送到客户端的字符串
    const str = 'Your request url is $(req.url), and request method is $(req.method;
    // res.end() 方法的作用:
    // 向客户端发送指定的内容,并结束这次请求的处理过程
    res.end(str)
})

5.解决中文乱码问题

当调用 res.end()方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式

设置响应头 Content-Type 的值为 text/html; charset=utf-8

res.setHeader( 'Content-Type' ,'text/html; charset=utf-8' )

server.on( 'request',(req, res) => {
// 发送的内容包含中文
const str = `您请求的 url 地址是 $(req.url},请求的 method 类型是 $(req.method)`// 为了防止中文显示乱码的问题,需要设置响应头 Content-Type 的值为 text/html; charset=utf-8
res.setHeader( 'Content-Type' ,'text/html; charset=utf-8' )
// 把包含中文的内容,响应给客户端
res .end(str)

6.根据不同的url响应不同的 html内容

1.核心实现步骤
  1. 获取请求的 url 地址
  2. 设置默认的响应内容404 Not found
  3. 判断用户请求的是否为 //index.html首页
  4. 判断用户请求的是否为 /about.html 关于页面
  5. 设置 Content-Type 响应头,防止中文乱码
  6. 使用 res.end() 把内容响应给客户端
2.代码
const http = require('http');
const server = http.createServer();
server.on('request',(req,res) => {
    // 1. 获取请求的 url 地址
    const url = req.url;
    // 2. 设置默认的响应内容为404 Not found
    let content = '404 Not found';//判断里面都没有就会返回这个
    // 3. 判断用户请求的是否为/或/index.html首页
    // 4. 判断用户请求的是否为 /about.html 关于页面
    if(url === '/' || url === '/index.html'){
        content = '<h1>首页</h1>'
    }
    else if(url === '/about.html'){
        content = '<h1>关于页面</h1>'
    }
    // 5. 设置 Content-Type 响应头,防止中文乱码
    res.setHeader('Contect-TYpe','text/html;charset=utf-8')
    // 6. 使用 res.end()把内容响应给客户端
    res.end(content);
})
server.listen(80,() => {
    console.log('server running at http://127.0.0.1');
})

模块化

概念

模块化是指解决一个复杂问题时,自项向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

编程领域中的模块化

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并相互依赖的多个小模块

把代码进行模块化的好处:

  1. 提高了代码的复用性
  2. 提高了代码的可维护性
  3. 可以实现按需加载

模块化规范

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

例如:
  • 使用什么样的语法格式来引用模块
  • 在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处:

大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用利人利己。

Node.js中模块化

Nodejs 中模块的分类

Node.js 中根据模块来源的不同,将模块分为了3大类,分别是:

  • 内置模块 (内置模块是由 Node.js 官方提供的,例如fs、path、http 等)
  • 自定义模块 (用户创建的每个js 文件,都是自定义模块)
  • 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载

加载模块

使用require()方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。

//1.加载内置的fs模块
const fs = require('fs');
//2.加载用户的自定义模块(给路径)
const custom = require('./custom.js'); 
//3.加载第三方模块(关于第三方模块的下载和使用)
const moment =require('moment');

注意:使用require()方法加载其它模块时,会执行被加载模块中的代码

注意:在使用require加载用户自定义模块期间,可以省略 .js 后缀名

模块作用域

什么是模块作用域

和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

模块作用域的好处

防止了全局变量污染的问题

image.png (自己试了下,注释的内容没有注释时,加载这个自定义模块后不会打印那句出来,注释后就可以)

向外共享模块作用域中的成员

1.module 对象 (摸走~~ 摸走你的成员~~)

在每个.js 自定义模块中都有一个module 对象,它里面存储了和当前模块有关的信息,打印如下: image.png

2.module.exports对象

在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用

外界用require()方法导入自定义模块时,得到的成员,就是那个模块中module.exports所指向的对象

默认情况下这个对象为空对象{}

image.png

3.共享成员时的注意点

使用require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准

image.png

4.exports 对象

由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports 对象。默认情况下exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准

也就是上面写的module.exports.username = 'zs' 可以写成 exports.username = 'zs

image.png

image.png

image.png

image.png

5.Nodejs 中的模块化规范

Node.js 遵循了CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖

CommonJS规定:
  1. 每个模块内部,module 变量代表当前模块
  2. module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口
  3. 加载某个模块,其实是加载该模块的 module.exports 属性。require()方法用于加载模块

npm与包(未)

1.什么是包

Node.js 中的第三方模块又叫做包。 就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同

2.包的来源

不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用.

注意:Nodejs中的包都是免费且开源的,不需要付费即可免费下载使用

3.为什么需要包

由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低

包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率

包和内置模块之间的关系,类似于jQuery 和 浏览器内置 API 之间的关系

4.从哪里下载包

国外有一家IT 公司,叫做npm,Inc.这家公司旗下有一个非常著名的网站: www.npmis.com/,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!到目前位置,全球约 1100 多万的开发人员,通过这个包共享平台,开发并共享了超过 120 多万个包 供我们使用npm,inc.公司提供了一个地址为 registrympmis.org/ 的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。

模块的加载机制(未)

Express

Express简介

1.什么是 Express

官方给出的概念: Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架

通俗的理解: Express 的作用和 Node,js 内置的 http 模块类似,是专门用来创建 Web 服务器的

Express的本质:就是一个npm 上的第三方包,提供了快速创建Web 服务器的便捷方法

进一步理解 Express

Q:不使用 Express 能否创建 Web 服务器?

A:能,使用 Node.js 提供的原生 http 模块即可

Q:有了http 内置模块,为什么还有用 Express?

A: http内置模块用起来很复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。

Q:http 内置模块与 Express 是什么关系?

A:类似于浏览器中 WebAPI和jQuery 的关系。后者是基于前者进一步封装出来的

Express 能做什么

对于前端程序员来说,最常见的两种服务器,分别是:

  • Web 网站服务器: 专门对外提供 Web 网页资源的服务器
  • API 接口服务器:专门对外提供API 接口的服务器
  • 使用 Express,我们可以方便、快速的创建 Web 网站的服务器或API 接口的服务器

Express的基本使用

1.安装

在项目所处的目录中,运行npm i express@4.17.1

image.png

2.创建基本的Web服务器
//1.导入express
const express = require('express')
//2.创建web服务器
const app = express()
//3.调用app.listen(端口号,启动成功后的回调函数),启动服务器
app.listen(80,() => {
 console.log('express server running at http://127.0.0.1')
})
3.监听GET请求

通过app.get()方法,可以监听客户端的GET请求

4.监听POST请求

通过app.post()方法,可以监听客户端的POST请求

//参数1:客户端请求的URL地址
//参数2:请求对应的处理函数
//  req:请求对象(包含了与请求相关的属性与方法)
//  res:响应对象(包含了与响应相关的属性与方法)
app.get('请求URL'function(req,res)){/*处理函数*/}
app.post('请求URL'function(req,res)){/*处理函数*/}
5.把内容响应给客户端

通过res.send()方法,可以把处理好的内容发送给客户端

app.get('/user',(req,res) => {
    //调用express的res.send()方法,向客户端响应一个JSON对象
    res.send({name: 'zs',age: 20,gender: '男'})
})
app.post('/user',(req,res) => {
    //调用express的res.send()方法,向客户端响应一个文本字符串
    res.send('请求成功')
})
6.获取URL中携带的查询参数

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:

//通过req.query 可以获取到客户端发过来的查询参数

//注意:默认情况下,req.query是一个空对象

app.get('/',(req,res) => {
    //通过req.query 可以获取到客户端发过来的查询参数
    //注意:默认情况下,req.query是一个空对象
    console.log(req.query);
    res.send(req.query)
})
7.获取URL中的动态参数

通过req.params对象,可以访问到URL中,通过 匹配到的动态参数:

//注意:这里的:id是一个动态参数
app.get('/user/:id',(req,res) => {
    //req.params是动态匹配到的URL参数,默认也是一个空对象
    console.log(req.params);
    res.send(req.params)
})

自己理解:‘:id‘对应位置写什么,获取的id就等于什么

image.png

注意:
  • 冒号后面的名称可以自己起,合法合理就行
  • 可以有多个动态参数
app.get('/user/:ids/:username',(req,res) => {
    //req.params是动态匹配到的URL参数,默认也是一个空对象
    console.log(req.params);
    res.send(req.params)
})

image.png

托管静态资源

1.express.static()
2.托管多个静态资源目录

多次调用express.static()

const express = require('express');
const app = express();
​
//在这里,调用express.static()方法,对外提供静态资源
app.use(express.static('./clock/'))//需要对哪个文件夹对外提供资源,就把路径写下
app.use(express.static('./file/'))
​
app.listen(80,() => {
    console.log('express server running at http://127.0.0.1');
})
注意:

访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件

就是以上代码:在浏览器输入127.0.0.1/要查找的文件 这个文件如果在两个文件夹里面都有这个文件,就只执行第一个clock里面的文件;调换顺序就是执行file里面的文件;如果第一个没有就继续往下找

3.挂载路径前缀

如果希望在托管的静态资源访问路径之前,瓜子啊路径前缀,可以使用如下方式:

app.use('/public',express.static('public'))

我把原本的app.use(express.static('./clock/'))加上前缀’/abc' :

app.use('/abc',express.static('./clock/'))

访问是就得加上/abc的前缀

image.png

image.png

nodemon

1.为什么要使用 nodemon

在编写调试 Node.js 项目的时候,如果修改了项目的代码,则需要频繁的手动 close 掉,然后再重新启动,非常繁琐.现在,我们可以使用 nodemon (www.npmis.com/package/nod…) 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。

2.安装nodemon

在终端中,运行:

npm install -g nodemon

3.使用nodemon

当基于Node.js编写了一个网站应用时,传统的方式,是运行node app.js命令,来启动项目。这样做的坏处是:代码被修改之后,需要手动重启项目。

现在,我们可以将node命令替换为nodemon命令,使用nodemon app.js来启动项目。好处是:代码被修改后,会被nodemon监听到,从而实现自动重启项目的效果。

image.png

Express路由

1.概念

什么是路由

广义上讲,路由就是映射关系

image.png

express中的路由

在express中,路由指的是客户端的请求与服务器处理函数之间的映射关系

Express中的路由分3部分组成,分别是请求的类型,请求的URL地址,处理函数,格式如下:

app.METHOD(PATH,HANDLER)

  • method对应请求类型(get/post)
  • path对应请求的URL地址
  • handler对应处理函数
express中路由的例子
//匹配GET请求,且请求URL为 /
app.get('/',(req,res) => {
    res.send('Hello World!')
})
​
//匹配 POST 请求,且请求 URL为 /
app.post('/',(req,res) => {
    res.send('Got a POST request')
})
路由的匹配过程

每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。

在匹配时,会按照路由的先后顺序进行匹配,如果请求类型请求的URL同时匹配成功,则Express会将这次请求,转交给对应的function函数进行处理。

image.png

2.路由的使用

1.最简单的用法

在Express中使用路由最简单的方式,就是把路由挂载到app上(用得不多,因为今后会挂载越来越多的路由,文件体积会越来越大)

const express = require('express');
const app = express();
//挂载路由
app.get('/',(req,res) => {
    res.send('hello world!  ')
})
app.post('/',(req,res) => {
    res.send('Post Request.')
})
app.listen(80,() => {
    console.log('express server running at http://127.0.0.1');
})
2.模块化路由

为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块

步骤:
  1. 创建路由模块对应的js文件
  2. 调用express.Router() 函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用module.exports向外共享路由对象
  5. 使用app.use() 函数注册路由模块
注册路由模块
  1. 导入路由模块
  2. 使用app.use()注册路由模块
//这是路由模块(这个文件名./42.router)
//1.导入express
const express = require('express');
//2.创建路由对象
const router = express.Router();
//3.挂载具体路由
router.get('/user/list',(req,res) => {
    res.send('Get user list.')
})
router.post('/user/add',(req,res) => {
    res.send('Add new user.')
})
//4.向外导出路由对象
module.exports = router;
const express = require('express');
const app = express();
// 1. 导入路由模块
const router = require('./42.router');
// 2. 使用app.use()注册路由模块
app.use(router);
//注意:  app.use() 函数的作用:用来注册全局中间件  (比如上面那个router就属于中间件)
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
为路由模块添加前缀

类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单:

//1.导入路由模块
const userRouter = require('./router/user.js')
//2.使用app.use()注册路由模块,并添加统一的访问前缀 /api
app.use('/api',userRouter)

Express中间件

1.中间件概念

1.什么是中间件

中间件特指业务流程中间处理环节

image.png

2.Express中间件的调用流程

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

image.png

3.Expres中间件的格式

Expres中间件,本质上就是一个function处理函数

格式:

image.png

注意:

中间件函数的形参列表中,必须包含next参数,而路由处理函数中只包含req和res。

区别中间件和路由:

最后是否有next

4.next函数的作用

next函数是实现多个中间件连续调用的关键,它表示把流程关系转交给下一个中间件路由

image.png

2.Express中间件的初体验

1.定义中间件函数
const exp = require('constants')
const express = require('express')
const app = express();
//定义一个最简单的中间件函数
const mw = (req,res,next) => {
    console.log('这是一个中间件函数');
    //把流转关系,转交给下一个中间件或路由
    next();//最后一定要调用
}
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
2.全局生效的中间件

客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。

通过app.use(中间件函数) ,即可定义一个全局生效的中间件

const express = require('express')
const app = express();
//定义一个最简单的中间件函数
const mw = (req,res,next) => {
    console.log('这是一个中间件函数');
    //把流转关系,转交给下一个中间件或路由
    next();//最后一定要调用
}
​
//将mw注册为全局生效的中间件
app.use(mw);
​
​
//调用下面路由之前就会先执行上面中间件里面那个打印
app.get('/',(req,res) => {
    res.send('Home page.');
})
app.get('/user',(req,res) => {
    res.send('User page.');
})
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
3.定义全局中间件的简化形式
app.use((req,res,next) => {
    console.log('这是最简单的中间件函数');
    next();
})
4.中间件的作用

多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义属性方法,供下游的中间件或路由进行使用。

可以简化代码书写

image.png

const express = require('express')
const app = express();
app.use((req,res,next) => {
    const time = Date.now();
    //为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
    req.startTime = time;
    next();
})
app.get('/',(req,res) => {
    // const time = Data.now()//得到请求到达服务器的时间
    res.send('Home page.' + req.startTime);
})
app.get('/user',(req,res) => {
    // const time =  Data.now()//得到请求到达服务器的时间
    res.send('User page.' + req.startTime);
})
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
5.定义多个全局中间件

可以使用app.use() 连续定义多个全局中间件。客户端请求到达服务器后,会按照中间件定义的先后顺序依次进行调用。

代码:(调用路由,会先打印前面两个)
const express = require('express');
const app = express();
//定义第一个全局中间件
app.use((req,res,text) => {
    console.log('调用了第一个全局中间件');
    text();
})
//定义第二个全局中间件
app.use((req,res,text) => {
    console.log('调用了第二个全局中间件');
    text();
})
//定义一个路由
app.use('/user',(req,res) => {
    res.send('User page.');
})
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
6.局部生效的中间件

不使用app.use()定义的中间件,叫做局部生效的中间件

代码:
const express = require('express');
const app = express();
//定义中间件函数
const mw = (req,res,next) => {
    console.log('调用了局部生效的中间件');
    next();
}
app.get('/',mw,(req,res) => { //写在中间那个
    res.send('Home page');
})
app.get('/user',(req,res) => {
    res.send('User page');
})
app.listen(80,() => {
    console.log('http://127.0.0.1');
})
7.定义多个局部中间件

下面两种方式是等价的

app.get('/',mw1,mw2,(req,res)=> { res.send('Home page.')})
app.get('/',[mw1,mw2],(req,res)=> { res.send('Home page.')})
8.了解中间件的5个使用注意事项
  1. 一定要在路由之前注册中间件
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用 next()
  4. 函数为了防止代码逻辑混乱,调用 next()函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象

3.中间件的分类

为了方便大家理解和记忆中间件的使用,Express 官方把常见的中间件用法,分成了 5 大类,分别是:

  1. 应用级别的中间件
  2. 路由级别的中间件
  3. 错误级别的中间件
  4. Express 内置的中间件
  5. 第三方的中间件
1.应用级别的中间件

通过app.use()app.get()app.post() ,绑定到app实例上的中间件,叫做应用级别的中间件

2.路由级别的中间件

绑定到express.Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别的中间件没有任何区别。只不过应用级别的中间件是绑定到app上,路由级别的中间件绑定到router实例上

image.png

3.错误级别的中间件
错误级别中间件的作用:

专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。

格式:

错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)

注意:

错误级别的中间件必须注册在所有路由之后

const express = require('express');
const app = express();
//1.定义路由
app.get('/',(req,res) => {
    //人为的制造错误
    throw new Error ('服务器内部发生错误!')
    res.send('Home page');
})
//定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err,req,res,next) => {
    console.log('发生了错误!' + err.essage);
    res.send('Error:' + err.message)
})
app.listen(80,() => {
    console.log('Express server running at http://127.0.0.1');;
})
4.Express 内置的中间件(p48 未)

自Express 4.16.0 版本开始,Express 内置了3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:

  1. express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼性)
  2. express.json 解析JSON 格式的请求体据(有兼容性,仅在4.16.0+ 版本中可用)
  3. express.urlencoded 解析 URL-encoded 格式的请求体数据 (有兼容性,仅在4.16.0+ 版本中可用)
5.第三方的中间件(p48 未)

非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。 例如:在express@4.16.0之前的版本中,经常使用 body.parser 这个第三方中间件,来解析请求体数据。使用步骤如下:

  1. 运行 npm install body-parser 安装中间件
  2. 使用 require 导入中间件
  3. 调用 app.use() 注册并使用中间件

4.自定义中间件(未)

使用Express写接口

1.创建基本的服务器

//创建基本服务器
//导入express
const express = require('express');
//创建服务器实例
const app = express();
​
// ...//启动服务器
app.listen(80,() => {
    console.log('express server running at http://127.0.0.1');
})

2.创建API路由模块

const express = require ('express');
const router = express.Router();
​
//在这里挂载对应的路由
​
​
module.exports = router;

3.编写GET接口

//在这里挂载对应的路由
router.get('/get',(req,res) => {
    //通过req.query获取客户端  通过查询字符串,发送到服务器的数据
    const query = req.query;
    //通过调用res.send()方法,向客户端响应处理的结果
    res.send({
        status: 0, //0 表示处理成功, 1 表示处理失败
        msg: 'GET请求成功1', //状态的描述
        data: query //需要响应给客户端的数据
    })
})  
//创建基本服务器
//导入express
const express = require('express');
//创建服务器实例
const app = express();
//导入路由模块
const router = require('./52.apiRouter');
//把路由模块注册到app上
app.use('/api',router);//可以把router看成一个中间件,通过use把它注册成全局中间件
//启动服务器
app.listen(80,() => {
    console.log('express server running at http://127.0.0.1');
})

image.png

4.编写POST接口

//定义post接口
router.post('/post',(req,res) => {
    //通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body;
    //调用 res.send() 方法,向客户端响应结果
    res.send({
        status:0,
        msg: 'POST请求成功!',
        data:body
    })
});

在那个的服务器里面要写:

//配置解析表单数据的中间件
app.use(express.urlencoded({extended:false}))

总代码:

//创建基本服务器
//导入express
const express = require('express');
//创建服务器实例
const app = express();
​
//配置解析表单数据的中间件
app.use(express.urlencoded({extended:false}))
​
//导入路由模块
const router = require('./52.apiRouter');
//把路由模块注册到app上
app.use('/api',router);//可以把router看成一个中间件,通过use把它注册成全局中间件//启动服务器
app.listen(80,() => {
    console.log('express server running at http://127.0.0.1');
})
const express = require ('express');
const router = express.Router();
​
//在这里挂载对应的路由
router.get('/get',(req,res) => {
    //通过req.query获取客户端  通过查询字符串,发送到服务器的数据
    const query = req.query;
    //通过调用res.send()方法,向客户端响应处理的结果
    res.send({
        status: 0, //0 表示处理成功, 1 表示处理失败
        msg: 'GET请求成功1', //状态的描述
        data: query //需要响应给客户端的数据
    })
})  
​
//定义post接口
router.post('/post',(req,res) => {
    //通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body;
    //调用 res.send() 方法,向客户端响应结果
    res.send({
        status:0,
        msg: 'POST请求成功!',
        data:body
    })
});
​
module.exports = router;

5.CORS跨域资源共享

1.接口的跨域问题

上面编写的GET和POST接口,存在一个很严重的问题:不支持跨域请求

  1. CORS(主流的解决方案,推荐使用)
  2. JSONP(有缺陷的解决方案:只支持 GET 请求)
2.使用 cors 中间件解决跨域问题

cors是Express的一个第三方中间件。通过安装和配置cors 中间件,可以很方便地解决跨域问题使用步骤分为如下3步:

  1. 运行 npm install cors 安装中间件
  2. 使用 const cors = require(cors)导入中间件
  3. 在路由之前调用 app.use(cors0) 配置中间件
//一定要在路由之前,配置cors这个中间件,从而解决接口跨域问题
const cors = require('cors');//导入
app.use(cors());//用use全局注册这个中间件
3.什么是CORS

CORS (Cross-Origin Resource Sharing跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头就可以解除浏览器端的跨域访问限制

image.png

4.CORS的注意事项
  1. CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
  2. CORS 在浏览器中有兼容性。只有支持XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS的服务端接口 (例如:IE10+、Chrome4+、FireFox3.5+)。
5.CORS 响应头部 - Access-Control-Allow-Origin

响应头部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下: Access-Control-Allow-Origin: <origin>|*

其中,origin 参数的值指定了允许访问该资源的外域 URL

例如,下面的字段值将只允许来自 itcast.cn 的请求:

res.setHeader('Access-Control-Allow-Origin',"http://itcast.cn')

如果指定了Access-Control-Allow-Origin 字段的值为通配符* ,表示允许来自任何域的请求,示例代码如下:

res.setHeader('Access-Control-Allow-Origin','*')
6.CORS 响应头部 - Access-Control-Allow-Headers

默认情况下,CORS 支持客户端向服务器发送如下的 9 个请求头:

Accept、Accept-Language、 Content-Language、DPR、 Downlink、 Save-Data Viewport-Width、 Width 、Content-Type (值仅限于 text/plain、 multipart/form-data、application/x-www-form-urlencoded 三者之一)

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!

// 注意: 多个请求头之间使用英文的逗号进行分割

// 允许客户端额外向服务器发送 Content-Type 请求头和 X-Custom-Header 请求头
res .setHeader('Access-Control-Allow-Headers','Content-Type, X-Custom-Header')
7.CORS 应头部 - Access-Control-Allow-Methods

默认情况下,CORS仅支持客户端发起 GET、POST、HEAD 请求

如果客户端希望通过 PUTDELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法 示例代码如下:

// 只允许 POST、GET、DELETE、HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
// 允许所有的 HTTP 请求方法
res.setHeader('Access-Control-Allow-Methods,'*')
9.简单请求

同时满足以下两大条件的请求,就属于简单请求:

  1. 请求方式: GET、POST、HEAD 三者之一
  2. HTTP 头部信息不超过以下几种字段: 无自定义头部字段、Accept、Accept-Language、 Content-Language、DPR、Downlink、Save-Data、Viewport-Width、 WidthContent-Type (只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
10.预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求:

  1. 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
  2. 请求头中包含自定义头部字段
  3. 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据

11.简单请求和预检请求的区别(代码未 p55 6分钟处)

简单请求的特点: 客户端与服务器之间只会发生一次请求预检请求的特点:客户端与服务器之间会发生两次请求OPTION 预检请成功之后,才会发起真正的请求

6.JSONP接口

1.回顾JSONP 的概念与特点
概念:

浏览器端通过

特点:
  1. JSONP不属于真正的Ajax 请求,因为它没有使用XMLHttpRequest这个对象
  2. JSONP仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求。
2.创建JSONP接口的注意事项

如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突必须在配置 CORS 中间件之前声明JSONP 的接口。否则JSONP接口会被处理成开启了 CORS的接口。示例代码如下:

//优先创建JSONP接口【这个接口不会被处理成CORS接口】
app.get('/apoi/jsonp',(req,res) => { })
​
//再配置 CORS 中间件【后续的所有接口,都会被处理成CORS接口】
app.use(cors())
​
//这是一个开启了CORS的接口
app.get('/api/get',(req,res) => { })
3.实现JSONP 接口的步骤
  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过JSONP形式发送给客端的数据
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,响应给客户端的
4.实现JSONP 接口的具体代码
app.get('/api/jsonp',(req,res) => {
// 1.获取客户端发送过来的回调函数的名字
const funcName = req.query.callback
// 2.得到要通过 JSONP 形式发送给客户端的数据
const data = { name: 'zs', age: 22 }
// 3.根据前两步得到的数据,拼接出一个函数调用的字符串
const scriptStr = `${funcName}($(JSON.stringify(data)})`
// 4.把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行
res.send(scriptStr)
})
5.在网页中使用jQuery 发起JSONP 请求(未)