NodeJS 学习笔记

1. Node中的模块系统

使用 Node 编写应用程序主要就是在使用:

  • EcmaScript 语言
    • 和浏览器不一样,在 Node 中没有 BOM、DOM
  • 核心模块
    • 文件操作的 fs
    • http 服务的 http
    • url 路径操作模块
    • path 路径处理模块
    • os 操作系统信息
  • 第三方模块(必须通过 npm 下载才可以使用 )
    • art-template
  • 自己编写的模块
    • 路径

1.1. 什么模块化

  • 文件作用域
  • 通信规则
    • 加载 require
    • 导出

1.2. CommonJS 模块规范

JS 本身不支持模块化。在 Node 中的 JS 有个很重要的概念:模块系统。

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

1.2.1. 加载 require

语法:

var 自定义变量名称 = require('模块')

两个作用:

  • 执行被加载模块中的代码
  • 得到被加载模块中的 exports 导出接口对象

1.2.2. 导出 exports

  • Node 中是模块作用域,默认文件中所有的成员旨在当前文件模块有效
  • 对于希望可以被其他模块访问的成员,需要把这些公开的成员都挂载到 exports 接口对象中。

导出多个成员(必须在对象中):

// module.xxx = xxx
exports.a = 123
exports.b = 'hello'
exports.c = function() {
    console.log('ccc')
}
exports.d = {
    foo: 'bar'
}

导出单个成员(拿到的就是:函数,字符串):

module.exports = 'hello'

// 以这个为准,后者会覆盖前者
module.exports = function(x, y) {
    return x + y
}

也可以这样导出多个成员:

module.exports = {
    add: function() {
        return x + y
    },
    str: 'hello'
}

1.2.3. 原理解析

exportsmodule.exports 的一个引用:

console.log(exports == module.exports)  // => true  

exports.foo = 'bar'

// 等价于
module.exports.foo = 'bar'

301 和 302 的区别:

  • 301:永久重定向,浏览器会记住,用的比较少;
  • 302:临时重定向。

1.2.4. exportsmodule.exports 的区别

  • 每个模块中都有一个 module 对象
  • module 对象中有一个 exports 对象
  • 可以把需要导出的成员都挂载到 module.exports 接口对象中
  • 也就是以 module.exprots.xxx = xxx 的方式
  • 但是每次这种方式很麻烦,点的有点多
  • 所以 Node 同时在每一个模块中提供了一个成员叫 exports
  • exports === module.exports 结果为 true
  • 所以对于 module.exports.xxx = xxx 的方式完全可以 exports.xxx = xxx
  • 当模块需要导出单个成员的时候,这个时候必须使用 module.exports = xxx 的方式
  • 不能使用 exports = xxx, 这不管用
  • 因为每个模块最终向外 return 的是 module.exports
  • exports 只是 module.exports 的一个引用
  • 所以即便为 exports = xxx 重新赋值,也不会影响 module.exports
  • 但是有一种赋值方式比较特殊 exports = module.exports 这个用来重新建立引用关系的。

1.2.5. require 方法加载规则

可以参考:深入浅出 Node.js(三):深入 Node.js 的模块机制

  1. 核心模块
    • 模块名
  2. 第三方模块
    • 模块名
  3. 用户自己编写
    • 路径
  • 优先从缓存加载,加载过的文件会缓存起来,不会重复加载。
  • 判断模块标识
    • 核心模块
    • 第三方模块
      • 凡是第三方模块都必须通过 npm 来下载
      • 使用的时候可以通过 require(‘包名’) 的方式来进行加载才可以
      • 既不是核心模块,也不是路径形式的模块
      • 先找到当前文件所处目录中的 node_modules 目录
        • node_modules/art-template
        • node_modules/art-template/package.json 文件
        • node_modules/art-template/package.json 文件中的 main 属性
        • main 属性中记录了 art-template 的入口模块,真正加载的是 index.js
        • 如果 package.json 文件不存在或者 main 指定的入口模块也没有
        • 则 node 会自动找该目录下的 index.js 文件,也就是说 index.js 会作为一个默认备选项
      • 如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找
      • 如果上一级还没有,则继续往上上一级查找
      • 。。。
      • 如果直到当前磁盘根目录还找不到,最后报错:
      • can not find module xxx
      • 一个项目有且仅有一个 node_modules 而且是放在项目的根目录中
    • 自定义模块

1.3. npm

  • node package manager

1.3.1. npm 网站

1.3.2. npm 命令工具

npm 的第二层含义是一个命令行工具,npm 也有版本的概念。

npm --version # 查看版本信息

升级 npm:

npm install --global npm

1.3.3. 常用命令

  • npm init
    • npm init -y 可以跳过向导,快速生成
  • npm install
    • 一次性把 dependencies 选项中的依赖项全部安装
    • npm i 简写
  • npm install 包名
    • 只下载
    • npm i 包名
  • npm install --save 包名
    • 下载且保存依赖项
    • npm i -S 包名
  • npm uninstall 包名
    • 只删除,如果有依赖项依然会保存
    • npm un 包名
  • npm uninstall --save 包名
    • 删除的同时也会把依赖信息去除
    • npm un -S 包名
  • npm 命令 --help
    • 查看具体命令的帮助信息

1.3.4. 解决 npm 被墙问题

1.4. package.json

包描述文件,就像产品的说明书一样。这个文件可以通过 npm init 方式来自动初始化出来。

  • 最有用的是 dependencies 选项,用来保存第三方包的依赖信息。
  • 安装第三方包时执行 npm install 包名 --save 通过 --save 保存依赖项信息。
  • npm install 自动把 package.json 中的 dependencies 中所有的依赖项都下载下来。

1.5. package-lock.json

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

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

1.6. Node 中的加密模块

  1. 检测是否支持 crypto 内置于node模块中(一般都支持)
let crypto;
try {
  crypto = require('crypto');
} catch (err) {
  console.log('不支持 crypto');
}
  1. 封装一个简单的md5加密方法
// 封装一个简单的 md5 加密方法
// 文件保存为 md5Crypto.js

const crypto = require('crypto')

var md5Crypto = (password) =>  {
    /**
     * 加密可选参数:加密后的位数
     * 1. md5 32位
     * 2. sha256 64位
     * 3. sha512 128位
     */
    const hash = crypto.createHash('md5')
    hash.update(password)
// hex为加密算法前后对比时, 一定要使用一致, 加密结果通常有两种表示方法:hex和base64
    const md5Password = hash.digest('hex')

    return md5Password
}

module.exports = md5Crypto
  1. 使用
// 导入模块
var md5Crypto = require('./md5Crypto')

// 进行加密处理
var md5Password = md5Crypto(password)

参考文章:

  1. NodeJS:MD5加密
  2. Node.js中的MD5加密
  3. node中 crypto实现简单的md5加密

1.7. path 路径操作模块

参考文档:http://nodejs.cn/api/path.html

  • path.basename
    • 获取一个路径的文件名(默认包含扩展名)
  • path.dirname
    • 获取一个路径中的目录部分
  • path.extname
    • 获取一个路径中的扩展名部分
  • path.parse
    • 把一个路径转为对象
      • root 根路径
      • dir 目录
      • base 包含后缀名的文件名
      • ext 后缀名
      • name 不包含后缀名的文件名
  • path.join
    • 当需要进行路径拼接的时候,推荐使用这个方法
  • path.isAbsolute
    • 判断一个路径是否是绝对路径

2. Node 中的其它成员

2.1. __dirname__filename

在每个模块中,除了 requireexports 等模块相关 API 外,还有两个特殊的成员:

  • __dirname 动态获取 可以用来获取当前文件模块所属目录的绝对路径
  • __filename 动态获取 可以用来获取当前文件的绝对路径
  • __dirname__filename 是不受执行 node 命令所属路径影响的

在文件操作中,使用相对路径是不可靠的,因为在 Node 中文件操作的路径被设计为相对执行 node 命令所处的路径(不是 bug,这样设计是有使用场景的)。所以为了解决这个问题,需要把相对路径变为绝对路径就可以了。

这里可以使用 __dirname__filename 来解决问题。在拼接路径的过程中,使用 path.join() 来辅助拼接。如下:

var fs = require('fs')
var path = require('path')

app.use('/node_modules/', express.static(path.join(__dirname, 'node_modules')))

以后在文件操作中使用的相对路径都统一转换为动态的绝对路径

补充:模块中的路径标识和这里的路径没关系,不受影响(相对于文件模块)。

3. Express

原生的 http 在某些方面表现不足以应对我们的开发需求,所有我们就需要使用框架来加快我们的开发效率,让我们的代码更高度统一。

npm install express --save

初步感知,app.js :

// 0. 安装
// npm i -S express
// 1. 引包
var express = require('express')

// 2. 创建服务器应用程序
// 也就是原来的 http.createServer
var app = express()

// 在 Express 中开放资源就是一个 API 的事
// 公开指定目录
// 只要这样做了, 就可以直接通过 /public/xxx 的方式访问 public 目录中的所有资源了
app.use('/public/', express.static('./public/'))
app.use('/static/', express.static('./static/'))
app.use('/node_modules', express.static('./node_modules/'))

// 当服务器收到 get 请求 / 的时候,执行回调处理函数
app.get('/', (req, res)=> res.send('hello express!'))

// 得到路径,一个个的判断
app.get('/about', (req, res)=> res.send('你好,我是 express!'))

app.get('/about', (req, res)=> res.send('你好,我是 express!'))

app.get('/about', (req, res)=> res.send('你好,我是 express!'))

// 相当于 server.listen
app.listen(3000, ()=> console.log('app is running at port 3000.'))

3.1. 起步

3.1.1. 安装

npm install --save express

hello world

var express = require('express')
var app = express()

app.get('/', (req, res) => res.send('hello world!'))
app.listen(3000, () => console.log('express app is listening on port 3000!'))

3.1.2. 基本路由

  • 请求方法
  • 请求路径
  • 请求处理函数

get

// 当以 GET 方法请求 / 的时候,执行对应的处理函数
app.get('/', (req, res) => res.send('hello world!'))

post

// 当以 POST 方法请求 / 的时候,指定对应的处理函数 
app.post('/', (req, res) => res.send('Got a POST request'))

3.1.3. 静态服务

// /public 资源
app.use(express.static('public'))
// /static 资源
app.use(express.static('static'))

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

3.2. 在 express 中配置使用 art-template 模板引擎

安装:

npm install --save art-template express-art-template

配置:

app.engine('html', require('express-art-template'))

使用:

app.get('/admin', (req, res) => {
// express 默认回去项目中的 views 目录找index.html
    res.render('admin/index.html', {
        title: '管理系统'
    })
})

如果希望修改默认的 views 视图渲染存储目录,可以:

// 第一个参数 views 不能写错
app.set('views', render 函数的默认路径)

3.3. 在 Express 中获取表单 GET 请求参数

Express 内置了一个 API,可以直接通过 req.qurey 来获取

app.get('/pinglun', (req, res) => {
    var comment = req.query
    comment.dataTime = '12323234543'
    comments.unshift(comment)
    // 重定向
    res.redirect('/')
})

3.4. 在 Express 中获取表单 POST 请求体数据

在这里插入图片描述
如上图所示,构成处理代码所需要做的主要是:

  1. 在用户第一次请求时显示默认表单。
    • 表单可能包含空白字段(例如,如果您正在创建新记录),或者可能预先填充了初始值(例如,如果您要更改记录,或者具有有用的默认初始值)。
  2. 接收用户提交的数据,通常是在HTTP POST请求中。
  3. 验证并清理数据。
  4. 如果任何数据无效,请重新显示表单 - 这次使用用户填写的任何值,和问题字段的错误消息。
  5. 如果所有数据都有效,请执行所需的操作(例如,将数据保存在数据库中,发送通知电子邮件,返回搜索结果,上传文件等)
  6. 完成所有操作后,将用户重定向到另一个页面。

表格处理代码,通常使用GET路由,以实现表单的初始显示,以及POST路由到同一路径,以处理表单数据的验证和处理。这是将在本教程中使用的方法!Express本身不提供表单处理操作的任何特定支持,但它可以使用中间件,以处理表单中的POST和GET参数,并验证/清理它们的值。

body-paser 中间件在 2019 年已经被弃用,直接使用 express 调用即可,不用再安装这个中间件。使用如下:

app.use(express.urlencoded({extended: false}))
app.use(express.json())
// 当以 POST 请求 /post 的时候,执行指定的处理函数
// 这样可以利用不同的请求方法让一个请求路径使用多次
app.post('/post', (req, res) => {
    // console.log('收到表单 post 请求了')
    // 1. 获取表单 POST 请求体数据
    // 2. 处理
    // 3. 发送响应
    var comment = req.body
    comment.dataTime = '12323234543'
    comments.unshift(comment)
    res.redirect('/')
})

下述内容已被弃用,仅作了解。


在 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 请求体数据了
app.use(bodyParser.urlencoded({ extended: false }))

app.use(bodyParser.json())

使用:

app.use(function (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.write('you posted:\n')
  // 可以通过 req.body 来获取表单 POST 请求体数据
  res.end(JSON.stringify(req.body, null, 2))
})

3.5. CRUD 实例

3.5.1. 模块化思想

模块如何划分:

  • 模块职责要单一
  • Vue
  • angular
  • React
  • 也非常有利于学习前端三大框架

3.5.2. 起步

  • 初始化
  • 安装依赖
  • 模板处理

3.5.3. 路由设计

请求方法请求路径get 参数post 参数备注
GET/studens渲染首页
GET/students/new渲染添加学生页面
POST/students/newname、age、gender、hobbies处理添加学生请求
GET/students/editid渲染编辑页面
POST/studens/editid、name、age、gender、hobbies处理编辑请求
GET/students/deleteid处理删除请求

3.5.4. 提取路由模块

router.js

/**
 * router.js 路由模块
 * 职责:
 *  处理路由
 *  根据不同的请求方法+请求路径设置具体的请求处理函数
 */

var fs = require('fs')

var Student = require('./sutdent')
// express 提供了一种更好的方式,专门用来包装路由

var express = require('express')

// 1. 创建一个路由容器
var router = express.Router()


// 2. 把路由都挂载到 router 容器中
router.get('/', (req, res) => {
    // res.send('hello world!')
    // readFile 的第二个参数是可选的,传入 utf-8 就是告诉它把读取到的文件
    // 直接按照 utf-8 编码转成我们能认识的字符
    // 除了这样来转换之外,也可以通过 data.tostring() 的方式
    fs.readFile('./db.json', 'utf-8', (err, data) => {
        if (err) return res.status(500).send('Server error.')
        // console.log(data)
        res.render('index.html', {
            fruits: [
                '苹果',
                '香蕉',
                '橘子'
            ],
            // 从文件中读取到的数据一定是字符串
            // 所以这里一定要手动转成对象才可以使用
            // 转换成字符串
            students: JSON.parse(data).students
        }) 
    })
})


router.get('/students', (req, res) => {

    Student.find((err, students) => {
        if (err) return res.status(500).send('Server error.')
        res.render('index.html', {
            fruits: [
                '苹果',
                '香蕉',
                '橘子'
            ],
            // 从文件中读取到的数据一定是字符串
            // 所以这里一定要手动转成对象才可以使用
            // 转换成字符串
            students: students
        })
    })
})

router.get('/students/new', (req, res) => {
    // res.send('new new new')
    // res.render('/', )
    res.render('new.html')
})

// 添加保存学生
router.post('/students/new', (req, res) => {
    // var data = req.body
    // console.log(data)
    // 1. 获取表单数据
    // 2. 处理:将数据保存到 sb.json 文件中用以持久化
    // 3. 发送响应
    // 先读取出来,转成对象
    // 然后王对象中 push 数据
    // 然后把对象转为 字符串
    // 然后把字符串再次写入文件
    Student.save(req.body, (err) => {
        if(err) return res.status(500).send('Server error.')
        res.redirect('/students')
    })
})

// 渲染编辑学生页面
router.get('/students/edit', (req, res) => {
    // 1. 在客户端的列表页中处理链接问题(需要有 id 参数)
    // 2. 获取要编辑的学生 id
    // 3. 渲染编辑页面
    // 根据 id 把学生信息查出来
    // 使用模板引擎渲染页面
    Student.findById(parseInt(req.query.id), (err, student) => {
        if(err) return res.status(500).send('Server error.')
        // console.log(Student)
        res.render('edit.html', {
            student: student
        })
    })
})

// 处理编辑学生
router.post('/students/edit', (req, res) => {
    // 1. 获取表单数据 req.body
    // 2. 更新 Students.update()
    // 3. 发送响应
    Student.updateById(req.body, (err) => {
        if(err) return res.status(500).send('Server error.')
        res.redirect('/students')
    })
    // console.log(req.body)
}) 


// 处理删除学生
router.get('/students/delete', (req, res) => {
    // 1. 获取要删除的 id
    // 2. 根据 id 执行删除操作
    // 3. 根据操作结果发送响应数据
    // console.log(req.query.id)
    Student.deleteById(req.query.id, (err) => {
        if(err) return res.status(500).send('Server error.')
        res.redirect('/students')
    })
})

// 3. 把 router 导出
module.exports = router

app.js

var router = require('./router')

// 挂载路由
app.use(router)

3.5.5. 设计操作数据的 API 文件模块

/**
 * student.js
 * 数据操作文件模块
 * 职责:操作文件中的数据,只处理数据,不关心业务
 * 封装异步 API
 */

var fs = require('fs')

var dbPath = './db.json'

/**
 * 获取所有学生列表
 * callback 中的参数
 *  第一个参数是 err: 成功是 null, 错误是 错误对象
 *  第二个参数是结果:成功是 数组,错误是 undefined
 * return []
 */
exports.find = (callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) return callback(err)
        callback(null, JSON.parse(data).students)
    })
}

/**
 * 
 * @param {Number} id  学生 id
 * @param {Function} callback 回调函数
 */
exports.findById = (id, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) return callback(err)
        var students = JSON.parse(data).students
        var result = students.find((item) => { return item.id === parseInt(id) })
        callback(null, result)
    })
}

/**
 * 添加保存学生
 */
exports.save = (student, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) return callback(err)
        var students = JSON.parse(data).students

        // 处理 id, 唯一不重复
        student.id = students[students.length - 1].id + 1

        // 把用户传递的对象保存到数组中
        students.push(student)

        // 把对象转为字符串
        var fileData = JSON.stringify({ students: students })

        // 把字符串保存到文件中
        fs.writeFile(dbPath, fileData, (err) => {
            // 错误就是把错误对象传递给它
            if (err) return callback(err)
            // 成功就没错,所以错误对象是 null
            callback(null)
        })
    })
}

/**
 * 更新学生
 */
exports.updateById = (student, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) return callback(err)
        var students = JSON.parse(data).students

        // 把 id 统一转换为数字类型
        student.id = parseInt(student.id)
        // 要修改谁,就把谁找出来
        // ES 6 中的一个数组方法:find
        // 需要接收一个函数作为参数
        // 当某个遍历项符合 item.id == student.id 条件的时候,find 会终止遍历,同时返回遍历项
        var stu = students.find((item) => { return item.id === student.id })

        // 遍历拷贝对象
        for (var key in student) stu[key] = student[key]

        // 把对象转为字符串
        var fileData = JSON.stringify({ students: students })

        // 把字符串保存到文件中
        fs.writeFile(dbPath, fileData, (err) => {
            // 错误就是把错误对象传递给它
            if (err) return callback(err)
            // 成功就没错,所以错误对象是 null
            callback(null)
        })
    })
}

/**
 * 删除学生
 */
exports.deleteById = (id, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) return callback(err)
        var students = JSON.parse(data).students

        // findIndex 方法专门用来根据条件查找元素的下标
        var deleteId = students.findIndex((item) => { return item.id === parseInt(id) })

        // 根据下标从数组中删除对应的学生对象
        students.splice(deleteId, 1)

        // 把对象转为字符串
        var fileData = JSON.stringify({ students: students })

        // 把字符串保存到文件中
        fs.writeFile(dbPath, fileData, (err) => {
            // 错误就是把错误对象传递给它
            if (err) return callback(err)
            // 成功就没错,所以错误对象是 null
            callback(null)
        })
    })
}

3.5.6. 自己编写的步骤

  • 处理模块
  • 配置开放静态资源
  • 简单路由: /students 渲染静态页出来
  • 路由设计
  • 提取路由模块
  • 将数据处理抽象封装成一个文件 student.js
  • 先写好 student.js 文件结构
    • 查询所有学生列表的 API find
    • findById
    • save
    • updateById
    • deleteById
  • 实现具体功能
    • 通过路由收到请求
    • 接收请求中的数据(get、post)
      • req.query
      • req.body
    • 调用数据操作 API 处理数据
    • 根据操作结果给客户端发送响应
  • 业务功能顺序
    • 列表
    • 添加
    • 编辑
    • 删除
  • find
  • findIndex

3.5.7. 使用 Node 操作 MySQL 数据库

4. 异步编程

4.1. 回调函数

不成立的情况

function add(x, y, callback) {
  console.log(1)
  setTimeout(function () {
    var ret = x + y
    return ret
  }, 1000)
  console.log(ret)
  // 到这里就执行结束了,不会等到前面的定时器,所以直接就返回了默认值 undefined
}
console.log(add(10, 20)) // => undefined

回调函数

function add(x, y, callback) {
  console.log(1)
  setTimeout(function () {
    var ret = x + y
    callback(ret)
  }, 1000)
}

add(10, 20, function (ret) {
  console.log(ret)
})

注意:凡是需要得到一个函数内部异步操作的结果

  • setTimeout
  • readFile
  • writeFile
  • ajax

这些情况必须通过:回调函数。
在这里插入图片描述
一般情况下,把函数作为参数的目的是为了获取函数内部的异步操作结果。

JavaScript 单线程、事件循环。

基于原生 XHMHttpRequest 封装 get 方法:


var get = (url, callback) => {
    var oReq = new XMLHttpRequest()
    // 当请求加载成功之后要调用指定的函数
    oReq.onload = () => {
        // 现在需要得到这里的 oReq.responseText
        callback(oReq.responseText)
    }
    oReq.open('get', url, true)
    oReq.send()
}

get('data.json', (data) => {
    console.log(data)
})

4.2. Promise

callback hell

在这里插入图片描述

无法保证顺序的:

var fs = require('fs')

fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if(err) {
        // return console.log('读取失败)
        // 抛出异常
        //  1. 阻止程序的执行
        //  2. 把错误消息打印到控制台
        throw err
    }
    console.log(data)
})

fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
    if(err) {
        // return console.log('读取失败)
        // 抛出异常
        //  1. 阻止程序的执行
        //  2. 把错误消息打印到控制台
        throw err
    }
    console.log(data)
})

fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
    if(err) {
        // return console.log('读取失败)
        // 抛出异常
        //  1. 阻止程序的执行
        //  2. 把错误消息打印到控制台
        throw err
    }
    console.log(data)
})

通过回调嵌套的方式保证顺序:

var fs = require('fs')

fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if(err) {
        // return console.log('读取失败)
        // 抛出异常
        //  1. 阻止程序的执行
        //  2. 把错误消息打印到控制台
        throw err
    }
    console.log(data)
    fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
        if(err) {
            // return console.log('读取失败)
            // 抛出异常
            //  1. 阻止程序的执行
            //  2. 把错误消息打印到控制台
            throw err
        }
        console.log(data)
        fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
            if(err) {
                // return console.log('读取失败)
                // 抛出异常
                //  1. 阻止程序的执行
                //  2. 把错误消息打印到控制台
                throw err
            }
            console.log(data)
        })
    })
})

为了解决上述编码方式带来的问题(回调地狱嵌套),所以在 ES6 中新增了一个API: Promise

在这里插入图片描述

Promise 基本语法:

// 在 ES6 中新增了一个 API
// Promise 是一个构造函数
var fs = require('fs')

// 创建 Promise 容器
// 1. 给别人一个承诺
// Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise((resolve, reject) => {
    // console.log(2)
    fs.readFile('./data/aa.txt', 'utf-8', (err, data) => {
        if (err) {
            // 失败了,承诺容器中的任务失败了
            // 把容器的 Pending 状态变为 Rejected
            // 抵用 reject 就相当于调用了 then 方法的第二个参数函数
            reject(err)
        } else {
            // 承诺容器中的任务成功了
            // 把容器的 Pending 状态改为成功 Resolved
            // 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
            resolve(data)
        }
    })
})

// p1 就是那个承诺
// 当 p1 成功了,然后(then) 做指定的操作
// then 方法接接收的 function 就是容器中的 resolve 函数
p1
    .then((data) => {
        console.log(data)
    }, (err) => console.log('读取文件失败了', err))

在这里插入图片描述
在这里插入图片描述

完整代码展示:

// 在 ES6 中新增了一个 API
// Promise 是一个构造函数
var fs = require('fs')

// 创建 Promise 容器
// 1. 给别人一个承诺
// Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise((resolve, reject) => {
    fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
        if (err) {
            // 失败了,承诺容器中的任务失败了
            // 把容器的 Pending 状态变为 Rejected
            // 调用 reject 就相当于调用了 then 方法的第二个参数函数
            reject(err)
        } else {
            // console.log(3)
            // 承诺容器中的任务成功了
            // console.log(data)
            // 把容器的 Pending 状态改为成功 Resolved
            // 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
            resolve(data)
        }
    })
})

var p2 = new Promise((resolve, reject) => {
    fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
        if (err) {
            reject(err)
        } else {
            resolve(data)
        }
    })
})

var p3 = new Promise((resolve, reject) => {
    fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
        if (err) {
            reject(err)
        } else {
            resolve(data)
        }
    })
})

// console.log(4)
// p1 就是那个承诺
// 当 p1 成功了,然后(then) 做指定的操作
// then 方法接接收的 function 就是容器中的 resolve 函数
p1
    .then((data) => {
        console.log(data)
        // 当 p1 读取成功的时候
        // 当前函数中 return 的结果就可以在后面的 then 中 function 接收到
        // 当 return 123 后就收到 123 return 'hello' 后面就接收到 'hello'
        // 没有return后面收到的就是 undefined
        // 真正有用的是可以 return 一个 Promise 对象
        // 当 return 一个 Promise 对象的时候,后续的 then 中的方法的第一个参数会作为 p2 的 Resolve
        return p2
    }, (err) => console.log('读取文件失败了', err))
    .then((data) => {
        console.log(data)
        return p3
    })
    .then((data) => {
        console.log(data)
        console.log('end')
    })

封装 Promise 版本的 readFile

var fs = require('fs')

var pReadFile = (filePath) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

pReadFile('./data/a.txt')
    .then((data) => {
        console.log(data)
        return pReadFile('./data/b.txt')
    })
    .then((data) => {
        console.log(data)
        return pReadFile('./data/c.txt')
    })
    .then((data) => {
         console.log(data)
    })

5. Node 综合 Web 案例

5.1. 文件目录结构

在这里插入图片描述
在这里插入图片描述

models 文件夹下存放着数据库中每个表对应的数据模型,可以理解为数据库中每个表的字段。如 ManagerMode.js 文件如下:

module.exports =  (db, callback) => {
	// 用户模型
	db.define("ManagerModel", {
		mg_id: { type: 'serial', key: true },
		mg_name: String,
		mg_pwd: String,
		mg_time: Number,
		role_id: Number,
		mg_mobile: String,
		mg_email: String,
		mg_state: Number
	}, {
		table: "sp_manager"
	});
	return callback();
}

数据库中的表如下:

在这里插入图片描述

5.2. 模板页

5.3. 路由设计

5.4. 表单提交

表单具有默认的提交行为,默认是同步的。同步表单提交,浏览器会锁死(转圈儿)等待服务端的响应结果。表单的同步提交之后,无论服务端响应的是什么,都会直接把响应的结果覆盖掉当前页面。

服务端重定向针只针对同步请求有效,对异步请求无效。

注册成功后跳转后首页:

// 跳转到首页
window.location.href = '/'

5.5. 通过 Session 保存登录状态

在 Express 框架中,默认不支持 Session 和 Cookie,可以使用第三方中间件:express-session 来解决。

  1. 安装
npm install express-session
  1. 配置(一定要在 app.use(router) 之前)
var  session = require('express-session')


app.use(session({
    // 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
    // 目的是为了增加安全性,防止客户端恶意伪造
    secret: 'ustc_infonet',
    resave: false,
    saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
}))
  1. 使用。当把这个这个插件配置好之后,就可以通过 req.session 来访问和设置 Session 成员
// 添加 Session 数据
req.session.foo = 'bar'
// 访问 Session 数据
req.session.foo

注册成功,使用 Session 记录用户的登录状态:

req.session.user = user

6. 其他

6.1. 代码无分号问题

6.2. 修改完代码自动重启

使用 nodemon 工具帮助解决频需要更改代码重启服务器问题。 nodemon 是一个基于 Node.js 开发的第三方命令行工具,安装如下:

npm install -g nodemon

安装完毕之后使用:

node app.js

# 使用nodemon
nodemon app.js

只要是通过 nodemon app.js 启动的服务,当文件发生变化的时候,自动重启服务器。

6.3. 文件操作路径和模块路径

文件操作路径

var fs = require('fs')
// 所有的文件操作的 API 都是异步的
// 就像 ajax 请求一样
// 文件操作中的相对路径可以省略 ./
fs.readFile('data/a.txt', (err, data) => {
    if (err) return console.log('读取失败')
    console.log(data.toString())
})

// 在文件操作的相对路径中
// ./data/a.txt 相对于当前目录
// data/a.txt 相对于当前目录
//  /data/a.txt 绝对路径,当前文件模块所处磁盘根目录
// c:/xx/xx... 绝对路径
fs.readFile('./data/a.txt', (err, data) => {
    if (err) return console.log('读取失败')
    console.log(data.toString())
})

模块操作路径

// 在模块加载中,相对路径中的 ./ 不能省略
// 如果忽略了 . 也是磁盘根目录
require('./data/foo.js')('hello')
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值