根据学习B站周老师记录的笔记,并且也做了对应的思维导图,搭配食用更佳

Node.JS简介

何为Node.JS

  • Node.JS为JavaScript运行时
  • Node.JS是JavaScript的运行平台
  • 它既不是一门语言,也不是框架,它是一个平台

Node.JS中的JS

  • Node.JS没有dom和bom
  • EcmaScript
  • Node.JS在JavaScript执行环境中为JavaScript提供一些服务器级别的API
    • 文件读写
    • 网络服务构建
    • 网络通信
    • http服务器
    • ……

Node.JS构建于Chrome的V8引擎

  • 代码只是具有特定格式的字符串
  • 引擎可以认识它,帮你解析和执行
  • Google Chrome的V8引擎是目前公认的解析执行JavaScript代码最快的
  • Node.js的作者把Google Chrome中的V8引擎移植出来,开发了一个独立的JavaScript运行时环境

Node.JS的特性

  • envent-driven 事件驱动
  • non-blocking I/O mode 非阻塞I/O模型(异步)
  • ightweight and efficent. 轻量和高效

Node.JS的作用

  • web服务器后台
  • 命令行工具
    • npm(node)
    • git(c语言)
    • hexo(node)

其它

npm为世界上最大的开源生态系统

初识

安装配置

  • 下载:https://nodejs.org/en/
  • 安装node环境
  • 检测node环境
    • 开启命令行提示符(cmd)
    • 输入node --vesion
    • node -v
    • 正常状态下返回版本号

解析执行JavaScript

  • 创建和编写JavaScript脚本文件
  • 打开终端(cmd),定位脚本文件所在位置(也就是执行脚本所在文件目录)
  • 输入node 文件名进行执行

注意:文件名不要使用Node.JS命名,并且最好不用中文

fs

文件操作模块

  • 浏览器中的 JavaScript 没有文件操作能力
  • Node中的 JavaScript 具有文件操作能力
  • fs 是 file-system 的简写,为文件系统的意思
  • 在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块
  • 在 fs 这个核心模块中,就提供了所有文件操作相关的 API,
    • fs.readFile 用来读取文件
    • fs.writeFile用来写入文件
  • 文件中存储的都是二进制数据,用toString()方法转换为字符串

读取文件

  1. 使用require方法加载fs核心模块

  2. 使用readFile函数读取文件

    • 两个参数,第一个为文件路径,第二个为回调函数

    • 回调函数中有两个参数

    • 第一个参数为返回错误,第二个为数据

    • 成功:error: null; data: 返回数据

    • 失败: error: 错误对象; data: undefined

    • 可以利用error进行语句判断

1
2
3
4
5
6
7
8
9
10
11
//使用node中的fs模块读取文件(使用require语句)
var a = require('fs');
//readFIle方法读取文件内容
a.readFile('测试.txt',function(error,data){
console.log(error);
if(error === null){
console.log(data.toString());
} else {
console.log("文件错误,读取文件失败");
}
});

写入文件

  1. 使用require方法加载fs核心模块
  2. 使用writeFile函数读取文件
    • 包含三个参数
    • 第一个参数为文件路径及名称
    • 第二个参数为写入文本内容
    • 第三个参数为回调函数
    • 回调函数中的error参数
      文件写入成功 :errornull
      文件写入失败: error 就是错误对象
1
2
3
4
5
6
7
8
9
var a = require('fs');
a.writeFile('你是最棒的.md','你是最棒的',function(error) {
console.log(error);
if (error === null) {
console.log("写入成功");
} else {
console.log("写入失败");
}
});

http

IP地址和端口号

  • IP地址用来定位计算机
  • 端口号用来定位具体应用程序
  • 所有需要联网的应用程序都需要占用一个端口号
  • 计算机默认端口号尽量不要访问
    • 80

服务器

  • 提供服务:为数据服务
  • 发请求
  • 接收请求
  • 处理请求
  • 反馈(发送响应)
  • 当客户端请求过来,就会自动触发服务器的request请求事件,然后执行第二个参数:回调处理函数

启动服务器

  1. 加载http模块
  2. 创建web服务对象
  3. 监听(注册)request事件
  4. 绑定端口号,启动服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//加载http模块
var http = require('http')

//创建web服务对象
var server = http.createServer()

//注册request事件
server.on('request',function(){
console.log('收到客户端请求')
})

//绑定端口号,启动服务
server.listen(3000,function(){
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问')
})

request请求事件处理函数

  • request 请求事件处理函数,需要接收两个参数:requestresponse
  • request 请求对象:请求对象可以用来获取客户端的一些请求信息,例如请求路径
  • reponse响应对象:响应对象可以用来给客户端发送响应消息
  • 响应内容只能是二进制数据或者字符串
  • 转出数据内容为其他数据类型时需转化格式
  • 一个请求对应一个响应,已经结束响应则无法重复发送响应
  • 无请求就无响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//加载http模块
var http = require('http')
//创建web服务
var server = http.createServer()
//监听request事件,设置请求处理函数,函数参数调用request和response
server.on('request', function (request,response) {
console.log('请求路径为' + request.url) //request.url为请求路径
console.log('请求我的客户端的地址是:', request.socket.remoteAddress,
request.socket.remotePort)
//响应条件判断,判断请求路径

//json测试
var phone = [
{
name: 'apache',
price: 80000,
lcd: 'a'
},
{
name: 'asdfg',
price: 5000,
lcd: 'd'
},
{
name: 'hello',
price: 25000,
lcd: 'c'
}]

switch (request.url) {
case '/':
response.write('你好啊')
response.end()
break;
case '/a':
response.write('hello')
response.end()
break;
case '/b':
response.end('leihoua') //可以直接使用.end()函数
break;
case '/c':
response.end(JSON.stringify(phone)) //转化为字符串格式
break;
default:
response.write('当前路径未存在')
response.end()
break;
}

})
//绑定端口号,启动服务
server.listen(3000,function() {
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问')
})

Content-type

  • 服务器默认发送的数据为utf8编码内容
  • 浏览器在不知道浏览器响应内容编码的情况下,会按当前操作系统默认编码解析
  • 中文操作系统默认编码为gbk
  • 在 http 协议中,Content-Type 就是用来告知对方我给你发送的数据内容是什么类型
  • 不同的资源对应的 Content-Type 是不一样
  • Content-Type工具:http://tool.oschina.net/commons
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//加载http模块
var http = require('http')
//创建web服务对象
var server = http.createServer()
//注册request监听事件
server.on('request',function (req,res) {
if(req.url === '/index') {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello,你好鸭')
} else if (req.url === '/html') {
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('<p>来呀来呀,快来<a href="#">点我</a>鸭</p>')
}
// text/plain 就是普通文本
// 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送是 text/html 格式的内容
})
//绑定端口号,启动服务
server.listen('3000',function () {
console.log('server begin...')
})

返回文件(http-fs)

  • 发送的并不是文件,本质上来讲发送是文件的内容(一堆字符串)

  • 当浏览器收到服务器响应内容之后,就会根据你的 Content-Type 进行对应的解析处理

  • 结合 fs 发送文件中的数据

  • 字符数据需指定编码

  • 图片不需要指定编码

根据url返回内容

  1. 加载指定模块

  2. 创建服务对象

  3. 监听request事件

    1. 判断请求的url
    2. 为对应的url返回文件
      1. 读取文件内容
      2. 响应输出文件内容(注意转换格式与编码)
  4. 绑定端口号,启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//加载指定模块
var fs = require('fs')
var http = require('http')
//创建服务对象
var server = http.createServer()
//监听request事件
server.on('request', function (req,res) {
var url = req.url;
if(url === '/index'){
//读取页面文件
fs.readFile('09-data/abc.html', function(err,data){
if(err) {
console.log('读取文件失败')
} else{
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data.toString())
}
})
} else if (url === '/img') {
//读取图像文件
fs.readFile('09-data/601db542da7bc1d7ecbfb218685332a.jpg', function(err,data) {
if(err) {
console.log('无法读取该文件')
} else {
res.setHeader('Content-Type', 'image/jpeg')
res.end(data);
}
})
} else {
res.end('404')
}
})
//绑定端口号,启动服务
server.listen('3000', function() {
console.log('server is running')
})

浏览器输入路径打开文件

​ 需求:用户在浏览器输入文件名即可访问该文件内容

  1. 将用户输入的文件转化为fs所找文件路径
  2. fs读取文件
  3. res响应文件内容
  4. 输出文件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//引用模块
var http = require('http')
var fs = require('fs')
//创建服务
var server = http.createServer()
//监听request事件
server.on('request', function(req, res) {
var www = 'C:/Users/12524/Desktop/Node.JS'
var url = req.url //不能为中文路径
fs.readFile(www + url, "GBK", function(err, data) {
if (err) {
return res.end('404,nod found') //return停止代码执行
} else {
res.end(data)
}
})
})
//设置端口号,发布服务
server.listen('3000', function() {
console.log('server is running')
})

重定向

状态码301和302

301

  • 永久性的重定向,搜索引擎在抓取新内容的同时将旧网址替换为重定向之后的网址

302

  • 暂时性跳转,搜索引擎抓取新的内容的同时保留旧网址
  • 服务器返回302时,搜索引擎认为新网址时暂时的

path

路径操作模块

基本操作

官方文档:https://nodejs.org/docs/latest-v13.x/api/path.html

  • path.basename:获取路径的文件名,默认包含扩展名
  • path.dirname:获取路径中的目录部分
  • path.extname:获取一个路径中的扩展名部分
  • path.parse:把路径转换为对象
    • root:根路径
    • dir:目录
    • base:包含后缀名的文件名
    • ext:后缀名
    • name:不包含后缀名的文件名
  • 🔺path.join:拼接路径(第二个参数会自动判断/是否多余)
  • path.isAbsolute:判断一个路径是否为绝对路径

body-parser

简介

body post解析中间件

处理程序之前,在中间件中对传入的请求体进行解析(response body)

处理post请求体

body-parser 提供四种解析器
JSON body parser
Raw body parser
Text body parser
URL-encoded form body parser

安装

1
npm i body-parser

引入

1
var bodyParse = require("body-parse")

配置

1
2
3
4
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

使用

1
2
3
router.post('/login',function(req,res){
console.log(req.body)
})

原生获取post请求体方式

记得先引入querystring模块

querystring用作分割请求体内容并转化为对象格式

因为有时候会用到文件上传,所以这里要判断数据请求头的content-type,如果是multipart/form-data,则让formidable中间件去处理,否则自己处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.use((req, res, next) => {
let data = ``
if (req.method.toLowerCase() === 'get') {
return next()
}
// 如果是有文件的表单POST,则不处理
if (req.headers['content-type'].startsWith('multipart/form-data')) {
req.on('data', chunk => {
data += chunk
})
req.on('end', () => {
req.body = queryString.parse(data)
next()
})
}
})

node中的其他成员

__dirname() __filename

  • __dirname:动态获取当前模块文件所处目录的绝对路径
  • __filename: 动态获取当前文件的绝对路径

在文件操作中,使用相对路径是不可靠的,因为node中文件操作的路径被设计为相对于执行node命令所处的路径。为了解决这个问题,需要使用__dirname()__filename把相对路径变为绝对路径(绝对路径不受任何影响)。

在拼接路径的过程中,为了避免手动拼接带来的一些错误,就可以使用path.join()来辅助拼接

1
2
3
4
5
6
7
8
9
10
11
12
var fs = require('fs')
var path = require('path')

//path会将路径名和文件名拼接起来且能识别适应文件名前路径形式
fs.readFile(path.join(__dirname, 'a.txt'), 'utf8', function(err, data) {
if (err) {
console.log('error')
} else {
console.log(data)
console.log(path.join(__dirname, './a.txt'))
}
})

注意:

模块中的路径标识和文件操作中的相对路径标识不一致

模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响

node中的模块系统

模块化

何为模块化

  • 文件作用域(模块是独立的,在不同的文件使用必须要重新引用)
    • 注意: 在node中没有全局作用域,它是文件模块作用域
  • 通信规则
    • 加载: require
    • 导出: exports

模块类型

  • 核心模块
    • 文件操作的fs
    • http服务操作的http
    • url路径操作模块
    • path路径处理模块
    • os操作系统信息
    • ……
  • 第三方模块
    • art-template
    • 必须通过npm来下载才可以使用
  • 自己写的模块
    • 自己创建的文件

模块标识

./ 相对于当前路径(在文件操作中可省略)

/在当前模块所处的磁盘根目录

javascript模块化(需补充)

  • Node 中的 CommonJS
  • 浏览器中的:
    • AMD require.js
    • CMD sea.js
  • es6中增加了官方支持

CommonJS模块规范

  • 模块作用域
  • 使用require方法来加载模块
  • 使用exports接口对象来导出模板中的成员

加载require

使用require函数加载模块

  • 若调用核心模块和第三方模块,必须加上相对路径./,可以省略后缀名

  • require作用

    • 加载文件模块并执行里面的代码
    • 拿到被加载文件模块导出的接口对象
1
var http = require('http')

require方法加载规则

  • 优先从缓存加载(即不会重复调用同个模块中的函数)
    • 避免重复加载,提高模块加载效率
    • node会自动寻找当前文件路径的node_modules,从而加载第三方包,若没有,则会继续往上一级目录找,直到找到(如没找到则报错)

main.js

1
2
3
4
5
6
7
8
9
require('./a')

// 优先从缓存加载
// 由于 在 a 中已经加载过 b 了
// 所以这里不会重复加载
// 可以拿到其中的接口对象,但是不会重复执行里面的代码
var fn = require('./b')

console.log(fn)

a.js

1
2
3
4
console.log('a.js 被加载了')
var fn = require('./b')

console.log(fn)

b.js

1
2
3
4
5
console.log('b.js 被加载了')

module.exports = function () {
console.log('hello bbb')
}

第三方模块加载过程

art-template为例

  1. 先找到当前项目路径的node_modules
  2. node-modules/art-template
  3. node-modules/art-template/package.json
  4. 找到说明文件中的main属性
    • main属性记录art-template的入口模块
  5. 加载使用这个第三方包
    • 实际加载的还是index.js文件
  6. 若,package.json或main指定的入口模块不存在则node会找该目录下的index.js文件
  7. 条件均不满足则自动往上一级目录查找

导出exports

使用exports函数导出需被外部访问的成员

  • exports
    • 在每个文件模块中都提供了一个对象:exports
    • exports 默认是一个空对象
    • 将需被外部访问的成员挂载到这个 exports 对象中

导出多个成员

方法一

1
2
3
4
5
6
7
8
exports.a = 123;
exports.b = function(){
console.log('bbb')
};
exports.c = {
foo:"bar"
};
exports.d = 'hello';

方法二

1
2
3
4
5
6
module.exports = {
foo = 'hello',
add:function(){
return x+y;
}
};

导出单个成员(这时引用该模块,直接返回hello字符串)

1
module.exports = 'hello';

案例:假设存在a模块和b模块,现在要用a模块访问b模块的成员

a模块:

1
2
3
4
5
console.log('hello,i am a');
var b = require('./b.js');//调用该模块的同时,将该模块内容引入
// require('./b.js');
console.log(b.bbb(10,20));
console.log('bye,a are leave');

b模块:

1
2
3
4
5
6
7
// var c = require('./c');
console.log('hello,i am b.');
require('./c');
exports.bbb = function(a,b) {
return a + b;
};
console.log('bye,b are leave');

c模块

1
2
console.log('hello,i am c');
console.log('bye,c are leave');

结果

image-20200502145729142

module.exports和exports的关系

真正要导出数据是module.exports,而node为了方便我们操作,所以指定了一个变量exports等同于module.exports,,如下代码所示

1
var exports = module.exports

所以

1
2
exports.a = 2
exports.b = 3

这些值最终会等同于module.exports.a = 2和module.exports.a = 3,同时会被导出

但如果,你这样写代码

1
exports = 2

那么,你相当于改了exports的值,那么它就会与module.exports分道扬镳,它的值也不会等于module导出的值

所以,为了保险起见,新手最好使用module.exports导出你要导出的内容,避免错误

npm

介绍

NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

网站

npmjs.com 网站 是用来搜索npm包的

npm命令行工具

npm也有版本概念,可以通过npm --version来查看npm的版本

升级npm(自己升级自己):

1
npm install --global npm

常用命令

  • npm init(生成package.json说明书文件)

  • npm init -y(可以跳过向导,快速生成)

  • npm install

    • 一次性把dependencies选项中的依赖项全部安装
    • 简写(npm i)
  • npm install 包名

    • 只下载
    • 简写(npm i 包名)
  • npm install –save 包名

    • 下载并且保存依赖项(package.json文件中的dependencies选项)
    • 简写(npm i 包名)
    • –save 自动生成说明说文件信息package.json
    • 文件信息自动添加至package.json
    • 若删除掉某些项目使用的模块但有保留package.json,则可在命令行(当前项目文件夹下)执行 npm install 全部自动下回来
  • npm uninstall 包名

    • 只删除,如果有依赖项会依然保存
    • 简写(npm un 包名)
  • npm uninstall –save 包名

    • 删除的同时也会把依赖信息全部删除
    • 简写(npm un 包名)
  • npm help

    • 查看使用帮助
  • npm 命令 –help

    • 查看具体命令的使用帮助(npm uninstall –help)

package.json

每一个项目都要有一个package.json文件(包描述文件,就像产品的说明书一样)

这个文件可以通过npm init自动初始化出来

对于目前来讲,最有用的是dependencies选项,可以用来帮助我们保存第三方包的依赖信息。

如果node_modules删除了也不用担心,只需要在控制面板中npm install就会自动把package.json中的dependencies中所有的依赖项全部都下载回来。

  • 建议每个项目的根目录下都有一个package.json文件
  • 建议执行npm install 包名的时候都加上--save选项,目的是用来保存依赖信息

package.json和package-lock.json

npm 5以前是不会有package-lock.json这个文件

npm5以后才加入这个文件

当你安装包的时候,npm都会生成或者更新package-lock.json这个文件

package.json用处

  1. 提升下载速度
    • npm5以后的版本安装都不要加--save参数,它会自动保存依赖信息
    • 当你安装包的时候,会自动创建或者更新package-lock.json文件,这个文件会包含node_modules中所有包的信息(版本,下载地址等),这样的话重新npm install的时候速度就可以提升
  2. 锁定版本号
    • 从文件来看,有一个lock,称之为锁
    • 这个lock使用来锁版本的
    • 如果项目依赖了1.1.1版本
    • 如果你重新install其实会下载最新版本,而不是`1.1.1``
    • ``package-lock.json`的另外一个作用就是锁定版本号,防止自动升级

Express

案例:express留言板、学生信息增删改

简介

Express 是一个简洁而灵活的 Node.JS Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

使用 Express 可以快速地搭建一个完整功能的网站。

Express 框架核心特性:

  • 可以设置中间件来响应 HTTP 请求。
  • 定义了路由表用于执行不同的 HTTP 请求动作。
  • 可以通过向模板传递参数来动态渲染 HTML 页面。

安装

1
2
npm init //生成说明文件
npm install --save express

image-20200509202639542

简单使用

  1. 引包
  2. 创建服务器应用程序
  3. 接受请求,返回响应
  4. 发布服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//引包
var express = require('express')

//创建服务
var app = express()

//开放文件路径
app.use('/public', express.static('./public/'))

//接收请求,返回响应
app.get('/', function(req,res) {
res.send('成功啦!')
})

//发布服务
app.listen('3000', function() {
console.log('server is running...')
})

art-template

安装

1
2
npm install --sava art-template
npm install -save express-art-template

配置

1
2
app.engine('html', require('express-art-template'))
app.set('/views', './views/') //可以更改模板引擎默认调用资源文件夹路径,注意书写

使用

1
2
3
res.render('index.html', {
students: students.students
})

初识(数据渲染)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var express = require('express')
var fs = require('fs')

var app = express()

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

app.engine('html', require('express-art-template'))
app.set('/views', './views/') //可以更改模板引擎默认调用资源文件夹路径,注意书写

app.get('/', function(req, res) {
fs.readFile('db.json', 'utf8', function(err, data) {
if (err) {
return console.log('数据有误')
}
// console.log(JSON.parse(data))
var students = JSON.parse(data)
res.render('index.html', {
students: students.students
})
})
})

app.listen(3000, function() {
console.log('server is running...')
})

express-session

官方文档:https://github.com/expressjs/session

安装

1
npm i express-session

配置

  • 该插件会为req请求对象添加一个成员:req.session默认是一个对象
1
2
3
4
5
6
7
8
9
//配置session中间件
app.use(session({
//自定义字符串来对sessionid进行加密处理,避免出现相同的sessionid
secret: 'dong yi',
resave: false,
//无论是否使用了session,设置为true都会默认都会给予钥匙(sessionid)
saveUninitialized: false
cookie: { secure: true }
}))

使用

1
2
3
4
5
6
7
8
9
10
11
12
//写
//添加Session数据
//session就是一个对象
req.session.foo = 'bar';

//读
//获取session数据
req.session.foo

//删
req.session.foo = null;
delete req.session.foo //推荐

其它

session:保存登录选项,🔺在内存中存储

cookie:保存不敏感数据

MongoDB

案例:案例4 学生信息(增删改-mongoose)

关系型和非关系型数据库

关系型数据库

表就是关系,或者说表与表之间存在关系

  • 所有的关系型数据库都需要通过sql语言来操作
  • 所有的关系型数据库在操作之前都需要设计表结构
  • 而且数据表还支持约束
    • 唯一的
    • 主键
    • 默认值
    • 非空

非关系型数据库

  • 非关系型数据库非常的灵活
  • 有的关系型数据库就是key-value对
  • 但MongDB是长得最像关系型数据库的非关系型数据库
    • 数据库 -》 数据库
    • 数据表 -》 集合(数组)
    • 表记录 -》文档对象

一个数据库中可以有多个数据库,一个数据库中可以有多个集合(数组),一个集合中可以有多个文档(表记录)

1
2
3
4
5
6
7
8
//也就是说你可以任意的往里面存数据,没有结构性这么一说
{
qq(数据库):{
user(集合):[
{(文档)},{},{}...
]
}
}

安装

下载

🔺环境配置(安装)

node使用(npm)

1
npm i mongoose

检测环境

控制台输入mongod --version

启动和关闭数据库

启动数据库服务

1
mongod --dbpath D:\mongodb\data\db

关闭服务

1
2
1.在开启服务的控制台,直接Ctrl+C;
2.直接关闭开启服务的控制台。

启动数据库,连接本地MongoDB服务

1
mongo

退出

1
exit

mongo基本命令

  • show dbs

    查看数据库列表(数据库中的所有数据库)

  • db

    查看当前连接的数据库

  • use 数据库名称

    切换到指定的数据库(没有会新建)

  • show collections

    查看当前目录下的数据表

  • db.表名.find()

    查看表中信息

  • InsertOne()

    插入数据(对象形式,命名字符串加双引号)

    image-20200524113123433

  • dropDatabase()

    删除数据库

  • db.collection.remove({})

    删除文档

  • db.collection.drop()

    删除集合

🔺Node中操作MongoDB

mongoose初识

1
2
3
4
5
6
7
8
9
10
//引包
const mongoose = require('mongoose');
//创建数据库
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true});
//定义集合
const Cat = mongoose.model('Cat', { name: String });
//添加数据
const kitty = new Cat({ name: 'Zildjian' });
//保存成功提示(ES6)
kitty.save().then(() => console.log('meow'));

设计文档结构(表结构)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//引用架构对象
var Schema = mongoose.Schema
//设计集合结构以及约束
var catSchema = new Schema({
name: {
type: String,
required: true //约束:必须给值
},
age: {
type: Number,
required: true
}
})
//创建集合并引用集合结构以及添加约束
const Cat = mongoose.model('Cat', catSchema)

数据添加(增)

1
2
3
4
5
6
//添加数据(记录)
const kitty0 = new Cat({ name: 'Zild', age: 6 });
const kitty1 = new Cat({ name: 'jian', age: 7 });
//保存并提示(ES6)
kitty0.save().then(() => console.log('meow'));
kitty1.save().then(() => console.log('meow'));

数据删除(删)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cat.deleteOne({name:'Zild'}, function(err,ret){
if(err){
console.log('删除失败')
}else {
console.log(ret)
}
})
Cat.deleteMany({name:'Zild'},function(err,ret){
if(err){
console.log('删除失败')
}else(){
console.log(ret)
}
})
//remove方法官方不推荐使用,这里不考虑

数据更改(改)

  • 根据id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cat.where({ _id: "5ec9c7012909a22e7c89b5ac" }).update({ name: 'Zild', age: 8 }, function(err, ret) {
if (err) {
console.log('更新失败')
} else {
console.log(ret)
}
})

Cat.findByIdAndUpdate({ _id: "5ec9c7012909a22e7c89b5ac" }, { name: 'Zild', age: 9 }, function(err, ret) {
if (err) {
console.log("查询失败")
} else {
console.log(ret) //返回修改前的数据
}
})
  • 根据文档(记录)
1
2
3
4
5
6
7
Cat.findOneAndUpdate({ name: 'Zild' }, { name: 'Zild1', age: 9 }, function(err, ret) {
if (err) {
console.log("查询失败")
} else {
console.log(ret) //返回修改前的数据
}
})

数据查询(查)

查询全部

1
2
3
4
5
6
7
Cat.find(function(err, ret) {
if (err) {
console.log("查询失败")
} else {
console.log(ret)
}
})

查询指定属性的全部对象

1
2
3
4
5
6
7
Cat.find({ name: 'Zild' }, function(err, ret) {
if (err) {
console.log("查询失败")
} else {
console.log(ret)
}
})

查询指定属性的单个对象

1
2
3
4
5
6
7
Cat.findOne({ name: 'Zild' }, function(err, ret) {
if (err) {
console.log("查询失败")
} else {
console.log(ret)
}
})

🔺异步编程

得到函数内部异步操作的结果

回调函数:通过一个函数,获取函数内部的操作。(根据输入得到输出结果)

  • 在该情况下无法获得函数内异步操作的结果
1
2
3
4
5
6
7
8
9
10
11
function get(a, b) {
console.log(a + b)
setTimeout(function() {
console.log(2)
ret = a + b
return ret
}, 6000);
console.log(3)
}

console.log(get(20,30))

结果:返回undefined

image-20200524151737456

  • 若要获得该数据则只能通过回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function get(a, b, c) {
console.log(a + b)
setTimeout(function() {
console.log(2)
ret = a + b
return c(ret)
}, 6000);
console.log(3)
}

// console.log(get(20,30))

get(20, 30, function(a) {
console.log(a)
})

结果

image-20200524152155139

  • 注意:

    凡是需要得到一个函数内部异步操作的结果(setTimeout,readFile,writeFile,ajax,readdir)

    这种情况必须通过 回调函数 (异步API都会伴随着一个回调函数)

回调地狱

啥是回调地狱

就是这幅图(图源网络)

image-20200527211637374

为什么会有回调地狱

回调地狱的原因是,当人们试图以一种从上到下的视觉方式执行JavaScript的方式编写JavaScript时。期望第1行发生的任何事情都会在第2行的代码开始运行之前完成,但是,在JavaScript上,有时候这并没办法进行,比如,在你通过异步读取文件时,也就是用fs模块读取多个文件时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require('fs')

fs.readFile('./a.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

fs.readFile('./b.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

fs.readFile('./c.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

执行多次后,你会发现,有那么几次,也有可能好几次,看人品吧反正是,它是没有规则的读出来的(往往可能你的文件越大,读出来的时间会更久),也就是说,它并不会按照代码书写顺序去执行,这便是异步编程(如果试了没有,那就一直试,反正总会有的)。异步API导致了代码并不是按顺序执行的(可以读读这篇文章 https://www.jianshu.com/p/39adf6ab8ad1 ——然后嘞,就会有上面那种解决方法,但是你会发现,代码非常的丑(别人是这样说的,反正我不是太这么认为,甚至觉得有点好看),还有非常难维护(这点认同)。所以就出现了几种解决方法 —Promise

Promise

  • Promise:承诺,保证
  • Promise本身不是异步的,但往往都是内部封装一个异步任务

丢出一张图形容Promise函数,相当于一个容器(下图源于所学教程,pending(悬而未决的))

image-20200527213850773

代码如下,较易维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var fs = require('fs')

//resolved(解决(成功)),rejected(驳回(失败))
var p1 = new Promise(function(resolved, rejected) {
//文件编码!!!!!!
fs.readFile('./a.txt', 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

var p2 = new Promise(function(resolved, rejected) {
fs.readFile('./b.txt', 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

var p3 = new Promise(function(resolved, rejected) {
fs.readFile('c.txt', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

//链式编程,🔺Promise会默认将then中return的值实例成一个promise对象,所以可以调用then方法,实现链式调用
p1
.then(function(data) {
console.log(data)
return p2
})
.then(function(data) {
console.log(data)
return p3
}).then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})

then函数(ES6)说明:

image-20200527214049975

封装Promise中的readFile方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var fs = require('fs')

var pReadFile = function(filepath) {
return new Promise(function(resolved, rejected) {
//文件编码!!!!!!
fs.readFile(filepath, 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})
}

pReadFile("a.txt")
.then(function(data) {
console.log(data)
return pReadFile("b.txt")
})
.then(function(data) {
console.log(data)
return pReadFile("c.txt")
}).then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})

Promise应用场景

解决客户端回调嵌套问题

当出现类似于表关联的数据时,这时候就会遇到嵌套问题,当嵌套的数据只有一两个个还好,如果出现三四个甚至五六个,这时候就会出现回调地狱的问题,这里使用promise解决

所需知识:
  1. npm模块:json-server、http-server
  2. 客户端模板引擎art-template
  3. Ajax
  4. jquery
步骤
  1. 安装json-server和http-server以及其他必要模块

    1
    2
    3
    4
    npm i -g http-server
    npm i -g json-server
    npm i jquery --save
    npm i template --save
  2. 建立html页面

  3. 设计表单,人员信息与工作职业相关联,搭配模板字符串使用

    1
    <form action="" id="user_form"></form>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script type="text/template" id="tpl">
    <div>
    <label for="">用户名</label>
    <input type="text" value="{{ user.username }}">
    </div>
    <div>
    <label for="">年龄</label>
    <input type="text" value="{{ user.age }}">
    </div>
    <div>
    <label for="">职业</label>
    <select name="" id="">
    {{ each jobs }} {{ if user.job === $value.id }}
    <option value="{{ $value.id }}" selected>{{ $value.name }}</option>
    {{ else }}
    <option value="{{ $value.id }}">{{ $value.name }}</option>
    {{ /if }} {{ /each }}
    </select>
    </div>
    </script>
  4. 引用相关模板字符串以及JQuery模块

    1
    2
    <script src="node_modules/art-template/lib/template-web.js"></script>
    <script src="node_modules/jquery/dist/jquery.js"></script>
  5. 书写Ajax向服务器发起请求,并封装便于使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
        	function get(url, callback) {
    var oReq = new XMLHttpRequest()
    oReq.onload = function () {
    oReq.responseText
    callback(oReq.responseText)
    }
    oReq.open("get", url, true)
    oReq.send()
    }
  6. 开启json-server服务,使用data.json文件(67步使用cmd)

    1
    json-server data.json
  7. 将当前文件所处文件夹开放为服务器

    1
    http-server
    • 若要禁用缓存,则使用以下命令
    1
    http-server -c-1
  8. 若采用回调地狱类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    get("http://127.0.0.1:3000/users/1",function(userData){
    get("http://127.0.0.1:3000/jobs",function(jobsData){
    var htmlStr = template("tpl", {
    user: JSON.parse(userData),
    jobs: JSON.parse(jobsData)
    })
    console.log(htmlStr)
    document.querySelector("#user_form").innerHTML = htmlStr
    })
    })
  9. 使用Jquery版的Promise

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //Jquery的Ajax自带promise
    var data = {}
    $.get("http://127.0.0.1:3000/users/2")
    .then(function(user){
    data.user = user
    return $.get("http://127.0.0.1:3000/jobs")
    })
    .then(function(jobs){
    data.jobs = jobs
    // console.log(data)
    var str = template("tpl",{
    user: data.user,
    jobs: data.jobs
    })
    document.querySelector('#user_form').innerHTML = str
    })
  10. 封装Promise版本的AJAX方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Rget(url,callback){
    return new Promise(function(resolve,reject){
    var xhr = new XMLHttpRequest()
    // 当请求加载成功之后要调用指定的函数
    xhr.onload = function () {
    // 我现在需要得到这里的 xhr.responseText
    resolve(JSON.parse(xhr.responseText))
    callback && callback(JSON.parse(xhr.responseText))
    }
    xhr.onerror = function (err){
    reject(err)
    }
    xhr.open("get", url, true)
    xhr.send()
    })
    }
    • 使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var data = {}
    Rget("http://127.0.0.1:3000/users/2")
    .then(function(user){
    data.user = user
    return Rget("http://127.0.0.1:3000/jobs")
    })
    .then(function(jobs){
    data.jobs = jobs
    var str = template("tpl", {
    user: data.user,
    jobs: data.jobs
    })
    document.querySelector("#user_form").innerHTML = str
    })

    Promise操作数据库

(26-Promise,Promise操作数据库)

  • mongoose中所有的API都支持promise

🔺根据查询是否已存在该记录从而决定是否创建新记录

1
2
3
4
5
6
7
8
9
10
11
Cat.findOne({ name: "好啊" })
.then(function(cat){
if(cat){
console.log('该cat已存在')
} else{
return new Cat({"name" : "好啊", "age" : 16 }).save()
}
})
.then(function(data){
console.log(data)
})

注意:

  1. 每次改完js或html文件后在浏览器需刷新多次
  2. 每次改完json文件后需要重新启动json-server服务

catch异常处理

在全部then之后添加.catch(err => {})即可对任何一个then处理过程抛出的异常进行捕获并中止代码继续执行

例如:读取文件并进行后续相关操作,若处理过程发生一个错误则传递给catch,后面所有的then就不再执行

这里要注意区分,如果是在then中自行处理err,则代码还是会继续往下执行,这是和catch不同的点

1
2
3
4
5
6
7
8
9
10
11
readFile('a.txt', 'utf8')
.then(data => {
console.log(data)
return readFile('a.txt', 'utf8')
})
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})

中间件

案例:案例5论坛

中间件的概念

参考文档:http://expressjs.com/en/guide/using-middleware.html

中间件:把很复杂的事情分割成单个,然后依次有条理的执行。就是一个中间处理环节,有输入,有输出。

说的通俗易懂点儿,中间件就是一个(从请求到响应调用的方法)方法。

把数据从请求到响应分步骤来处理,每一个步骤都是一个中间处理环节。

形象化如这个图:自来水厂的净水过程

自来水

同一个请求对象所经过的中间件都是同一个请求对象和响应对象。

中间件的分类:

应用程序级别的中间件

万能匹配(不关心任何请求路径和请求方法的中间件):

1
2
3
4
app.use(function(req,res,next){
console.log('Time',Date.now());
next();
});

关心请求路径和请求方法的中间件:

1
2
3
4
app.use('/a',function(req,res,next){
console.log('Time',Date.now());
next();
});

路由级别的中间件

严格匹配请求路径和请求方法的中间件

get:

1
2
3
app.get('/',function(req,res){
res.send('get');
});

post:

1
2
3
app.post('/a',function(req,res){
res.send('post');
});

put:

1
2
3
app.put('/user',function(req,res){
res.send('put');
});

delete:

1
2
3
app.delete('/delete',function(req,res){
res.send('delete');
});

Express的中间件

Express 中,对中间件有几种分类

  • 当请求进来,会从第一个中间件开始进行匹配

    • 如果匹配,则进来
      如果请求进入中间件之后,没有调用 `next` 则代码会停在当前中间件
      如果调用了 `next` 则继续向后找到第一个匹配的中间件
    • 如果不匹配,则继续判断匹配下一个中间件
  • 不关心请求路径和请求方法的中间件,也就是说任何请求都会进入这个中间件

  • 中间件本身是一个方法,该方法接收三个参数:

    • Request 请求对象
    • Response 响应对象
    • next 下一个中间件
    • 当一个请求进入一个中间件之后,如果不调用 next 则会停留在当前中间件
      所以 next 是一个方法,用来调用下一个中间件的
      调用 next 方法也是要匹配的(不是调用紧挨着的那个)

错误处理中间件

1
2
3
4
app.use(function(err,req,res,next){
console.error(err,stack);
res.status(500).send('Something broke');
});

配置使用404中间件:(论坛案例)

1
2
3
app.use(function(req,res){
res.render('404.html');
});

配置全局错误处理中间件: (论坛案例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.get('/a', function(req, res, next) {
fs.readFile('.a/bc', funtion() {
if (err) {
// 当调用next()传参后,则直接进入到全局错误处理中间件方法中
// 当发生全局错误的时候,我们可以调用next传递错误对象
// 然后被全局错误处理中间件匹配到并进行处理
next(err);
}
})
});
//全局错误处理中间件
app.use(function(err,req,res,next){
res.status(500).json({
err_code:500,
message:err.message
});
});

内置中间件

第三方中间件

参考文档:http://expressjs.com/en/resources/middleware.html

  • body-parser
  • compression
  • cookie-parser
  • mogran
  • response-time
  • server-static
  • session

art-template搭配dateformat模块

简介

art-template搭配dateFormat实现将时间戳格式化为你想要的日期格式

步骤

  1. npm下载对应模块

    1
    npm i --save dateformat
  2. 页面代码(注意art-template默认渲染的页面后缀名为art,time为渲染的时间数据,’yyyy-mm-dd’为你要定义的时间格式)

    1
    {{ dateFormat(time, 'yyyy-mm-dd')}}
  3. node引用

    1
    2
    3
    const template = require('art-template');
    const path = require('path');
    const dateFormat = require('dateformat');
  4. 配置模板引擎

    1
    template.defaults.imports.dateFormat = dateFormat;
  5. 渲染页面并打出在cmd控制台

    1
    2
    3
    4
    const html = template('06.art', {
    time: new Date()
    });
    console.log(html)

    其它

nodemon

作用

修改完代码自动重启

安装

1
npm install --global nodemon

使用

1
nodemon 文件

ES6函数

find()

方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

实现原理

1
2
3
4
5
6
7
8
9
10
Array.prototype.findme = function(callback) {
for (var i = 0; i < this.length; i++) {
if(callback(this[i],i)){
return this[i]
}
}
}
console.log(abc.findme(function(item){
return item.id = 3
}))

findIndex()

返回数组中满足提供的测试函数的第一个元素的索引

隐藏元素控件

用来放一些不希望被用户看见,但是需要被提交到服务端的数据

1
<input type="hidden" name="id" value="{{ student.id }}">