web学习-Node.js入门学习

Posted GY-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web学习-Node.js入门学习相关的知识,希望对你有一定的参考价值。

web学习-Node.js入门学习

1.回顾与思考

浏览器中的javascript的组成部分:

为什么JavaScript可以在浏览器中执行:

为什么JavaScript可以操作DOM和BOM:

浏览器中的JavaScript运行环境(运行环境指的是代码正常运行所需的必要环境):

2. 初识Node.js

2.1 Node.js的简介

Node.js是一个基于Chrome V8引擎JavaScript运行环境Node.js官网

Node.js中的JavaScript运行环境:

  • Node.js可以做什么:Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和API,然而,基于Node.js提供的这些基础功能,很多强大的工具和框架如雨后春笋,层出不穷,所以学习Node.js,可以让前端程序员胜任更多的工作和岗位:
    • 基于Express框架 ,可以快速构建Web应用
    • 基于Electron框架,可以构建跨平台的桌面应用
    • 基于restify框架,可以快速构建API接口项目
    • 读写和操作数据库、创建实用的命令行工具辅助前端开发、…

2.2Node.js的环境安装

如果希望通过Node.js来运行JavaScript代码,则必须在计算机上安装Node.js环境才能运行。

安装包可以去Node.js官网下载

  • LTS版本和Current版本的区别:
    • LTS为长期稳定版,对于追求稳定性企业级项目来说,推荐安装LTS版本的Node.js
    • Current为新特性尝鲜版,对热衷于尝试新特性的用户来说,推荐安装Current版本的Node.js,但是Current版本中可能存在隐藏的Bug或安全性漏洞,因此不推荐企业级项目中使用Current版本的Node.js

如何确定Node.js已经安装成功:终端输入命令node -v可查看安装的版本,能看到安装的版本则表示安装成功, 否则没有安装成功

如何在Node.js环境中执行JavaScript的代码: 在终端进入到js文件目录下, 输入终端指令:node 要执行文件的路径

注意: 使用tab键, 能够快读不全路径

在终端中使用tab键快速不全路径, mac电脑不知道到支不支持

2.3. fs文件系统模块

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

  • fs.readFile()方法,用来读取指定文件的内容
  • fs.writeFile()方法,用来向指定的文件中写入内容

但是如果想要在JavaScript中,使用fs模块操作文件,则需要使用如下方式导入它:const fs = require(''fs)

2.3.1 fs.readFile()

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

参数解读:

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

上面方法是正确读取文件的内容, 下面是 读取文件错误的错误信息打印

2.3.2 fs.writeFile()

fs.writeFile(path,data, [options], callback)
  • 参数解读:
    • 参数1: 必选参数,需要指定一个文件的路径
    • 参数2:必须参数,表示写入的内容
    • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf-8
    • 参数4:必选参数,文件写入完成后的回调函数
    • 注意:只能用来创建文件,不能用来创建路径;重复调用write方法,新写入的内容会覆盖旧的内容

文件写入成功,则error对象是null, 否则是一个错误对象。

2.3.3 练习-整理考试成绩

2.3.4 fs模块-路径动态拼接的问题

在使用fs模块操作文件时,如果提供的操作路径.././开头的相对路径时,很容易出现路劲动态拼接错误的问题。
原因: 代码在运行的时候,会以执行node命令是所处的目录,动态拼接出被操作文件的完整路径。
解决方案: 在使用fs模块操作文件时,直接提供完整路径,不要提供./../开头的相对路径,从而防止路径动态拼接的问题。


  • __dirname是一个固定路径,表示当前node文件执行文件所处在的目录

2.4 path路径模块

  • path模块:是Node.js官方提供的, 用来处理路径的模块, 它提供了一系列的方法和属性,用来满足用户对路径的处理需求
    • path.join(): 用来将多个路径片段拼接成一个完整的路径字符串
    • path.basename():用来从路径字符串中,将文件名解析出来
    • 注意:如果要在JavaScript代码中,使用path模块处理路径,则需要使用如下的方式先导入它:const path = require('path')

path.join([…paths])

  • paths路径片段的序列
  • 返回值:

path.basename()的示例代码

path.extname(): 获取路径中的扩展名部分,语法格式如下path。extname(path)

  • path 必选参数,表示一个路径的字符串
    返回: 返回得到的扩展名字符串

2.5 http模块

http模块是Node.js官方提供的, 用来创建Web服务器的模块,通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务

当然要使用http模块创建Web服务器,则需要先导入该模块:const http = require('http')

2.5.1 服务器相关的概念

服务器和普通电脑的区别在于,服务器上安装了web服务器软件, 例如:IIS、Apache等。通过安装这些服务软件就能把一台普通的电脑变成一台web服务器

在Node.js中,我们不需要使用这些第三方的web服务器。因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外听过web服务。

IP地址:表示互联网上每台计算机的唯一地址,因此IP地址具有唯一性。如果把个人电脑比作一台电话,那么IP地址就相当于电话号码,只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。

IP地址的格式:通常使用点分十进制表示(a.b.c.d)的形式,其中a,b,c,d都是0~255之间的十进制整数。例如:用点份十进制的IP地址(192.168.1.1)

注意:

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

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

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

2.5.2 创建最基本的web服务器

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

然后再浏览器输入http://127.0.0.1访问本机, 输出控制台会输出如下信息。

req请求对象: 只要服务器接受到客户端的请求,就会调用server.on()为服务器绑定的request事假处理函数。如果想要在事件处理函数,访问与客户端相关的数据或属性可以使用如下方式:

res响应对象: 在服务器request事件处理函数中,如果访问与服务器相关的数据或属性,可以使用如下方式:

2.5.3 根据不同的url响应不同的html内容

  • 核心实现步骤:
    1. 获取请求的url地址
    2. 设置默认的响应内容为 404 Not found
    3. 判断用户请求的是否为//index.html首页
    4. 判断用户请求是否为/about.html关于页面
    5. 设置Content-Type响应头,防止中文乱码
    6. 使用res.end()把内容响应给客户端

3. 模块化


3.1 模块化的基本概念

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

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

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

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

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:使用什么样的语法格式来引用模块、在模块中使用什么样的语法格式向外暴露成员

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

3.2 Node.js中模块的分类

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

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

3.2.1 加载模块

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

注意:使用require()方法加载其他模块时,会执行被加载模块中的代码。在使用require()加载用户自定义模块期间,可以省略.js的后缀名

3.2.2 模块作用域

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

模块作用域的好处: 防止了全局变量污染的问题

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

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

module.exports对象: 在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。外界用require()方法导入自定义模块时,得到的就是module.exports所指向的对象。

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

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

module.exportsexports的使用误区: 时刻谨记,require()模块时,得到的永远是module.exports指向的对象:

  • Node.js中模块化规范:Node.js遵循了CommonJS模块化规范,CommonJS规定了模块化特性各模块之间如何相互依赖。CommonJS规定:
    • 每个模块内部, Module变量代表当前的模块
    • module变量是一个对象, 它的exports属性,即module.exports是对外的接口
    • 加载某个模块, 其实是加载该模块的module.exports属性。require()方法用于加载模块

3.3 npm与包

3.3.1 npm和包的概述

不同于Node.js中的内置模块与自定义模块, 包是由第三方个人或团队开发出来的,免费供所有人适用。
注意: Node.js中的包都是免费且开源的,不需要付费即可免费下载使用。

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

包是基于内置模块封装出来的,提供了更加高级的功能,更方便的API,极大提高了开发效率。
包和内置模块之间的关系,类似JQuery和浏览器内置API之间的关系。

  • 搜索包的地址:https://www.npmjs.com
  • 下载包的地址:https://registry.npmjs.org
  • npm,Inc公司除了提供上述地址之外,还提供了一个包管理工具Node Package Manager(简称npm包管理工具),这个工具随着Node.js的安装一起安装到电脑上了,大家可以在终端上执行npm -v命令,来查看电脑上所安装npm包管理工具的版本。

3.3.2 npm初体验

  • 格式化时间的传统做法:

  • 格式化时间的高级做法:
    • 使用npm包管理工具,在具体的项目中安装格式化时间的包 monment
    • 使用require()导入格式化时间的包
    • 参考monment的官方API 文档对时间进行格式化

在项目中安装包的命令:npm install 包的完整名称 , 上述命令还可以简写为npm i 完整包名称

那么初次安装包之后,多了哪些文件?

初次安装包完成后,在项目文件夹下多了一个叫做node_modules的文件夹和package-lock.json的配置文件。
node_modules文件夹用来存放所有已安装大项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包的。
package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等

注意: 程序员不要手动修改node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护他们

默认情况下,使用npm install命令安装包的时候,会自动安装最新版本的包。如果需要指定安装指定版本的包,可以在包名之后,通过@符号指定具体的版本.例如:

包的版本号是以点分十进制形式进行定义的,总共有三位数字,例如2.24.0,其中每一位数字代表的含义如下:

  • 第一位数字: 大版本
  • 第二位数字:功能版本
  • 第三位数字:Bug修复版本
    版本号的提升规则: 只要前面的版本号增长,则后面的版本号归零。

3.3.3 包管理配置文件

npm 规定,在项目根目录中, 必须提供一个叫做package,.json的包管理配置文件。用来记录与项目有关的一些配置信息。例如:

  • 项目的名称、版本号、描述等
  • 项目中都用到了那些包
  • 哪些包只在开发期间会用到
  • 那些包在开发部署时都需要用到

如果第三方的体积过大,不方便团队之间共享项目源代码。解决方案:共享时剔除node_modules

如何在记录项目中安装了哪些包?
项目根目录中,创建了一个叫做package.json的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码。

注意:在今后的项目开发中,一定要把node_modules文件夹,添加到.gitignore忽略文件中。

  • 快速创建package.json : npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中快速创建package.json这个包管理配置文件:

  • 如果包含中文和空格,会报名称不合法的错误

  • dependencies结点:package.json文件中,有一个dependencies结点,专门用来记录您使用npm install命令安装了哪些包

当我们拿到一个剔除了node_modules的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。否则会报类似于下面的错误:

可以运行npm install 命令或者npm i一次性安装所有的依赖包:

关于卸载包,可以运行命令npm uninstall命令来卸载指定的包,例如:npm uninstall moment, 这里需要注意的是 npm uninstall 命令执行成功后, 会把卸载的包,自动从package.json的dependencies种移除

如果某些包只在项目开发阶段会用到,在项目上线后不会用到,则建议把这些包记录到devDependencies几点中。与之对应的,如果在某些包在开发项目上线之后都需要用到,则建议把这些包记录到dependenies节点中。

可以使用如下命令,将包记录到devDenpendencies节点中:

有时候下载包的速断比较慢?这是因为要国外的网站,所以有时候会比较慢。

可以切换npm的下包镜像源:

为了更方便的切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm提供的终端命令,可以快速产看和切换下包的镜像源。

3.3.4 包的分类

  • 项目包:那些被安装到项目的node_modules目录中的包都是项目包, 项目包又分两类:
    • 开发依赖包:被记录到devDenpendencies节点中的包,只在开发期间会用到
    • 核心依赖包:被记录到denpendencies节点中的包,在开发期间和项目上线之后都会用到

在执行npm install命令时, 如果提供了 -g参数,则会把包安装为全局包。

全局包会被安装到/usr/local/lib/node_modules目录下 npm i 包名 -g (全局安装指定的包)npm uninstall 包名 -g(卸载全局安装的包)

注意:1.只有工具性质的包,才有全局安装的必要性,因为他们提供了好用的终端命令。2、判断某个包是否需要全局才能使用,可以参考官方提供的使用说明即可

  • i5ting_toc:是一个可以把md文档转换成html页面的小工具,使用步骤如下:

  • 规范的包结构:一个规范的包,它的组成结构,必须符合以下3点要求:

    • 包必须以单独的目录而存在
    • 包的顶级目录下面必须包含package.json这个包管理配置文件
    • package.json中必须包含name(包的名字)、version(包的版本)、main(包的入口)这三个属性。
    • 注意:以上三点要求是一个规范的包结构必须遵守的格式,关于更多的约束,需要自行参考资料

3.3.5 开发属于自己的包

  • 初始化包的基本结构

    • 新建date-tools文件夹,作为包的根目录
    • 在date-tools文件中,新建如下三个文件:
      • package.json: 包的管理配置文件
      • index.js : 包的入口文件
      • README.md: 包的说明文档
    1. 配置package.json文件:

  1. 实现index.js中的格式化时间功能:

注意: 我们没有直接导入确定的.js文件,node会去该文件下面去查找是否有package.json文件,然后再看.json文件中是否有指定main属性(入口) 如果 没有则会报错 找不到文件

如果我们里面后面还有很多其他的功能方法, index.js文件中的代码会越来越多,会变得越来越难维护,为了解决这种情况,所以我们把功能相关的代码抽离出来作为一个独立的文件。

注意:...是展开对象中的每一个属性

  • 编写包的说明文档:包根目录中README.md文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考。 内容包含: 安装方式、导入方式、功能使用介绍、开源协议

3.3.6 发布自己开发的包

访问https://www.npmjs.com/,然后注册一个账号。然后我们需要再终端执行命令npm login,依次输入用户名、密码、邮箱后,即可以登录成功。这里有一个注意点在运行npm login 命令之前,必须先把下包的服务器地址切换为npm 的官方服务器,否则会导致发布包失败

  • 发布包:将终端切换到包的根目录之后,执行npm publish命令,即可将包发布到npm上(注意:包名不能雷同
  • 删除已发布包: 执行npm unpublish 包名 --force命令,即可从npm删除已发布的包。
    • npm unpublish 命令只能删除72小时以内发布的包
    • npm unpublish 删除的包,在24小时内不允许重复发布
    • 发布包的时候要慎重,尽量不要在王npm上发布没有意义的包

3.4 模块的加载机制

模块在第一次加载后会被缓存,这也意味着多次调用require()不会导致模块的代码被执行多次。注意:不论是内置模块、用户自定义模块、还是第三方模块,他们都会优先从缓存中加载,从而提高模块的加载效率

  • 内置模块是有Node.js官方提供的模块,内置模块的加载优先级最高。例如,require(‘fs’)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs

  • 使用require()加载自定义模块时,必须指定以./../开头的路径标识符,在加载自定义模块时,如果没有指定./或则../这样的路劲标识符,则node会把它当做内置模块第三方模块进行加载。

同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,终端报错
  • 第三方模块加载机制: 如果传递给require()的模块标识符不是一个内置模块,也没有以./../开头,则Node.js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。

  • 目录作为模块: 当把目录作为模块标识符,传递给require()进行加载的时候,又三种加载方式:
    • 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为require()加载的入口
    • 如果目录里没有package.json文件,或则main入口不存在或无法解析,则Node.js将会视图加载目录下的index.js文件。
    • 如果以上两步都失败了,则Node.js会在终端打印错误信息,报告模块的缺失:Error Cannot find module ‘xxx’

4.Express

什么是Express?

官方给出的概念:Express是基于Node.js平台,快速、开放、极策的Web开发框架。通俗理解Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的,其本质就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法Express中文官网 ,这里安装的是4.17.1版本。

Express能做什么?

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

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

4.1 Express的基本使用

在项目所处的目录中, 运行如下的终端命令,即可将express安装到项目中使用:npm i express@4.17.1

  • 使用express创建基本的web服务器:

  • 监听GET请求, 我们通过app.get()方法,可以监听客户端的GET请求,具体的语法格式如下:

  • 监听POST请求,通过app.post()方法,可以监听客户端的POST请求。具体的语法格式如下:

  • 服务器把内容响应给客户端,通过res.send()方法,可以把处理好的内容,发送给客户端:

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

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

  • 托管静态资源:express提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过下面代码就可以将public目录下的图片、CSS文件、javaScript文件对外开放了app.user(express.static('public'))


  • 托管多个静态资源目录:如果要托管多个静态资源目录,请多次调用express.static()函数:


注意:访问静态资源文件时,express.stack()函数会提供目录的添加顺序查找所需要的文件

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

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

在终端执行命令npm install -g nodemon,即可将nodemon安装为全局可用的工具

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

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

4.2 express路由

4.2.1 路由的介绍

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

Express中路由分3不分组成, 分别是请求的类型、请求的URL地址、处理函数格式如下:app.METHOD(PATH, HANDLER)

  • 路由的匹配过程: 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后, 才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型请求的URL同时匹配成功, 则Express 会将这次请求, 转交给对应的function函数进行处理。

注意:按照定义的先后顺序进行匹配,请求类型请求的URL同时匹配成功,才会调用对应的处理函数

4.2.2 路由的使用

在Express 中使用路由最简单的方式, 就是把路由挂载到app上,实力代码如下

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

  1. 创建路由模块对应的js文件
  2. 调用express.Route()函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用module.express向外共享路由对象
  5. 使用app.user()函数注册路由模块

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

4.3 express中间件

4.3.1 中间件的概念

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

Express中间件的格式: Express的中间件,本质上就是一个function处理函数,Express中间件的格式如下:

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

上面多次提到next()函数,那么next()函数的作用是什么了?

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

4.3.2 Express 中间件的初体验

那么我们如何定义一个中间件函数,可以通过如下方式:

全局生效的中间件: 客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。通过调用app.use(中间件函数),即可定义一个全局生效的中间件,示例代码如下:

定义中间件的简化形式:

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

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

除了全局中间件之外,我们还可以使用局部中间件,所谓局部生效的中间件, 就是不适用app.use()定义的中间件, 叫做局部生效中间件

注意:不使用app.use()注册的中间件都是局部中间件

跟全局中间件一样,我们一样也可以同时定义多个中间件。我们可以通过如下两种等价的方式,使用多个局部中间件

  • 中间件的5个使用注意事项:
    • 一定要在路由之前注册中间件
    • 客户端发送过来的请求, 可以连续调用多个中间件进行处理
    • 执行完中间件的业务代码之后,不要忘记调用next()函数
    • 为了防止代码逻辑混乱 ,调用next()函数后不要在写额外的代码
    • 连续调用多个中间件时,多个中间件之间,共享req和res对象

4.3.3 中间件的分类

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

  • 应用级别的中间件
  • 路由级别的中间件
  • 错误级别的中间件
  • Express内置的中间件
  • 第三方的中间件

通过app.use()app.get()app.post(),绑定到APP实例上的中间件, 叫做应用级别的中间件。代码示例如下:

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

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

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

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

  • Express内置的中间件:自从Express 4.16.0版本开始Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:
    • express.static:快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性
    • express.json: 解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用
    • express.urlencoded: 解析URL-encoded格式的请求数据(有兼容性,仅在4.16.0+版本中可用

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

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

注意:Express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的

4.3.4 自定义中间件

学习上述中间件的知识 ,我们可以尝试写一个自己的中间件。接下来我们手动模拟一个类似于express.unlencoded这样的中间件, 来解析POST提交服务器的表单数据。实现步骤如下:

  1. 定义中间件
  2. 监听req的data事件(在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。)
  3. 监听req的end事件(当请求数据接受完毕之后,会自动触发req的end事件。因此,我们可以在req的end事件中,拿到并处理完整的请求数据。)
  4. 使用querystring模块解析请求体数据(Node.js内置一个querystring模块,专门用来处理查询字符串。通过这个模块提供的parse()函数,可以轻松把查询字符串,解析成对象的格式的数据)
  5. 将解析出来的数据对象挂在为req.body(上游的中间件和下游的中间件及路由之间,共享同一份req和res。因此我们可以将解析出来的数据,挂在为req的自定义属性,命名为req.body,供下游使用)
  6. 将自定义中间件封装为模版

上述我们已经完成了我们自己中间件的功能实现, 但是我们发现我们的中间件的逻辑代码和主模块的代码在一起, 这样的代码结构不太好管理, 为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,

4.4使用Express写接口

4.4.1 接口实现

上述html使用的jQuery是在线版本的相关地址-免费CND服务

编写接口之后,当我们进行接口测试的时候,发现浏览器报了如下错误:

4.4.2 CORS跨域资源共享

上述编写的接口,我们也发现了,存在一个很严重的问题: 不支持跨域请求。解决接口跨域问题的方案主要有两种:

  1. CORS(主流的解决方案,推荐使用)
  2. JSONP(有缺陷的解决方案:支持支GET请求)

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

  • 运行npm install cors中间件
  • 使用const cors = require('cors')导入中间件
  • 在路由之前调用app.use(cors())配置中间件

我们可能会有一个疑问? 到底什么是CORS?

cors(Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源

浏览器的同源安全策略默认会组织网页’跨域’获取资源,但如果接口服务器配置了CORS相关的HTTP响应头,就可以接触浏览器的跨域访问限制。

  • CORS的注意事项:
    • CORS主要是在服务器进行配置,客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。
    • CORS在浏览器中有兼容性,只支持XMLHttpRequestLevel2的浏览器,才能正常访问开启了CORS的服务端接口,例如IE10+、chrome4、FireFox3.5+

CORS响应头部:Access-Control-Allow-Origin,响应头部中可以携带该字段,其语法如下:Access-Control-Allow-Origin: | *,其中origin参数指定了允许访问该资源的外域URL`。

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

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

CORS响应头Access-Control-Allow-Headers默认情况下,CORS仅支持客户端像服务器发送如下的9个请求头:Accept、Accept-Language、Content-Lanaguage、DPR、Downlink、Viewport-Width、Width、Content-Type(值仅于text/plain、mulitpart/from-data、application/x-www-form-urlencoded三者之一)

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

CORS响应头部Access-Control-Allow-Methods,默认情况下,CORS仅支持客户端发起的GET、POST、HEAD请求,如果客户端希望通过PUT、DELETE等方式请求服务器资源,则需要再服务器端,通Access-Control-Allow-Methods指明实际请求所允许使用的HTTP方法

CORS请求的分类,客户端在请求CORS接口时,根据请求方式请求头的不同,可以将CORS的请求分成两大类,分别是:简单请求预检请求

简单请求:就是满足请求方式是GET、POST、HEAD三者之一, HTTP头部信息不超过上述的9种字段,无自定义表头。

预检请求: 只要符合其中一个要求,都需要进行预检请求。请求方式为GET、POST、HEAD之外的请求Method类型, 请求头中包含自定义头部字段,向服务器发送了application/json格式的数据

4.4.3 JSONP接口

浏览器端通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP。其特点:

  • JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
  • JSONP仅支持GET请求,不支持POST、PUT、DELETE等请求

创建JSONP请求的注意事项:如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前申明JSONP的接口,否则JSONP接口会被处理成开启了CORS的接口。

使用JSONP的接口步骤:

  1. 获取客户端发来的回调函数名字
  2. 得到要通过JSONP形式发送给客户端的数据
  3. 根据前两部得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的数据,响应给客户端script标签进行解析执行

5. mysql数据库

关于MySQL数据一些操作数据库的指令可以参考文章Java学习-MySQL数据库

这里主要介绍下载Express中怎么使用数据库。首先在项目中操作数据的步骤:

  1. 安装操作MySQL数据库的第三方模块(mysql
  2. 通过mysql模块链接到MySQL数据库
  3. 通过mysql模块执行SQL语句

2023年Web前端开发学习路线图

web前端工程师入门需要学啥?

前端Node.js需要掌握那些内容?

带你入门nodejs第一天——node基础语法及使用

带你入门nodejs第一天——node基础语法及使用

node.js从入门到放弃