五、过渡内容
-
模块系统
- 核心模块
- 第三方模块
- art-template
- 必须通过npm来下载才可以使用
- 自己写的模块
- 自己创建的文件
-
npm
-
package.json
-
Express
- 第三方Web开发框架
- 高度封装了http模块
- 更加专注于业务,而非底层
-
增删改查
- 使用文件来保存数据
-
MongoDB
- 所有方法都封装好了
-
什么是模块化
- 文件作用域
- 通信规则
- 加载require
- 导出
-
CommonJS模块规范
在Node中的JavaScript还有一个很重要的概念,模块系统
- 模块作用域
- 使用require方法来加载模块
- 使用exports接口对象来导出模块中的成员
-
加载
require
语法:
var 自定义变量名称 = require('模块')
两个作用:
- 执行被加载模块中的代码
- 得到被加载模块中的exports导出接口对象
-
导出exports
-
Node中是模块作用域,默认文件中所有的成员只在当前文件模块中有效
-
对于希望可以被其他模块访问的成员,我们需要把这些公开的成员挂载到exports接口对象
-
导出多个成员(必须在对象中)
exports.a = 123 exports.b = 'hello' exports.c = function () { console.log('ccc') } exports.d = { foo: 'bar' }
-
导出单个成员(拿到的就是:函数、字符串)
module.exports = 'hello'
以下情况会覆盖:
module.exports = 'hello' //以这个为准 module.exports = { add: function() { return x + y } str: 'hello' }
-
也可以这样来导出多个成员
module.exports = { add: function() { return x + y }, str: 'hello' }
-
原理解析
exports和module.exports是一个引用
console.log(exports === module.exports) //true exports.foo = 'bar' //等价于 module.exports.foo = 'bar'
-
exports与module.exports的一些注意点
- exports === module.exports结果为true
- 对于
module.exports.xxx=xxx
的方式完全可以使用exports.xxx=xxx
- 当一个模块需要导出单个成员时,这个时候必须使用
module.exports=xxx
的方式 - 不要使用
exports=xxx
,不管用 - 每个模块向外
return
的是module.exports
exports
只是module.exports
的一个引用- 即便为
exports=xx
重新赋值,也不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports=module.exports
,这个用来重新建立引用关系
-
-
require加载规则
- 优先从缓存加载
- 判断模块标识
-
package.json
-
我们建议每一个项目都要有一个
package.json
文件(包描述文件,就像产品说明书一样) -
这个文件可以通过
npm init
的方式初始化- 对于咱们目前来说,最有用的是
dependencies
选项,可以帮我们保存第三方包的依赖信息 - 如果node_module删除了也不用担心,
npm install
会自动把package.json
中的dependencies
- 对于咱们目前来说,最有用的是
-
示例
C:\Users\zc\Desktop\npm-demo>npm init This utility will walk you through creating a package.json file. version: (1.0.0) See `npm help init` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. version: (1.0.0) 0.0.1 description: 这是一个测试项目 entry point: (index.js) main.js test command: git repository: keywords: author: zc license: (ISC) About to write to C:\Users\zc\Desktop\npm-demo\package.json: { "name": "npm-demo", "version": "0.0.1", "description": "这是一个测试项目", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "zc", "license": "ISC" } Is this OK? (yes) yes
对于目前来说,最有用的文件是package.json,可以用来保存第三方包的依赖信息
- 建议每个项目的根目录下都有一个
package.json
文件 - 建议执行
npm install 包名
的时候都加上--save
这个选项,目的是保存依赖项信息 - 如果
node_modules
丢失了也不用担心,我们只需要执行npm install
,就会自动把package.json
中的dependencies
中所有的依赖项都下载回来
- 建议每个项目的根目录下都有一个
-
-
package.json与package-lock.json
注意:npm 5以前是不会有
package-lock.json
这个文件的npm 5以后才加入了这个文件
安装包时,npm都会生成或更新
package-lock.json
这个文件-
npm 5以后的版本安装包不需要加
--save
参数,它会自动保存依赖信息 -
当你安装包的时候,会自动创建或更新
package-lock.json
这个文件 -
package-lock.json
这个文件会保存node_modules
中所有包的信息(版本、下载地址)- 这样的话重新
npm install
的时候速度就会提升
- 这样的话重新
-
从文件来看,有一个
lock
称之为锁-
这个
lock
是用来锁定版本的 -
如果项目依赖了
1.1.1
版本,重新install会下载最新版本,而不是1.1.1 -
我们的目的是希望可以锁定1.1.1版本,所以
package.json
这个文件的另一个作用就是锁定版本号,防止自动升级新版
-
-
-
npm常用命令
-
npm网站:https://www.npmjs.com
-
npm命令行工具
- npm版本
npm --version
- 升级npm(自己升级自己)
npm install --global npm
-
npm init
npm init -y可以跳过向导,快速生成
-
npm install
一次性把dependencies中的依赖项全部安装
-
npm install 包名
只下载
npm i 包名
-
npm install --save 包名
下载并保存依赖项(package.json文件中的dependencies)
npm i -S 包名
-
npm uninstall 包名
只删除,如果有依赖项会依然保存
npm un 包名
-
npm uninstall --save 包名
删除的同时也会把依赖信息也去除
npm un -S 包名
-
npm help
查看使用帮助
-
npm 命令 --help
查看指定命令的使用帮助
例如忘记了npm uninstall的简写时,可以输入
npm uninstall --help
查看帮助
-
-
解决npm被墙问题
npm存储包文件的服务器在国外,有时候会很慢,需要解决这个问题
cnpm:http://npm.taobao.org
# 在任意目录下执行都可以 # --global表示安装到全局,而非当前目录 # --global不能省略,否则不管用 npm install --global cnpm
把之前的npm替换为cnpm
举个例子
# 这里还是走国外的npm服务器,速度比较慢 npm install jquery # npm install jquery cnpm install jquery
如果不想安装cnpm又想使用淘宝的服务器来下载
npm install jquery --registry=https://registry.npm.taobao.org
但是每次手动这样加参数很麻烦,所以我们可以把这个选项加入配置文件中
npm config set registry https://registry.npm.taobao.org # 查看npm配置信息 npm config list
只要经过了上面命令的配置,则以后所有的npm install都会默认通过淘宝的服务器来下载
-
path路径操作模块
- path.basename获取一个路径的文件名(默认包含扩展名)
- path.dirname获取一个路径中的目录部分
- path.extname获取一个路径中的扩展名部分
- path.parse把一个路径转为对象
- root 根路径
- dir 目录
- base 包含后缀名的文件名
- ext 后缀名
- name 不包含后缀名的文件名
- path.join需要使用路径拼接时推荐这个方法
- path.isAbsolute判断一个路径是否是绝对路径
> path.basename('c:/a/b/c/index.js') 'index.js' > path.basename('c:/a/b/c/index.js', '.js') 'index' > path.basename('c:/a/b/c/index.js', '.html') 'index.js' > path.dirname('c:/a/b/c/index.js') 'c:/a/b/c' > path.extname('c:/a/b/c/index.js') '.js' > path.extname('c:/a/b/c/index.html') '.html' > path.isAbsolute('c:/a/b/c/index.html') true > path.isAbsolute('c/index.html') false > path.isAbsolute('./c/index.html') false > path.isAbsolute('/c/index.html') true > path.parse('c:/a/b/c/index.html') { root: 'c:/', dir: 'c:/a/b/c', base: 'index.html', ext: '.html', name: 'index' } > path.join('c:/a','b') 'c:\\a\\b' > var str = ''c:a/' + '/b' var str = ''c:a/' + '/b' ^ Uncaught SyntaxError: Unexpected identifier > var str = 'c:a/' + '/b' undefined > str 'c:a//b' > var str = 'c:a' + 'b' undefined > str 'c:ab' > path.join('c:/a/', '/b') 'c:\\a\\b' > path.join('c:/a/', '/b', 'c') 'c:\\a\\b\\c' > path.join('c:/a/', '/b', 'c', 'd') 'c:\\a\\b\\c\\d' > path.join('c:/a/', '/b', 'c', 'd', 'e') 'c:\\a\\b\\c\\d\\e' > path.join('c:/a/', '/b', 'c', 'd', 'e', 'f') 'c:\\a\\b\\c\\d\\e\\f'
-
Node中的其他成员
在每个模块中,除了
require
、exports
等模块相关API之外,还有两个特殊的成员:__dirname
动态获取可以用来获取当前文件模块所属模块的绝对路径__filename
动态获取可以用来获取当前文件的绝对路径__dirname
和__filename
是不受执行node命令所属路径影响的
在文件操作中,使用相对路径是不可靠的,因为在Node中文件操作的路径被设计为执行node命令所处的路径。
为了解决这个问题,需要把相对路径转变为绝对路径。
这里我们可以使用
__dirname
或者__filename
来帮我们解决这个问题在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,所以推荐多使用:
path.join()
来辅助拼接为了尽量避免这个问题,在文件操作中使用的相对路径统一转换为动态的绝对路径
补充:模块中的路径标识和这里的路径没关系,不受影响(相对于文件模块)
六、Express
6.1 起步
原生的http在某些方面表现不足以应对我们的开发需求,所以我们需要使用框架来加快开发效率,框架的目的就是提高效率,让我们的代码高度统一。
在Node中,有很多Web开发框架,这里我们以Express为主。
- 官网:http://expressjs.com
- 安装:
npm install --save express
6.2 修改完代码自动重启
这里使用一个第三方命名工具,nodemon
来帮我们解决频繁修改代码重启服务器问题
nodemon
是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装
npm install --global nodemon
安装完毕之后,使用
node app.js
# 使用nodemon
nodemon app.js
只要是通过nodemon app.js启动的服务,它会监视你的文件变化,当文件发生变化的时候,自动帮你重启服务器
6.3 基本路由
路由器
- 请求方法
- 请求路径
- 请求处理函数
get:
// 当你以GET方法请求/的时候,执行对应的处理函数
app.get('/', function(req, res) {
res.send('Hello World!')
})
post:
// 当你以POST方法请求/的时候,执行对应的处理函数
app.post('/', function(req, res) {
res.send('Got a POST request')
})
6.4 静态服务
app.use(express.static('public'))
app.use(express.static('files'))
app.use('/static', express.static('public'))
app.use('/static', express.static(path.join(__dirname, 'public')))
6.5 在Express中配置使用art-template模板引擎
安装:
npm install --save art-template
npm install --save express-art-template
配置:
app.engine('art', require('express-art-template'))
使用:
app.get('/', function(req, res) {
// express默认会去项目中的views目录中找index.html
res.render('index.html', {
title: 'hello world'
})
})
如果希望修改默认的views
视图渲染目录,可以:
// 注意:第一个参数views千万不要写错
app.set('views', 目录路径)
6.6 在Express中获取表单GET请求体数据
Express内置了一个API,可以通过req.query
来获取
req.query
6.7 在Express获取表单POST请求体数据
在Express中没有内置获取表单POST请求体的API,这里我们需要使用一个第三方包:body-parser
安装:
npm install --save body-parser
配置:
var express = require('express')
// 0.引包
var bodyParser = require('body-parser')
var app = express()
// 配置body-parser
// 只要加入这个配置,则在这个req请求对象上会多出来一个属性:body
// 也就是说你可以通过req.body来获取表单POST请求体数据了
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}))
//parse application/json
app.use(bodyParser.json())
使用:
app.use(function(req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(JSON.stringify(req.body, null, 2))
})
6.8 提取路由模块
请求方法 | 请求路径 | get参数 | post参数 | 备注 |
---|---|---|---|---|
GET | /students | 渲染首页 | ||
GET | /students/new | 渲染添加学生页面 | ||
POST | /students | name/age/gender/hobbies | 处理添加学生请求 | |
GET | /students/edit | id | 渲染编辑 | |
POST | /students/edit | id/name/age/gender/hobbies | 处理编辑请求 | |
GET | /students/delete | id | 处理删除请求 |
//router.js路由模块
//职责:
// 处理路由
// 根据不同的请求方法+请求路径设置具体的请求处理函数
//模块职责要单一,不要乱写
//划分模块的目的是为了增强项目代码的可维护性
//提升开发效率
var express = require('express')
// 1.创建一个路由容器
var router = express.Router()
// 2.把路由挂载到router路由容器中
router.get('/students', function (req, res) {
})
router.get('/students/new', function (req, res) {
})
router.post('/students/new', function (req, res) {
})
router.get('/students/edit', function (req, res) {
})
router.post('/students/edit', function (req, res) {
})
router.get('/students/delete', function (req, res) {
})
// 3.把router导出
module.exports = router
6.9 设计操作数据的API文件模块
// student.js文件模块
// 获取所有学生列表
exports.find = function () {
}
// 添加保存学生
exports.save = function () {
}
// 更新学生
exports.update = function() {
}
// 删除学生
exports.delete = function() {
}
6.10 自己编写的步骤
- 处理模板
- 配置开放静态资源
- 配置模板引擎
- 简单路由:/students渲染静态也处理
- 路由设计
- 提取路由模块
- 由于接下来一系列的业务操作都需要处理文件数据,所以要封装student.js
- 先写好student.js文件结构
- 查询所有学生列表的API find
- findById
- save
- updateById
- deleteById
- 实现具体功能
- 通过路由收到请求
- 接收请求中的数据(get、post)
- req.query
- req.body
- 调用数据操作API处理数据
- 根据操作结果给客户端发送响应
- 业务功能顺序
- 列表
- 添加
- 编辑
- 删除
- find
- findIndex
七、MongoDB
参考https://www.runoob.com/mongodb/mongodb-tutorial.html
7.1 关系型数据库和非关系型数据库
表就是关系,或者说表与表之间存在关系
- 所有的关系型数据库都需要通过
SQL
语言来操作 - 所有的关系型数据库在操作之前都需要设计表结构
- 而且数据表还支持约束
- 唯一的
- 主键
- 默认值
- 非空
- 非关系型数据库非常灵活
- 有的非关系型数据库就是key-value对
- 但是MongoDB是长得最像关系型数据库的非关系型数据库
- 数据库->数据库
- 数据表->集合(数组)
- 表记录->文档对象
- MongoDB不需要设计表结构
- 你可以任意地存放数据
7.2 下载与安装
-
下载
-
安装
-
配置环境变量
-
cmd输入
mongod --version
判断是否安装成功
7.3 启动和关闭数据库
启动:
# mongodb默认使用执行mongod命令所处盘符根目录下的/data/db作为自己的数据存储目录
# 所以在第一次执行该命令前先自己手动新建一个/data/db
mongod
如果想要修改默认的数据存储目录,可以:
mongod --dbpath=数据存储目录路径
停止:
在开启服务的控制台,直接Ctrl+C即可停止
或直接关闭开启服务的控制台也可以
7.4 连接数据库
连接:
# 该命令默认连接本机的MongoDB服务
mongo
退出:
# 在连接状态输入exit退出连接
exit
7.5 基本命令
show dbs
- 查看显示所有数据库
db
- 查看当前操作的数据库
use 数据库名称
- 切换到指定的数据库(如果没有会新建)
- 插入数据
- db.students.insertOne({“name”: “Jack”})
- show collections
- db.students.find()
7.6 在Node中如何操作MongoDB数据库
-
使用官方的
mongodb
包来操作:http://github.com/mongodb/node-mongodb-native -
使用第三方
mongoose
来操作MongoDB数据库mongoose
基于MongoDB官方的mongodb
包再一次做了封装- 网址:http://mongoosejs.com
- 官方指南:http://mongoosejs.com/docs/guide.html
- 官方API文档:http://mongoosejs.com/docs/api.html
-
MongoDB数据库的基本概念
- 可以有多个数据库
- 一个数据库中可以有多个集合
- 文档结构很灵活,没有任何限制
- MongoDB非常灵活,不需要像MySQL一样事先创建数据库、表、设计表结构
- 在这里只需要当你插入数据时指定往哪个数据库的哪个集合插入数据即可,一切都由MongoDB来帮你自动完成建库建表这件事
{ qq: { users: [ {name: '张三', age: 15}, {name: '李四', age: 15}, {name: '王五', age: 15}, {name: '张三123', age: 15}, {name: '张三321', age: 18} ], products: [ ], ... }, taobao: { }, baidu: { } }
-
增加数据
var admin = new User({ username: 'zs', password: '123456', email: 'admin@admin.com' }) admin.save(function (err, ret) { if (err) { console.log('保存失败') } else { console.log('保存成功') console.log(ret) } })
-
查询数据
查询所有:
User.find(function (err, ret) { if (err) { console.log('查询失败') } else { console.log(ret) } })
按条件查询所有:
User.find({ username: 'zs' }, function (err, ret) { if (err) { console.log('查询失败') } else { console.log(ret) } })
按条件查询单个:
User.findOne({ username: 'zs' }, function (err, ret) { if (err) { console.log('查询失败') } else { console.log(ret) } })
-
删除数据
根据条件删除所有:
User.remove({ username: 'zs' }, function (err, ret) { if (err) { console.log('删除失败') } else { console.log('删除成功') console.log(ret) } })
根据条件删除一个:
Model.findOneAndRemove(conditions, [options], [callback])
根据id删除一个:
Model.findByIdAndRemove(id, [options], [callback])
-
更新数据
根据条件更新所有:
Model.update(conditions, doc, [options], [callback])
根据指定条件更新一个:
Model.findOneAndUpdate([conditions], [update], [options], [callback])
根据id更新一个:
User.findByIdAndUpdate('5a001b23d219eb00c8581184', { password: '123' }, function (err, ret) { if (err) { console.log('更新失败') } else { console.log('更新成功') } })