node知识点总结

156 阅读35分钟

什么是nodejs

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 通俗的理解就是Node.js为JavaScript代码的正常运行,提供的必要的环境。

需要注意的是,浏览器是JavaScript的前端运行环境。 Node.js是JavaScript的后端运行环境。 Node.js中无法调用DOM和BOM等浏览器内置API。

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

nodejs安装

如果希望通过Node.js 来运行 Javascript 代码,则必须在计算机上安装Node.js 环境才行。 安装包可以从Node.js 的官网首页直接下载,进入到Node.js的官网首页(nodejs.org/zh-cn/),点击绿色的按钮,下载所需的版本后,双击直接安装即可。

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

安装完成后,打开终端,在终端输入命令node –v 后,按下回车键,即可查看已安装的Node.js 的版本号。

在node环境中执行javascript代码

在 Node.js 中执行JavaScript代码的方式有两种,分别是:

  1. 在REPL中,输入JavaScript代码并执行
  2. 通过命令行的形式,来执行指定JavaScript文件中的代码 在实际开发中,第二种方式用的最多。

REPL

REPL(Read - Eval - Print - Loop,简称REPL)是一个简单的,交互式的编程环境。它和浏览器调试工具中的console面板比较类似,我们可以通过REPL,来执行一些简单的Javascript代码。REPL中每个字母所代表的含义分别是读取-求值-输出-循环.

  • R(Read):每当我们输入完毕代码之后敲击回车键,Node.js 环境就会读取用户输入的代码.
  • E(Eval):把Read 进来的用户代码,进行解析和执行。
  • P(Print):把第二步中解析执行的结果,输出给用户。
  • L(Loop):当输出完毕之后,进入下一次的REP循环,等待用户输入新的代码。

打开任意终端,直接输入node 命令并回车,就会进入到REPL 环境中;在终端中,按两次Ctrl + C 快捷键,就能退出REPL 环境。

命令行

REPL环境只适合以交互式的方式,执行一些简单的JavaScript 代码。如果希望通过Node.js,来执行存放于.js文件中的代码,则推荐使用命令行的方式。打开终端,输入node要执行的js文件的路径,即可通过Node.js,来执行存放于.js文件中的代码。

global模块

global模块,是node的全局模块,在使用时不需要引入,直接使用。浏览器中的window是顶层对象、全局变量。global是全局模块,里面的变量都是全局变量。注意: node里面使用global里面的变量,不需要引入,说明其他的是需要引入的。

  1. console.log()打印。
  2. setTimeout和setInterval,延时器和定时器。
  3. __dirname 当前文件夹的绝对路径。
  4. require 引入模块。
  //1. console.log()  打印

  //2. setTimeout 和setInterval ,延时器和定时器
  setTimeout(() => {
    console.log('123');
  }, 1000);

  //3. __dirname 当前文件夹的绝对路径
  //从当前所在磁盘盘符一直到当前文件夹的路径
   console.log(__dirname);
 
  //4. require 引入模块 
  // 在node里是通过require 来引入模块的
  // 除了global其他模块都是需要引入的
  const fs = require('fs') 

fs文件系统模块

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

例如:

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

读取指定文件中的内容

使用fs.readFile()方法,可以读取指定文件中的内容,语法格式如下:

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

参数1:必选参数,需要指定一个文件路径的字符串,表示要读取哪个路径对应的文件。

参数2:可选参数,表示以什么编码格式来读取文件。

参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果。

以utf8的格式,读取指定文件的内容,并打印err和data的值。

const fs = require('fs');
fs.readFile('hello.txt', 'utf-8', (err, data) => {
    // 判断是否读取成功
    if (err) return console.log(err);
    console.log(data); 
});

向指定文件写入内容

使用fs.writeFile()方法,可以向指定的文件中写入内容,语法格式如下:

fs.writeFile(file, data[, options], callback)
  • 参数1:必选参数,需要指定一个文件路径的字符串,表示要文件的存放路径。
  • 参数2:必选参数,表示要写入的内容。
  • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf8。
  • 参数4:必选参数,文件写入完成后的回调函数。
const fs = require('fs');
fs.writeFile('./hello.txt', 'hello node', (err) => {
    // 判断是否写入成功
    if (err) return console.log(err);
    console.log('写入成功');
});

读取指定目录下的所有文件的名称

使用fs.readdir()方法,可以读取指定目录下所有文件的名称,语法格式如下:

fs.readdir(path[, options], callback)
  • 参数1:必选参数,表示要读取哪个目录下的文件名称列表。
  • 参数2:可选参数,以什么格式读取目录下的文件名称,默认值是utf8。
  • 参数3:必选参数,读取完成以后的回调函数。
const fs = require('fs');
fs.readdir('./', (err, data) => {
    // 错误处理
    if (err) return console.log(err);
    console.log(data);
});

路径动态拼接的问题

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

使用__dirname获取当前文件所在的绝对路径。

const fs = require('fs');
// 拼接要读取文件的绝对路径
let filepath = __dirname +'/hello.txt'
fs.readFile(filepath, 'utf-8', (err, data) => {
    // 判断是否读取成功
    if (err) return console.log(err);
    console.log(data); 
});

path路径模块

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

例如:

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

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

const path = require('path')

路径拼接 —— path.join()

使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下。

path.join([...paths])
  • ...paths 路径片段的序列
  • 返回值:
const path = require('path');

console.log( path.join('a', 'b', 'c') ); // a/b/c
console.log( path.join('a', '/b/', 'c') ); // a/b/c
console.log( path.join('a', '/b/', 'c', 'index.html') ); // a/b/c/index.html
console.log( path.join('a', 'b', '../c', 'index.html') ); // a/c/index.html
console.log(__dirname); // node自带的全局变量,表示当前js文件所在的绝对路径
// 拼接成绩.txt的绝对路径
console.log( path.join(__dirname, '成绩.txt') ); // ------ 最常用的

获取路径中的文件名 —— path.basename()

使用 path.basename() 方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下。

path.basename(path[,ext])
  • path 必选参数,表示一个路径的字符串。
  • ext 可选参数,表示可选的文件扩展名。
  • 返回: 表示路径中的最后一部分。
// 找文件名
console.log( path.basename('index.html') ); // index.html
console.log( path.basename('a/b/c/index.html') ); // index.html
console.log( path.basename('a/b/c/index.html?id=3') ); // index.html?id=3
console.log(path.basename('/api/getbooks')) // getbooks

获取路径中的文件扩展名 —— path.extname()

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

path.extname(path)
  • path 必选参数,表示一个路径的字符串。
  • 返回: 返回得到的扩展名字符串。
// 找字符串中,最后一个点及之后的字符
console.log( path.extname('index.html') ); // .html
console.log( path.extname('a.b.c.d.html') ); // .html
console.log( path.extname('asdfas/asdfa/a.b.c.d.html') ); // .html
console.log( path.extname('adf.adsf') ); // .adsf

http模块

http模块是Node.js官方提供的、用来创建web服务器和客户端的模块。它提供了一系列的方法和属性,例如:

  • http.createServer() 方法,用来创建一个web 服务器,从而对外提供web资源。
  • http.request() 方法,用来发起 http 网络请求,请求其它web 服务器上的资源。

如果要在JavaScript代码中使用http模块,则需要先导入它。

const http = require('http')

创建最基本的web服务器

创建web服务器的基本步骤

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

// 2. 创建 web 服务器实例 
const server = http.createServer();

// 3. 启动服务器
server.listen(3000, () => {
    console.log('my server start work');
});

// 4. 为服务器实例绑定 request 事件,监听客户端的请求 
// 当客户端发送请求到服务器的时候,会触发这个事件
server.on('request', () => {
    // 这里要处理客户端的请求
    console.log('hello html');
});

计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。 同样的道理,在一台服务器中,可以运行成百上千个web 服务。此时,通过端口号,客户端发送过来的网络请求,可以被准确地交给端口号对应的web服务进行处理。

request请求对象

只要服务器接收到了客户端的请求,就会调用通过server.on('request', (req, res) => {}) 为服务器绑定的request事件处理函数。 req是客户端提交过来的请求内容,如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:

接收req参数:

  • 获取请求的地址: req.url
  • 获取请求方式: req.method
  • 获取请求头: req.headers

request请求获取用户提交的get参数

request请求获取用户提交的get参数步骤:

1、引入node核心模块中的url模块

2、使用url模块中的parse方法,url.parse()

参数1: url地址

参数2: 是否生成query参数对象

实例:url.parse(req.url, true).query

// 引入node核心模块中的url模块
const url = require('url')
const http = require('http');
const server = http.createServer();
server.listen(3000, () => console.log('服务器开启了'));

server.on('request', (req, res) => {
    //使用url模块中的parse方法,第二个参数给true代表参数解析成query对象
    let obj = url.parse(req.url, true).query
    // 输出拿到的参数对象
    console.log(obj)
    res.end('hello web');
});

request获取用户提交的post参数

post参数是以数据流的方式提交给服务器的所以无法一次性拿到所有参数。需要监听数据流事件:

data事件:有数据提交给服务器时触发

end事件: 所有数据提交完成时触发

步骤:

1、引入node核心模块中的querystring模块

2、定义一个空字符串变量,用于准备拼接参数

3、监听req的data事件,拼接参数。

4、监听req的end事件,处理参数并完成业务逻辑。

// 引入node核心模块中的querystring模块
const querystring = require('querystring')
const http = require('http');
const server = http.createServer();
server.listen(3000, () => console.log('服务器开启了'));

server.on('request', (req, res) => {
    // 定义变量准备拼接参数
    let str = ''
    // 监听data事件,拼接参数
    req.on('data', chunk => {
      str += chunk
    })
    // end事件在所有数据提交完成时触发,监听end事件,开始处理参数。
    eq.on('end', () => {
        // 输出拼接好的参数
        console.log(str)
        // 使用querystring的parse方法把str转为对象
        let obj = querystring.parse(str)
        console.log(obj)
        
    })
    
    res.end('hello web');
});

response响应对象

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

设置res参数:

完成响应(可以配置页面输出的内容): res.end('hello html')

设置响应体: res.write('1234')

设置响应头: res.setHeader('Author', 'zhangsan')

设置响应状态码: res.statusCode = 200

综合性的设置响应状态码和响应头:

res.writeHead(200, {

'aa': 'bb'

'Author': 'zhangsan'

});

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

res.setHeader('Content-Type', 'text/html; charset=utf-8');
// 1. 加载 http 模块
const http = require('http');
// 2. 创建server 对象
const server = http.createServer();
// 3. 监听端口,开启服务器
server.listen(3000, () => console.log('my server running'));
// 4. 注册server的request事件,准备处理浏览器的请求
server.on('request', (req, res) => {
    // req request  请求,通过req对象可以获取到所有和请求相关的信息
    // res response 响应,通过res对象可以做出响应以及设置一些和响应相关的内容

    // // 设置响应头
    // res.setHeader('Content-Type', 'text/html; charset=utf-8');
    // res.setHeader('Author', 'zhangsan'); // 自己设置响应头,不要用中文
    // // 设置响应状态码
    // res.statusCode = 200;

    // 综合性的设置响应状态码和响应头的方法
    res.writeHead(200, {
        'Content-Type': 'text/html; charset=utf-8',
        'Author': 'zhangsan'
    });
    // write方法,也可以设置响应体,但是没有做出响应的意思,只是单纯的设置响应体
    res.write('1234');
    res.write('5678');
    // res.end(响应体); // 做出响应
    res.end('hello,你的请求我收到了');
    // 做出响应之后,不能再有其他代码。
});

npm

npm(node package manager)node包管理器。包就是模块。npm这个工具,在安装node的时候,就已经安装到你的计算机中了。命令行中执行: npm -v ,如果看到版本号,说明安装成功了。

npm的作用就是管理node模块的工具。

  • 下载并安装第三方的模块
  • 卸载第三方模块
  • 发布模块
  • 删除已发布的模块
  • ....

第三方模块:

  • 非内置模块,安装完node,还不能使用的模块,需要从网上下载安装,才能使用的模块
  • 第三方模块是个人、公司、组织编写的模块,发布到网上,供我们使用

初始化

使用npm工具之前,必须先初始化。

npm init -y
# 或
npm init
# 然后一路回车

初始化之后,会在项目目录中生成 package.json 的文件。

安装卸载项目模块

下载安装第三方模块:

npm install 模块名

npm i 模块名

卸载模块:

npm uninstall 模块名

npm un 模块名

下载安装的模块,存放在当前文件夹的 node_modules 文件夹中,同时还会生成一个记录下载的文件 package-lock.json。下载的模块,在哪里可以使用:在当前文件夹、在当前文件夹的子文件夹,在当前文件夹的子文件夹的子文件夹。。。怎样使用第三方模块呢?和使用内置模块一样,需要使用 require 加载模块。调用模块提供的方法完成工作(一般好的模块都会用使用文档的)。

全局模块

全局安装的模块,不能通过 require() 加载使用。全局安装的模块,一般都是命令或者工具。安装方法就是在安装模块的命令后面,加 -g

npm i 模块名 -g
# 或
npm i -g 模块名
  • 查看全局安装的模块: npm list -g --depth 0
  • 卸载方法(也是多一个 -g): npm un 模块名 -g
  • 全局安装的模块,在系统盘(C盘):通过命令 npm root -g 可以查看全局安装路径。

nodemon模块

nodemon的作用是代替node命令,启动服务的,当更改代码之后,nodemon会自动帮我们重启服务。

安装nodemon模块命令: npm i nodemon -g

本地安装和全局安装区别

有两种方式用来安装 npm 包:本地安装和全局安装。选用哪种方式来安装,取决于你如何使用这个包。

  • 本地安装: 想把我们用的包,安装到当前本地项目代码中使用。

比如npm i jquery , npm i moment。执行的安装命令(npm i moment) 位置,必须在当前项目根目录下执行。包位置在本地安装的包 => 当前项目下的 node_modules。使用包const moment = require('moment') , const $ = require('jquery')。

  • 全局安装: 想把一个包/库,当成一个工具来使用, 就采用全局安装。

比如npm i -g live-server。可以在任意地方, 都可以执行命令。包位置在C:\Users\用户名\AppData\Roaming\npm。使用包在终端命令行里使用, 不能在代码里。

dependencies作用是保存依赖包的记录。以后如果看到我们项目中 没有node_modules目录,但是有 package.json。说明我们项目是完整的。执行 npm i 会找 package.json 里面的 dependencies,并且安装里面的所有依赖包。执行npm i命令要在 package.json 同级目录。

更改镜像源

镜像源,就是下载安装第三方模块的网站。我们下载的第三方模块都是从国外的npm主站下载的,速度比较慢。淘宝在国内对npm上的第三方模块做了一个备份,也就是说,我们可以从国内下载第三方模块。除了淘宝之外,还有很多其他镜像源。

简单的更改镜像源方法:

  • 全局安装 nrm 的模块

nrm 用于管理镜像源

  • 使用nrm nrm ls 通过这个命令,可以查看可用的镜像源。 nrm use taobao,切换下载模块的网站为淘宝。

模块化

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。 对于整个系统来说,模块是可组合、分解和更换的单元。编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。

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

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

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。例如:使用什么样的语法格式来引用模块 (require('fs'))。在模块中使用什么样的语法格式向外暴露成员。模块化规范的好处在于大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

模块分类

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

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

无论是什么模块,我们都要使用 require() 去加载,然后才能使用。加载自定义的模块,需要加 ./ ,而且可以省略后缀 .js

// 加载核心模块
const fs = require('fs');

// 加载第三方模块
const express = require('express');

// 加载自定义模块
const custom = require('./custom');

模块作用域

在Node.js中,用户创建的每个 .js 文件都是自定义模块。在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。模块作用域的好处是避免了全局变量污染。由于模块具有一个模块级别的作用域,则另一个JS文件就无法使用当前模块定义的内容。

导入导出模块

为了能正常使用加载的模块中的成员,CommonJS给出了标准,即

  • 一个模块需要使用 module.exports 导出需要共享的内容。
  • 使用模块的JS文件需要使用 require() 导入模块。 模块导出的是什么,另一个使用模块的JS文件得到的就是什么。

require()具有缓存效果。使用 require() 加载模块后,会缓存起来,下次再调用 require()加载相同模块的时候,直接使用缓存,而不是从新加载,从而大大提高了性能。

CommonJS规范

CommonJS规范的出现是为了实现模块化。模块化规范的种类有AMD、CMD、CommonJS(Node.js遵循CommonJS)、ES6。

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

CommonJS 规定:

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

require()加载模块的机制

加载自定义模块和其他模块的机制有相同之处,也有不同之处,所以这里分开来看。

加载自定义模块

  1. 首次加载成功,会缓存模块。
  2. 下次从缓存中加载,速度更快。
  3. 加载自定义模块必须加 ./ ,如果是其他路径,对应变化,否则会把它当做核心模块或者第三方模块。
  4. 加载自定义模块的时候,如果是 require('./abc')。 (1)优先加载相同名字的文件,加载一个叫做 abc 的文件。

(2)自动补 .js 后缀,然后加载 abc.js 文件。

(3)自动补 .json 后缀,然后加载 abc.json 文件。

(4)自动补 .node 后缀,然后加载 abc.node 文件。

(5)以上文件都没有,则报错 Cannot find module './abc'

加载核心模块和第三方模块

  1. 首次加载成功,会缓存模块。
  2. 下次从缓存中加载,速度更快。
  3. 加载模块一定不能./ ,否则会把它当做自定义模块。
  4. 加载模块的时候,如果是 require('haha')

(1)优先加载核心模块。

(2)去查找并加载第三方模块,查找第三方模块的路径可以通过 module.paths 查看。

(3)加载第三方模块会从当前目录开始寻找node_modules文件夹,如果找到进入node_modules文件夹寻找对应的模块。如果没找到,进入上一级目录继续寻找node_modules,一直到根目录。如果一直没有找到,提示未找到模块。

express

Express是一个第三方模块,用于快速搭建服务器。它是一个基于 Node.js 平台,快速、开放、极简的web开发框架。express保留了http模块的基本API,使用express的时候,也能使用http的API。express还额外封装了一些新方法,能让我们更方便的搭建服务器。

安装express

项目文件夹中,执行 npm i express。即可下载安装express。注意:express不能安装在express文件夹中。否则安装失败。

使用Express构造Web服务器

使用Express构建Web服务器步骤:

  1. 加载 express 模块。
  2. 创建 express 服务器。
  3. 开启服务器。
  4. 监听浏览器请求并进行处理。
// 使用express 搭建web服务器
// 1) 加载 express 模块
const express = require('express');

// 2) 创建 express 服务器
const app = express();

// 3) 开启服务器
app.listen(3006, () => console.log('express服务器开始工作了'));

// 4) 监听浏览器请求并进行处理

app.get('GET请求的地址', 处理函数);

app.post('POST请求的地址', 处理函数);

express之所以能够实现web服务器的搭建,是因为其内部对核心模块http进行了封装。封装之后,express提供了非常方便好用的方法。需要注意的是,在express中,我们仍然可以使用http模块中的方法。

  • express

    • express.static() -- 开放静态资源
    • express.urlencoded() -- 获取POST请求体
    • 其他...
  • app

    • app.get() -- 处理客户端的GET请求
    • app.post() -- 处理客户端的POST请求
    • app.use() -- 设置应用级别的配置
    • 其他...
  • req

    • req.body -- 获取POST请求体
    • req.params -- 获取GET请求动态参数
    • req.query -- 获取GET请求参数(获取查询字符串参数)
    • 其他...
  • res

    • res.sendFile(文件的绝对路径) -- 读取文件,并将结果响应
    • res.set({name, value}) -- 设置响应头
    • res.status(200) -- 设置响应状态码
    • res.send(字符串或对象) -- 响应结果
    • res.json(对象) -- 以JSON格式响应结果
    • res.jsonp() -- 以JSONP格式响应结果
    • 其他...
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));

app.get('/test', (req, res) => {
    // express提供的新方法:res.send(); -- 做出响应,参数是响应体
    // res.send('hello,我是服务器'); // 会自动设置响应头
    // res.end(["aaa", "bbb", "ccc"]); // 报错,end方法,只能填字符串参数
    // res.send(["aaa", "bbb", "ccc"]); // 对于数组、对象等,会自动JSON.stringify()

    // express提供的新方法:sendFile(); -- 参数是一个文件(绝对)路径,会自动读取文件的内容,做出响应
    // res.sendFile(path.join(__dirname, 'books.json'));

    // express提供的新方法:res.json() -- 专门用于响应json数组的方法
    // res.json({
    //     status: 0,
    //     message: '添加成功'
    // });

    // express提供的新方法:res.set() -- 设置响应头一样,不能设置状态码
    res.set({
        'Author': 'zhangsan'
    });
    res.send('看看响应头');
});

get接口

app.get('请求的URL', callback)。

app.get('*', (req, res) => {}):它能够匹配到所有的GET请求,所以把它放到所有接口的最后。

app.get('/api/getbooks', (req, res) => {
    // 处理GET方式的/api/getbooks接口
});

app.get('/', (req, res) => {
    // 客户端没有指定请求的url,在这里处理。
});

app.get('*', (req, res) => {
    // 处理所有的GET请求
})

获取GET方式请求的参数

获取url?参数=值&参数=值

app.get('/test', (req, res) => {
    console.log(req.query); // { id: '3', bookname: 'zxx', age: '20' }
});

获取 url/:id/:name/:age

  • 这种方式的参数,叫做动态参数
  • 请求地址的写法:http://localhost:3006/test/3/zs/30
  • 要求请求的url参数必填,否则不能和定义的接口匹配
// 1个参数
// 浏览器的请求  http://localhost/test/3
// 测试接口,获取动态参数
app.get('/test/:id', (req, res) => {
	console.log(req.params); // 可以获取所有的动态参数 { id: 3 }
    res.send('收到参数');
});

// 多个参数
// 浏览器的请求  http://localhost/test2/3/zhangsan/20
// 测试接口,获取多个动态参数
app.get('/test2/:id/:name/:age', (req, res) => {
    console.log(req.params); // 可以获取所有的动态参数  
    // { id: '4', name: 'zhangsan', age: '20' }
    res.send('全部收到');
});

post接口

app.post('请求的URL', callback);

// app.post('请求的URL', callback);
app.post('/api/addbook', (req, res) => {
    // 处理POST方式的/api/addbook接口
});

app.post('*', (req, res) => {
    // 处理所有的POST请求
})

获取POST请求体

GET方式没有请求体,POST方式才有请求体。请求体,即客户端提交的数据。我们仍然可以使用http模块中的语法,获取请求体。

POST请求体,有哪些格式

  • 查询字符串 -- 对应的Content-Type: application/x-www-form-urlencoded
  • FormData对象 -- 对应的Content-Type: multipart/form-data; --XXADFsdfssf

接收post请求体

  • POST请求体的类型(Content-Type)

    • application/x-www-form-urlencoded 比如:id=1&name=zs&age=20
    • form-data 比如,提交的是FormData对象
    • application/json 比如,提交的是 {"id": 1, "name": "zs", "age": 20}
    • 其他...
  • 服务器端接收不同类型的请求体,使用的方式是不同的

    • urlencoded ---> app.use(express.urlencoded({extended: false}));
    • application/json ---> app.use(express.json());
    • form-data ---> 服务器端使用第三方模块处理(multer

开放静态资源

  • 什么叫做静态资源

    • css文件
    • 图片文件
    • js文件
    • 等等
  • 什么叫做开放静态资源

    • 开放,即允许客户端来访问
  • 具体做法

    • 比如,允许客户端访问public文件夹里面的文件
    • app.use(express.static('public'))

路由

广义上来讲,路由就是映射关系。在程序中的路径也是映射关系。在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。 Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下。

// 路径 ,就是我们之前说的接口的处理程序
app.get('/api/getbooks', (req, res) => {
    
});

app.post('/api/getbooks', (req, res) => {
    
});

app.post('/api/addbook', (req, res) => {
    
});

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

模块化路由

  1. 创建路由模块对应的 .js 文件

    (1) 创建router/login.js 存放 登录、注册、验证码三个路由

    (2) 创建router/heroes.js 存放 和英雄相关的所有路由

  2. 调用 express.Router() 函数创建路由对象

const express = require('express');
const router = express.Router();
  1. 向路由对象上挂载具体的路由
// 把app换成router,比如
router.get('/xxx/xxx', (req, res) => {});
router.post('/xxx/xxx', (req, res) => {});
  1. 使用 module.exports 向外共享路由对象
module.exports = router;
  1. 使用 app.use() 函数注册路由模块 -- app.js
// app.js 中,将路由导入,注册路由
const login = require('./router/logon.js');
app.use(login)

// app.use(require('./router/heroes.js'));
app.use( require(path.join(__dirname, 'router', 'heores.js')) );

为路由模块添加前缀

我们可以省略路由模块中的 /api 前缀,而是在注册路由的时候,统一设置。

app.use('/api', router);

具体地,在app.js中:

// 导入路由模块,并注册路由
app.use('/api',  require(path.join(__dirname, 'router', 'login.js'))  );
app.use('/my',  require(path.join(__dirname, 'router', 'heroes.js'))  );

路由文件中,把前缀 /api 和 /my 去掉。

使用路由模块的好处

  • 分模块管理路径,提高了代码的可读性
  • 可维护性更强
  • 减少路由的匹配次数
  • 权限管理更方便
  • etc...

MySql

数据库

数据库 (database) 是用来组织、存储和管理数据的仓库。 当今世界是一个充满着数据的互联网世界,充斥着大量的数据。数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。除了文本类型的数据,图像、音乐、声音都是数据。为了方便管理互联网世界中的数据,就有了数据库管理系统的概念(简称:数据库)。用户可以对数据库中的数 据进行新增、查询、更新、删除等操作。

常见的数据库及分类

市面上的数据库有很多种,最常见的数据库有如下几个:

  • MySQL 数据库(目前使用最广泛、流行度最高的的开源免费数据库;)
  • Oracle 数据库(收费)
  • SQL Server 数据库(收费)
  • Mongodb 数据库(Community + Enterprise)

其中,MySQL、Oracle、SQL Server 属于传统型数据库(又叫做:关系型数据库 或 SQL 数据库),这三者的 设计理念相同,用法比较类似。

而 Mongodb 属于新型数据库(又叫做:非关系型数据库 或 NoSQL 数据库),它在一定程度上弥补了传统型 数据库的缺陷。

  • 数据库服务器

    • 数据库(一般来说,一个项目,都会使用一个独立的数据库)

      • 数据表(真正存储数据的地方)

        • 行与列(每一行代表一条数据。列又叫做字段) 真正存储数据的是数据表。数据表和我们见过的Excel表格结构基本相同。

安装MySQL

MySQL 服务器软件 ---- 存储数据,可以创建数据库、数据表。MySQL图形化管理工具 --- 可以使用它管理(创建、增删改查等等)数据库。

安装操作MySQL的图形化工具(Navicat)

图形化的管理工具,有很多种。例如,mysql-workbeach(英文版,没有中文版)、Navicat。前面已经安装了MySQL软件。那么我们如何管理或者说使用它呢,对于我们来说,还需要安装一个管理MySQL的工具,它就是 Navicat

Navicat使用

创建表

比如创建一个学生信息表。

image.png 对于一张表,最重要的是表头的设计。对于数据库中的数据表,最重要的设计也是表头,只不过在数据库中把表头叫做字段。

image.png

  • id -- 自动递增 -- √

  • 最后保存,填表名 student

  • 其他补充点

    • 数据库中的数字类型

      • tinyint -128~127
      • smallint -65535 ~ 65535
      • int -21亿 ~ 21亿
      • bigint 更大
      • 数据库中的字符串类型
      • varchar - 变长字符串类型
      • char - 定长字符串类型

导入导出数据表

  • 导出:在数据表名字上,比如 student 上,右键 --> 转储SQL文件 --> 结构和数据,选择保存位置保存即可。
  • 导入:在数据库名上面 --> 右键 --> 运行SQL文件 --> 选择SQL文件,运行即可完成导入。导入注意事项,表名不能重复。

sql语句

SQL(英文全称:Structured Query Language)是结构化查询语言,专门用来访问和处理数据库的编程语言。SQL 是一门数据库编程语言。使用 SQL 语言编写出来的代码,叫做 SQL 语句。SQL 语言只能在关系型数据库(例如 MySQL、Oracle、SQL Server)中使用。非关系型数据库(例如 Mongodb) 不支持 SQL 语言。SQL可以 从数据库中查询数据、向数据库中插入新的数据、更新数据库中的数据、从数据库删除数据、可以创建新数据库、可在数据库中创建新表、可在数据库中创建存储过程、视图。

数据查询

SQL语句,不区分大小写。

基本的查询语法:

SELECT 字段1,字段2,... FROM 表名

select 字段,字段,.... from 表名

查询所有的字段:

SELECT * FROM 表名

带条件的查询:

SELECT * FROM 表名 [WHERE 条件] [ORDER BY 排序字段[, 排序字段]] LIMIT [开始位置,]长度

例如,查询所有英雄的姓名和昵称。

select name,nickname from heroes

查询全部英雄的全部信息。

select * from heroes

sql语句

带where子句的查询

select field1, field2... from 表名 查询表中的所有数据。

where 可以使用条件来筛选查询出的结果。

查询id小于10的英雄。

select * from heroes where id<10

查询id小于20的女英雄。

select * from heroes where id<20 and sex='女'

查询年龄大于等于20小于等于30的英雄。

select * from heroes where age>=20 and age<=30
select * from heroes where age between 20 and 30

模糊查询

通配符:

  • %: 代表任意长度(包括0)的任意字符
  • _: 代表1位长度的任意字符

like: 在执行模糊查询时,必须使用like来作为匹配条件

查询名字中带有 “斯” 的英雄。

select * from heroes where name like '%斯%'

查询名字的最后一个字是 “斯” 的英雄。

select * from heroes where name like '%斯'

查询名字中带有 “斯” ,并且要求 “斯”前面只能有一个字的英雄。

select * from heroes where name like '_斯%'

统计查询

  • max 查询最大值 select max(age) from heroes
  • Min 查询最小值 select min(age) from heroes
  • avg 查询平均值 select avg(age) from heroes
  • sum 查询总和(查询所有英雄的年龄之和) select sum(age) from heroes
  • count 查询总记录数(查询共计有多少个英雄) select count(*) cc from heroes

查询结果排序

order by 可以对查询结果按某个字段进行升序或者降序排列

  • 升序 asc (默认值)
  • 降序 desc

可进行排序的字段通常是 整型 英文字符串型 日期型 (中文字符串也行,但一般不用)。需要注意的是,如果SQL语句中,有where和order by,where一定要放到order by之前。

查询所有的英雄,按年龄升序排序。

select * from heroes order by age asc
select * from heroes order by age

查询所有的英雄,按年龄降序排序。

select * from heroes order by age desc

查询所有的英雄,先按年龄降序排序;如果年龄相同的,再按id降序排序。

select * from heroes order by age desc, id desc

查询年龄大于50岁的英雄,并按年龄降序排序。

select * from heroes where age>50 order by age desc

限制查询结果

limit 用来限制查询结果的起始点和长度。

  • 格式: limit start, length

    • start: 起始点。 查询结果的索引,从0开始。 0代表第一条数据。如果省略start,则默认表示从0
    • length: 长度 需要注意的是,where、order by、limit如果一起使用,是有顺序的,where在最前面、其次是order by、limit要放到最后。另外三者之间没有and之类的。

查询所有英雄中前5个英雄。

select * from heroes limit 起始位置, 长度
select * from heroes limit 0, 5
select * from heroes limit 5

查询所有英雄中,第6到10个英雄。

select * from heroes limit 5, 5

查询年龄最大的3个英雄。

select * from heroes order by age desc limit 3

查询年龄最大的3个女英雄。

select * from heroes where sex='女' order by age desc limit 3

连接查询

当一张表中的数据不能满足我们查询数据的需求时,我们要考虑多个表进行连接查询。MySQL中的连接查询,能够连接查询的两张或多张表,必须有关系才行。

语法:

select 字段 from 表1 join 表2 on 两表的关系 [where ... order by .... limit ...]

select 字段 from 表1 join 表2 on 两表的关系 join 表3 on 表的关系 .... [where ... order by .... limit ...]

select * from student join teacher on student.teacher_id=teacher.Id

给表名定义别名,简化SQL。

select * from student s join teacher t on s.teacher_id=t.Id 

指定字段查询。

select s.id, s.name, s.age, s.sex, t.name teacher_name from student s join teacher t on s.teacher_id=t.Id 

总结

  • 控制查询的列数

    • select 字段, 字段, ... from heroes ---- 选择查询指定的列
    • select * from heroes ---- 查询所有的列
  • 控制查询的行数

    • where 条件

      • where id>3
      • where id>3 and age<30
      • where id<3 or sex='男'
      • where name like '%斯%'
    • order by 字段 排序规则

      • order by age asc
      • order by age desc
    • limit 起始位置, 长度

      • limit 0, 3 相当于 limit 3
      • limit 3, 3
  • 连接查询

    • select 字段 from 表1 join 表2 on 两表的关系 join 表3 on 表的关系 .... [where ... order by .... limit ...]

select 字段 from 表名 [where 条件] [order by 排序字段 排序规则] [limit 起始位置,长度]

select count(*) as total from 表名 -- 查询总记录数

添加数据

基本的格式: insert into 表名 .....

往表里添加数据有三种方式。

方式一:指定字段和值,只要字段和值对应即可。和顺序无关。

insert into heroes (字段, 字段, ...) values (值, 值, ...)
insert into heroes (nickname, age, name) values ('哈哈', 98, '科加斯')

方式二:和顺序有关,因为没指定字段,所以值必须是所有的值,而且顺序和表中字段的顺序要一致。

insert into heroes values (null, '拉克丝', '光辉女郎', '女', 28)

方式三:使用set里设置新数据的值,没有顺序关系。

insert into heroes set 字段=值, 字段=值, ....
insert into heroes set name='李青', nickname='盲僧'

修改数据

格式: update 表名 set 字段1=值1, 字段2=值2,... where 修改条件

修改表中的哪一条(几条)数据的 字段1=值1...需要注意的是, 不指定修改条件会修改所有的数据。

加条件修改:

update heroes set age=28, skill='在地上打滚' where id=19

如果不指定条件,则会修改所有的行:

update heroes set sex='女'

删除数据

格式: delete from 表名 where 删除条件

delete from heroes where id=19

需要注意的是,不指定条件将删除所有数据。 不加条件,将删除所有的数据,危险操作。

delete from heroes

删除stu表:

drop table stu

node中的mysql模块

mysql模块是一个第三方模块,专门用来操作MySQL数据库。 可以执行增删改查操作。

curd: 就代表数据库的增删改查

c: create 就是添加 (增)

u: update 就是修改 (改)

r: read 就是查询 (查)

d: delete 就是删除 (删)

mysql模块的使用步骤

在Node中使用MySQL模块一共需要5个步骤:

  1. 加载 MySQL 模块
  2. 创建 MySQL 连接对象
  3. 连接 MySQL 服务器
  4. 执行SQL语句
  5. 关闭链接

1. 加载mysql模块

const mysql = require('mysql');

2. 创建连接对象(设置连接参数)

const conn = mysql.createConnection({
    // 属性:值
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: '',
    database: 'xx'
});

3. 连接到MySQL服务器

conn.connect();

4. 完成查询(增删改查)

conn.query(SQL语句, [SQL中占位符的值], (err, result, fields) => {
    err: 错误信息
    result: 查询结果
    fields: 当前查询过程中涉及到的字段信息,一般用不着
});

5. 关闭连接,释放资源

conn.end();

要完成增删改操作,只需要将SQL语句换成增删改语句即可。

对于增删改语句,返回的result是一个表示SQL执行结果的对象。其主要属性如下:

  • insertId 添加时有该属性,表示新增数据的id
  • affectedRows 受影响行数,表示受影响的行数。增删改的时候都有该属性
  • changRows 改变的行数,修改操作的时候,会有该属性

占位符模式的增删改查

SQL中的“?” 就是占位符。比如:

  • select * from heroes where id > ?
  • insert into heroes set ?
  • update heroes set ? where id = ?
  • delete from heroes where id = ?

当SQL语句中使用了占位符,则query方法需要使用参数2为这些占位符传递实际的值。并且不同的 "?" 需要的值格式也不同。具体要符合下面三种要求:

  1. SQL中有 1 个占位符,则query方法的第二个参数设置为一个值
  2. SQL中有 多 个占位符,则query方法的第二个参数设置为数组,数组中的值按顺序分别传递给每个占位符
  3. SQL中,如果 字段=值,字段=值...使用 “?” 站位了,则需为这个 “?” 传递一个对象,形式如下:
let val = {
    // 字段: 值
    name: '压缩',
    nickname: '疾风剑豪',
    // 其他...
}
let sql = 'select * from heroes where id < ?';
conn.query(sql, 3, (err, result) => {
    if (err) throw err;
    console.log(result);
});