zoukankan      html  css  js  c++  java
  • Node.js笔记

    一、Node 开发概述

    1.1 为什么学习服务端开发基础

    • 能够与后端程序员更加紧密的配合
    • 网站业务逻辑前置,学习前端技术需要后端技术支撑(Ajax)
    • 拓宽知识视野,能够站在更高的角度审视整个项目

    1.2 服务端开发要做什么

    • 实现网站的业务逻辑
    • 数据的增删改查

    1.3 为什么选择 Node

    • 使用 JavaScript 语法开发后端应用
    • 一些公司要求前端工程师掌握 Node 开发
    • 生态系统活跃,有大量开源库
    • 前端开发工具大多基于 Node 开发

    1.4 Node 是什么

    Node 是一个基于 Chrome V8 引擎的 JavaScript 代码运行环境

    运行环境

    • 浏览器(软件)能够运行 JavaScript 代码,那么浏览器就是 JavaScript 代码的运行环境
    • Node(软件)能够运行 JavaScript 代码,那么 Node 就是 JavaScript 代码的运行环境

    官网:https://nodejs.org/zh-cn/

    LTS = Long Term Support 长期支持版、稳定版
    Current 拥有最新特性,实验版

    二、Node.js 快速入门

    2.1 Node.js 的组成

    • JavaScript 由三部分组成
      • ECMAScript:核心,规定了语言的语法部分
      • DOM:浏览器为了能够控制它提供的 API
      • BOM:浏览器为了能够控制它提供的 API
    • Node.js
      • ECMAScript
      • Node 模块 API

    所有 ECMAScript 语法在 Node 环境中都可以使用。

    三、Node.js 模块化开发

    3.1 JavaScript 开发弊端

    JavaScript 在使用时存在两大问题,文件依赖命名冲突

    文件依赖:当在引入轮播图组件 slider.js 后,并没有生效,经过分析,发现它依赖 scroll.js,将 scroll.js 引入后,还没有生效,最后经过代码分析后,发现需要引入 jQuery.js,此时才成功。这一系列的分析过程,耗时且麻烦。

    命名冲突:a.js 文件中定义了 num 变量,b.js 依赖 a.js,那么 b.js 文件也可以使用 num 变量,这样容易导致命名冲突。

    3.2 模块化开发

    一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。

    优点:在某个模块出现问题时,可以将它替换,不影响其他模块的功能使用。

    3.3 Node.js 中模块化开发规范

    • Node.js 规定一个 JavaScript 文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
    • 模块内部可以用使用exports 对象进行成员导出,使用require方法导入其他模块。

    a.js

    // 在模块内部定义变量
    let version = 1.0;
    // 在模块内部定义方法
    const add = (num1, num2) => num1 + num2;
        
    // 向模块外部导出数据
    exports.version = version;
    exports.add = add;
    

    b.js

    // 在 b.js 模块中导入模块 a
    let a = require('./a.js');
    // 输出 a 模块中的 version 变量
    console.log(a.version);
    console.log(a.add(1, 2));
    

    导入模块时后缀可以省略。

    let a = require('./a');
    

    3.4 模块成员导出的两种方式

    • exports
    • module.exports

    exports 是 module.exports 的别名(地址引用关系),导出对象最终以 module.exports 为准。

    a.js

    var n = 100;
    var m = 200;
    exports.n = n;
    module.exports.m = m;
    

    b.js

    let a = require('./a.js');
    console.log(a);
    

    上图可以看出,exports 和 module.exports 默认指向了同一个对象。

    a.js

    var n = 100;
    var m = 200;
    exports.n = n;
    module.exports = {
        m: 300
    };
    

    可以发现,如果重新赋值 module.exports 对象,exports 的值没有了,说明了,导出对象最终以 module.exports 为准。

    3.5 系统模块

    什么是系统模块

    Node 运行环境提供的 API。因为这些 API 都是以模块化的方式进行开发的,所以又称 Node 运行环境提供的 API 为系统模块。

    1. 文件操作系统 fs 操作

      const fs = require('fs');
      

      读取文件内容:

      fs.readFile('文件路径/文件名称'[, '文件编码'], callback);

      读取文件语法示例:

      // 引用系统模块
      const fs = require('fs');
      // 读取上一级 css 目录下的 base.css
      fs.readFile('../css/base.css', 'utf-8', (err, doc) => {
          // 如果文件读取发生错误,参数 err 的值为错误对象,否则 err 的值为 null
          // doc 参数为文件内容
          if (err == null) {
              // 在控制台删除文件内容
              console.log(doc);
          }
      });
      

      写入文件内容:

      fs.writeFile('文件路径/文件名称', '数据', callback);

      写入文件内容示例:

      const content = '<h3>正在使用 fs.writeFile 写入文件内容</h3>';
      fs.writeFile('../index.html', content, err => {
          if (err != null) {
              console.log(err);
              return;
          }
          console.log('文件写入成功');
      });
      
    2. 系统模块 path 路径操作

      • 为什么要进行路径拼接

      在 Windows 上 ''、'/' 都可以作为路径分隔符,在 Linux 上只有 '/' 可以。

      路径拼接语法:

      path.join('路径', '路径', ...);

      // 导入 path 模块
      const path = require('path');
      // 路径拼接
      let finalPath = path.join('docs', 'a', 'b', 'c.css');
      // 输出结果:docs/a/b/c.css
      console.log(finalPath);
      

      相对路径 vs 绝对路径:

      • 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
      • 在读取文件或设置文件路径时,都会选择绝对路径
      • 使用 __dirname 获得文件所在的绝对路径
      fs.readFile(path.join(__dirname, 'a.js'), 'utf-8', (err, doc) => {
          
      });
      

    3.6 第三方模块

    别人写好的、具有特定功能的、我们能直接使用的模块即为第三方模块,由于第三方模块通常都是由多个文件组成,并且被放置在一个文件夹中,所以又名为“包”。

    第三方模块有两种存在形式:

    • 以 js 文件的形式存在,提供实现项目具体功能的 API 接口
    • 以命令行工具形式存在,辅助项目开发
    1. npmjs.com

      • 第三方模块的存储和分发仓库
    2. npm

      • node 的第三方模块管理工具

      下载:npm install 模块名称
      卸载:npm uninstall package 模块名称

      全局安装与本地安装:

      • 命令行工具:全局安装
      • 库文件:本地安装
    3. nodemon

      • nodemon 是一个命令行工具,用于辅助项目开发

      在 Node.js 中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐。

      使用步骤:

      1. 使用 npm install nodemon -g下载。
      2. 在命令行工具中使用 nodemon 命令替代 node 命令执行文件

    4. nrm

      • nrm(npm registry manager):npm 下载地址切换工具

      npm 默认的下载地址在国外,国内下载速度慢。

      使用步骤:

      1. 使用 npm install nrm -g 下载
      2. 查询可用下载地址列表:nrm ls
      3. 切换 npm 下载地址:nrm use 下载地址名称

    四、 第三方模块 Gulp

    基于 node 平台开发的前端构建工具。

    将机械化操作编写成任务,想要执行机械化操作时,执行一个命令行命令,任务就能自动执行了。

    用机器代替手工。

    4.1 Gulp 能做什么

    • 项目上线,HTML、CSS、JS 文件压缩合并
    • 语法转换(es6、less...)
    • 公共文件抽离
    • 修改文件浏览器自动刷新

    4.2 Gulp 使用

    1. 使用 npm install gulp 下载 gulp 库文件。
    2. 在项目根目录下建立 gulpfile.js 文件
    3. 重构项目的文件夹结构,src 目录放置源代码文件,dist 目录放置构建后文件
    4. gulpfile.js 文件中编写任务
    5. 在命令行工具中执行 gulp 任务

    4.3 Gulp 中提供的方法

    • gulp.src():获得任务要处理的文件
    • gulp.dest():输出文件
    • gulp.task():建立 gulp 任务
    • gulp.watch():监控文件的变化
    // 引用 gulp 模块
    const gulp = require('gulp');
    // 使用 gulp.task() 方法建立任务
    // 'fist' 是任务名称
    gulp.task('first', async() => {
        // 获取要处理的文件
        await gulp.src('src/css/base.css')
        // 将处理后的文件输出到 dist 目录
        .pipe(gulp.dest('dist/css'));
    });
    

    下载 gulp 命令行工具:npm install gulp-cli -g

    gulp first
    

    gulp 命令自动在当前文件找到 gulpfile.js 文件。

    注意使用 async()、await,否则报错

    4.4 Gulp 插件

    使用过程:

    1. 通过 npm 命令下载插件

    2. 建立任务;

    3. 命令行执行任务。

    gulpfile.js

    // 引用 gulp 模块
    const gulp = require('gulp');
    const htmlmin = require('gulp-htmlmin');
    const fileinclude = require('gulp-file-include');
    const less = require('gulp-less');
    const csso = require('gulp-csso');
    const babel = require('gulp-babel');
    const uglify = require('gulp-uglify');
    
    // html 任务
    // 1. html文件中代码的压缩操作
    // 2. 抽取html文件中的公共代码
    gulp.task('htmlmin', () => {
    	gulp.src('src/*.html')
    		.pipe(fileinclude())
    		// 压缩html文件中的代码
    		.pipe(htmlmin({ collapseWhitespace: true }))
    		.pipe(gulp.dest('dist'));
    });
    
    // css 任务
    // 1. less 语法转换
    // 2. css 代码压缩
    gulp.task('cssmin', async() => {
    	// 选择 css 目录下的所有 less 文件以及 css 文件
    	await gulp.src(['src/css/*.less', 'src/css/*.css'])
    		// 将less语法转换为css语法
    		.pipe(less())
    		// 将css代码进行压缩
    		.pipe(csso())
    		// 将处理的结果进行输出
    		.pipe(gulp.dest('dist/css'))
    });
    
    // js 任务
    // 1. ES6代码转换
    // 2. 代码压缩
    gulp.task('jsmin', async() => {
    	await gulp.src('src/js/*.js')
    		.pipe(babel({
    			// 它可以判断当前代码的运行环境,将代码转换为当前运行环境所支持的代码
                presets: ['@babel/env']
            }))
            .pipe(uglify())
            .pipe(gulp.dest('dist/js'))
    });
    
    // 复制文件夹
    gulp.task('copy', async() => {
    	await gulp.src('src/images/*')
    		.pipe(gulp.dest('dist/images'));
    
    	await gulp.src('src/lib/*')
    		.pipe(gulp.dest('dist/lib'))
    });
    
    // 构建任务
    gulp.task('default', ['htmlmin', 'cssmin', 'jsmin', 'copy']);
    

    4.5 node_modules 文件夹的问题

    1. 文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,传输速度会很慢。
    2. 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错。

    4.6 package.json文件的作用

    项目描述文件,记录了当前项目信息,例如:项目名称、版本、作者、github 地址、当前项目依赖了哪些第三方模块等。

    使用 npm init -y 命令生成。

    • scripts 是命令的别名,将很长的命令写成简化。

      {
          "scripts" : { 
              "build" : "nodemon a.js"
          }
      }
      

      此时,在命令行执行 build 就相当于执行了 nodemon a.js

    • dependencies 记录的是依赖了哪些第三方模块。

    npm install
    

    执行命令后,会在目录下寻找 package.json 文件,然后重新生成 node_modules 目录。这样,在将代码拷贝给别人时,就不需要将 node_modules 文件夹一并拷贝了。

    4.7 项目依赖和开发依赖

    • 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖。
    • 使用 npm install 包名 命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中。
    {
        "dependencies" : {
            "jquery" : "^3.3.1"
        }
    }
    
    • 在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖
    • 使用 npm install 包名 --save-dev 命令将包添加到 package.json 文件的 devDependencies 字段中
    {
        "devDependencies" : {
            "gulp" : "^3.9.1"
        }
    }
    
    • npm install
      • 下载所有的依赖
    • npm install --production
      • 只下载项目依赖

    4.8 package-lock.json 文件的作用

    • 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
    • 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作。

    五、Node.js 中模块加载机制

    5.1 模块查找规则 - 当模块拥有路径但没有后缀时

    require('./find');
    
    1. require 方法根据模块路径查找模块,如果是完整路径,直接引入模块;
    2. 如果模块后缀省略,先找同名 JS 文件,再找同名 JS 文件夹;
    3. 如果找到了同名文件夹,再找文件夹中的 index.js;
    4. 如果文件夹中没有 index.js,就会去该同名文件夹的 package.json 文件中查找 main 选项中的入口文件;
    5. 如果指定的入口文件不存在或者没有指定入口文件,就会报错,模块没有被找到

    5.2 模块查找规则 - 当模块没有路径且没有后缀时

    require('find');
    
    1. Node.js 会假设它是系统模块;
    2. Node.js 会去 node_modules 文件夹中;
    3. 首先查看是否有该名称的 JS 文件;
    4. 再看是否有该名称的文件夹;
    5. 如果有同名文件夹,再看里面是否有 index.js
    6. 如果没有 index.js,查看该同名文件夹中的 package.json 中的 main 选项确定模块入口文件。
    7. 如果指定的入口文件不存在或者没有指定入口文件,就会报错,模块没有被找到

    六、服务器

    6.1 创建 web 服务器

    // 引用创建网站服务器的系统模块
    const http = require('http');
    // 创建web服务器。app 对象就是网站服务器对象
    const app = http.createServer();
        
    // 当客户端有请求到来时
    app.on('request', (req, rsp) => {
        // 响应
        rsp.end('<h1>hi, user</h1>');
    })
    // 监听3000端口
    app.listen(3000);
        
    console.log('服务器已启动,监听3000端口,请访问localhost:3000');
    

    6.2 HTTP协议

    超文本传输协议(HyperText Transfer Protocol,简写 HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)与服务器端(网站)请求和应答的标准。

    6.3 报文

    在 HTTP 请求和响应的过程中传递的数据就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。

    1. 请求报文
      • 请求方式:GET、POST
        • req.method
      • 请求地址
        • req.url
      • 请求报文
        • req.headers
        • req.headers['accept']
    2. 响应报文
      • HTTP 状态码
        • rsp.writeHead(404)
      • 内容类型
        • text/html、txt/css、application/javascript、image/jpeg、application/json
        • rsp.writeHead(200, { 'content-type' : 'text/plain' });
        • rsp.writeHead(200, { 'content-type' : 'text/html' });
      • 编码类型
        • rsp.writeHead(200, { 'content-type' : 'text/html;charset=utf8' });

    6.4 HTTP请求和相应处理

    1. GET 请求参数

      const url = require('url');
      
      app.on('request', (req, rsp) => {
           console.log(req.url);
          // 参数 1:要解析的 url 地址
          // 参数 2:是否将查询参数解析成对象形式
          console.log(url.parse(req.url, true));
          
          var { query: params, pathname } = url.parse(req.url, true);
          console.log(params.name);
          console.log(params.age);
      });
      
    2. POST 请求参数

      • 参数被放置在请求体中进行传输
      • 获取 POST 参数需要使用 data 事件和 end 事件
      • 使用 querystring 系统模块将参数转换为对象格式
      const querystring = require('querystring');
          
      app.on('request', (req, rsp) => {
          // POST 参数是通过事件的方式接收的
          // data:当请求参数传递的时候触发 data 事件
          // end:当参数传递完成的时候触发 end 事件
          let postParams = '';
          
          req.on('data', params => {
              postParams += params;
          });
          req.on('end', () => {
              console.log(querystring.parse(postParams););  
          });
          rsp.end('OK!');  
      });
      
    3. 路由

      路由是指客户端请求地址与服务端程序代码的对应关系。

      app.on('request', (req, rsp) => {
          // 获取客户端的请求路径
          let { pathName } = url.parse(req.url);
          if (pathName == '/' || pathName == '/index') {
              rsp.end('欢迎来到首页~');
          } else if (pathName == '/list') {
              rsp.end('欢迎来到列表页~');
          } else {
              rsp.end('抱歉,无法找到页面!');
          }
      })
      
    4. 静态资源

      服务器不需要处理,可以直接响应给客户端的资源,例如:CSS、JavaScript、image 文件。

    5. 动态资源

      相同的请求地址不同的响应资源。

      const http = require('http');
      const url = require('url');
      const path = require('path');
      const fs = require('fs');
      const mime = require('mime');
      
      const app = http.createServer();
      
      app.on('request', (req, rsp) => {
          // 获取用户的请求路径
          let pathname = url.parse(req.url).pathname;
          pathname = pathname == '/' ? '/index.html' : pathname;
          // 将用户的请求路径转换成实际的服务器硬盘路径
          let realPath = path.join(__dirname, 'public' + pathname);
          // 获取文件的类型
          let type = mime.getType(realPath);
          // 读取文件
          fs.readFile(realPath, (error, result) => {
              if (error == null) {
                  rsp.writeHead(200, {
                      'content-type' : type
                  })
                  rsp.end(result);
              } else {
                  rsp.writeHead(404, {
                      'content-type' : 'text/html;charset=utf8'
                  })
                  rsp.end('文件读取失败');
              }
          })
      });
      app.listen(3000);
      console.log('服务器启动成功');
      

    七、Node.js 异步编程

    7.1 同步 API、异步 API

    • 同步 API:只有当前 API 执行完成后,才能继续执行下一个 API
    • 异步 API:当前 API 的执行不会阻塞后续代码的执行
    console.log(1);
    setTimeout(function () {
        console.log(2);
    }, 2000);
    console.log(3);
    

    7.2 同步、异步的区别

    • 同步 API 可以从返回值中拿到 API 执行的结果,但异步 API 不可以
    // 同步
    function sum(num1, num2) {
        return num1 + num2;
    }
    console.log(sum(1, 2));
    
    // 异步
    function getMsg() {
        setTimeout(function () {
            return {
                msg: 'hi'
            }
        }, 2000);
    }
    const msg = getMsg();
    console.log(msg);
    

    异步 API 以回调函数的方式返回数据。

    • 同步 API 从上到下依次执行,异步 API 不会等待 API 执行完成后再向下执行代码。
    console.log('代码开始执行');
    setTimeout(() => {
        c
    }, 2000);
    setTimeout(() => {
        console.log('0 秒后执行的代码');
    }, 0);
    console.log('代码结束执行');
    


    如果异步 API 后面代码的执行依赖当前异步 API 的执行结果,怎么处理?

    回调地狱

    const fs = require('fs');
    
    // 读取 1.txt
    fs.readFile('1.txt', 'utf8', (error, result1) => {
        console.log(result1);
        // 读取 2.txt
        fs.readFile('2.txt', 'utf8', (error, result2) => {
            console.log(result2);
            // 读取 3.txt
            fs.readFile('3.txt', 'utf8', (error, result3) => {
                console.log(result3);
            })
        })
    })
    

    7.3 Promise

    Promise 出现的目的是解决 Node.js 异步编程回调地狱的问题。

    是一种异步编程的语法改进。

    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            if (true) {
                resolve({ name : 'Tom' })
            } else {
                reject('失败了')
            }
        }, 2000);
    }):
    promise.then(result => console.log(result))
        .catch(error => console.log(error));
    
    • resolve
      • 一个函数
      • 将异步 API 的执行结果传递出去
    • reject
      • 一个函数
      • 将异步 API 的执行错误信息传递出去
    const fs = require('fs');
    
    // 读取 1.txt
    function promise1() {
        return new Promise((resolve, reject) => {
            fs.readFile('1.txt', 'utf8', (error, result) => {
                if (error == null) {
                    resolve(result);
                } else {
                    reject(error);
                }
            });
        });
    }
    // 读取 2.txt
    function promise2() {
        return new Promise((resolve, reject) => {
            fs.readFile('2.txt', 'utf8', (error, result) => {
                if (error == null) {
                    resolve(result);
                } else {
                    reject(error);
                }
            });
        });
    }
    // 读取 3.txt
    function promise3() {
        return new Promise((resolve, reject) => {
            fs.readFile('3.txt', 'utf8', (error, result) => {
                if (error == null) {
                    resolve(result);
                } else {
                    reject(error);
                }
            });
        });
    }
    
    promise1().then((result1) => {
        console.log(result1);
        return promise2();
    })
    .then((result2) => {
        console.log(result2);
        return promise3();
    })
    .then((result3) => {
        console.log(result3);
    })
    

    代码臃肿。

    7.4 异步函数

    异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,是代码变得清晰明了。

    async关键字

    1. 普通函数定义前加 async 关键字,普通函数变成异步函数;
    2. 异步函数默认返回 promise 对象;
    3. 在异步函数内部使用 return 关键字进行结果返回,结果会被包裹在 promise 对象中,return 关键字代替了 resolve 方法;
    4. 在异步函数内部使用 throw 关键字抛出程序异常;
    5. 调用异步函数再链式调用 then 方法获取异步函数执行结果;
    6. 调用异步函数再链式调用 catch 方法获取异步函数执行的错误信息。
    const fn = async() => {};
    
    // 1. 普通函数定义的前面加上 async 关键字,普通函数变成了异步函数
    async function fn() {
        // 3. 在异步函数内部使用 throw 关键字抛出错误。throw 后面的代码不再执行
        // throw '发生了一些错误';
        
        return 123;
    }
    console.log(fn());  // 2. 异步函数默认的返回值是 promise 对象
    
    fn.then(function(data) {
        console.log(data);
    }).catch(function(error) {
        console.log(error);
    })
    

    await 关键字

    1. 它只能出现在异步函数中;
    2. await 后面只能写 promise 对象,写其他类型的 API 是不可以的;
    3. await 可以暂停异步函数,等待 promise 对象返回结果后再向下执行
    async function p1() {
        return 'p1';
    }
    async function p2() {
        return 'p2';
    }
    async function p3() {
        return 'p3';
    }
    
    async function run() {
        let r1 = await p1();
        let r2 = await p2();
        let r3 = await p3();
        console.log(r1);
        console.log(r2);
        console.log(r3);
    }
    

    读取文件:

    const fs = require('fs');
    // 改造现有异步函数 api,让其返回 promise 对象,从而支持异步函数语法
    const promisify = require('util').promisify;
    // 调用 promisify 方法改造现有异步 API,让其返回 promise 对象
    const readFile = promisify(fs.readFile);
    
    async function run() {
        let r1 = await readFile('1.txt', 'utf8');
        let r2 = await readFile('1.txt', 'utf8');
        let r3 = await readFile('1.txt', 'utf8');
        console.log(r1);
        console.log(r2);
        console.log(r3);
    }
    run();
    

    八、Node.js 全局对象 global

    在浏览器中全局对象是 window,在 Node 中全局变量是 global。

    Node 中全局变量有以下方法,可以在任何地方使用,global 可以省略。

    • console.log():在控制台输出
    • setTimeout():设置超时定时器
    • clearTimeout():清除超时定时器
    • setInterval():设置间歇定时器
    • clearInterval():清除间歇定时器
    global.setTimeout(function() {
        
    }, 2000);
    

    九、MongoDB

    • 动态网站的数据都存储在数据库中
    • 数据库可以用来持久存储客户端通过表单收集的用户信息
    • 数据库软件本身可以对数据进行搞笑的管理

    数据库即存储数据的仓库。可以将数据进行有序的分门别类的存储。它是独立于语言之外的软件,可以通过 API 去操作它。

    在一个数据库软件中可以包含多个数据仓库,在每个数据仓库中可以包含多个数据集合,每个数据集合中可以包含多条文档(具体的数据)。

    术语 解释说明
    database 数据库,MongoDB 数据库软件中可以建立多个数据库
    collection 集合,一组数据的结合,可以理解为 JavaScript 中的数组
    document 文档,一条具体的数据,可以理解为 JavaScript 中的对象
    field 字段,文档中的属性名称,可以理解为 JavaScript 中的对象属性

    9.1 Mongoose

    • 使用 Node.js 操作 MongoDB 数据库需要依赖 Node.js 第三方包 mongoose
    • 使用 npm install mongoose 命令下载
    1. 启动 MongoDB

      Windows 在命令行工具中运行 net start mongoDB 启动,否则 MongoDB 无法连接。
      Mac OS 执行 ./mongod 命令启动。mac系统下安装、启动、停止mongodb

    2. 数据库连接

      使用 mongoose 提供的 connect 方法连接数据库。

      // 引入模块
      const mongoose = require('mongoose');
      // 发起连接
      mongoose.connect('mongodb://localhost/user', { useNewUrlParse: true })
          .then(() => console.log('数据库连接成功'))
          .catch(error => console.log('数据库连接失败', error));
      
    3. 创建集合

      创建集合分为两步,1 对集合设定规则,2 创建集合。创建 mongoose.Schema 构造函数的实例即可创建集合。

      // 设定集合规则
      const courseSchema = new mongoose.Schema({
          name: String,
          author: String,
          isPublished: Boolean
      });
      // 使用规则创建集合
      // 参数 1:集合名称,首字母大写,'Course' 在数据库中会设置为 courses
      // 参数 2:应用的规则
      const Course = mongoose.model('Course', courseSchema);  
      
    4. 创建文档

      创建文档实际上就是向集合中插入数据。

      • 创建集合实例;
      • 调用实例对象下的 save 方法将数据保存到数据库中。
      // 创建集合实例
      const course = new Course({
          name: 'Node.js course',
          author: 'Tom',
          isPublished: true
      });
      // 将数据保存到数据库中
      course.save();
      

      第二种方式:

      Course.create({ 
          name: 'JavaScript base',
          author: 'Tom',
          isPublished: true
      }, (error, doc) => {
          // 错误对象
          console.log(error);
          // 当前插入的文档
          console.log(doc);
      });
      

      和数据库所有的操作都是异步的。

      Course.create({ 
          name: 'JavaScript base',
          author: 'Tom',
          isPublished: true
      }).then(doc => {
          console.log(doc);
      }).catch(error => {
          console.log(error);
      })
      
    5. 将现成的数据插入到数据库

      mongoimport -d 数据库名称 -c 集合名称 -file 要导入的数据文件

    6. 查询文档

      // 根据条件查找文档(条件为空则查找所有文档),返回数组类型
      Course.find().then(result => console.log(result));
      // 查找 _id 为 5f60340bbfac088cceef24e4 的数据,返回数组类型
      Course.find({ _id: '5f60340bbfac088cceef24e4' }).then(result => console.log(result));
      // 查找一条满足查询条件的数据,返回对象类型
      Course.findOne().then(result => console.log(result));
      // 查询年龄 >20 且 <50 的用户
      User.find({ age: {$gt: 20, $lt: 50} }).then(result => console.log(result));
      // 查询爱好包含足球的用户
      User.find({ hobbies: {$in: ['足球']} }).then(result => console.log(result));
      // 选择要查询的字段。下面只想查询 name 和 email 字段,同时不想查询 _id 字段
      User.find().select('name email -_id').then(result => console.log(result));
      // 将数据按照年龄进行升序排列
      User.find().sort('age').then(result => console.log(result));
      // 将数据按照年龄进行降序排列
      User.find().sort('-age').then(result => console.log(result));
      // skip 跳过多少条数据,limit 限制查询数量
      User.find().skip(2).limit(2)..then(result => console.log(result));
      
      // 返回文档集合
      [
        {
          _id: 5f60340bbfac088cceef24e4,
          name: 'Node.js course',
          author: 'Tom',
          isPublished: true,
          __v: 0
        },
        {
          _id: 5f603487f5ac9d8ce2ee8a4b,
          name: 'Node.js course',
          author: 'Tom',
          isPublished: true,
          __v: 0
        },
        {
          _id: 5f603487f5ac9d8ce2ee8a4c,
          name: 'JavaScript base',
          author: 'Tom',
          isPublished: true,
          __v: 0
        },
        {
          _id: 5f603487f5ac9d8ce2ee8a4d,
          name: 'JavaScript base',
          author: 'Tom',
          isPublished: false,
          __v: 0
        }
      ]
      
    7. 删除文档

      // 删除单条文档。返回被删除的文档
      // 如果查询条件匹配到多个文档,那么将会删除第一个匹配的文档
      Course.findOneAndDelete({ 
          _id: '5f603487f5ac9d8ce2ee8a4b'
      }).then(result => console.log(result));
      // 删除多条文档。返回 { n: 4, ok: 1 },n 表示删除条数,ok 表示执行状态
      User.deleteMany({}).then(result => console.log(result));
      
    8. 更新文档

      // 更新单条文档,如果匹配到了多条文档,也是更新一条。
      User.updateOne({查询条件}, {要修改的值}).then(result => console.log(result));
      //User.updateOne({ name: '张三' }, { name: '李四' }).then(result => console.log(result));
      // 更新多条文档
      User.updateMany({查询条件}, {要修改的值}).then(result => console.log(result));
      //User.updateMany({}, { age: 18 }).then(result => console.log(result));
      
    9. mongoose 验证

      在创建集合规则时,可以设置当前字段的验证规则,验证失败就输出插入失败。

      const postSchema = new mongoose.Schema({
          title: {
              type: String,
              // 必选字段
              required: [true, '请传入文章标题'],
              // 字符串的最小长度
              minlength: [2, '文章长度不能小于 2'],
              // 字符串的最大长度
              maxlength: [5, '文章长度最大不能超过 5'],
              // 去除字符串两端的空格
              trim: true
          }, 
          age: {
              type: Number,
              // 数字的最小范围
              min: 18,
              // 数字的最大范围
              max: 100
          },
          publishDate: {
              type: Date,
              // 默认值
              default: Date.now
          },
          category: {
              type: String,
              // 枚举,列举出当前字段可选的值
              //enum: ['html', 'css', 'javascript', 'node.js']
              enum: {
                  values:['html', 'css', 'javascript', 'node.js'],
                  message: '分类名称要在一定的范围内才可以'
              }
          }, 
          author: {
              type: String,
              // 自定义验证规则
              validate: {
                  validator: v => {
                      // v 是要验证的值
                      // 返回布尔值:true - 验证成功;false - 验证失败
                      return v && v.length > 4;
                  }
                  // 自定义错误信息
                  message: '传入的值不符合验证规则';
              }
          }
      });
      const Post = mongoose.model('Post', postSchema);
      
      Post.create({
          title: 'a', 
          age: 60, 
          category: 'html'
      }).then(result => console.log(result))
      .catch(error => {
          // 获取错误信息对象
          const err = error.errors;
          // 循环错误信息
          for (var attr in err) {
              console.log(err[attr]['message']);
          }
      });
      
    10. 集合关联

      通常不同集合的数据之间是有关系的,例如文章信息和用户信息存储在不同集合中,但文章是某个用户发表的,要查询文章的所有信息包括发表用户,就需要用到集合关联。

      • 使用 id 对集合进行关联
      • 使用 populate 方法进行关联集合查询
      // 用户集合
      const User = mongoose.model('User', new mongoose.Schema({
          name: {
              type: String
          }
      }));
      // 文章集合
      const Post = mongoose.model('Post', new mongoose.Schema({
          title: { 
              type : String
          },
          // 使用 id 将文章集合和作者集合进行关联
          author: { 
              type: mongoose.Schema.Types.ObjectId, 
              ref: 'User'
          }
      }));
      
      // 创建用户
      User.create({ name: 'Tom'}).then(result => console.log(result));
      // 创建文章
      Post.create({ title: '123', author: 'User集合的id'}).then(result => console.log(result));
      
      Post.find().populate('author').then(result => console.log(result));
      

    9.2 数据库添加账号

    1. 创建 mongodb 服务 mongod
    2. 连接数据库 mongo
    3. 查看数据库 show dbs
    4. 切换到 admin 数据库 use admin
    5. 创建超级管理员账号 db.createUser()
    6. 切换到数据库 use blog,如果不存在会自动创建
    7. 创建普通账号 db.createUser()
    8. 删除 mongodb 服务 use admin;db.shutdownServer();
    9. 创建 mongodb 服务 mongod
    10. 在项目中使用账号连接数据库 mongoose.connect('mongodb://username:password@localhost:port/database')

    十、模板引擎

    模板引擎是第三方模块。

    让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护。

    未使用模板引擎的写法

    var arr = [{ name: 'Tom', age: 20 }];
    var str = '<ul>';
    for (var i = 0; i < arr.length; i++) {
        str += '<li>
            <span>' + arr[i].name + '</span>
            <span>' + arr[i].age + '</span>
        </li>';
    }
    str += '</ul>';
    

    使用模板引擎的写法

    <ul>
        {{each arr}}
            <li>{{$value.name}}</li>
            <li>{{$value.age}}</li>
        {{/each}}
    </ul>
    

    10.1 art-template 模板引擎

    1. 命令行工具中使用 npm install art-template 命令进行下载
    2. 使用 const template = require('art-template') 引入模板引擎
    3. 告诉模板引擎要拼接的数据和模板在什么位置 const html = template('模板数据', 数据);
    // 导入模板引擎模块
    const template = require('art-template');
    // 将特定模板与特定数据进行拼接
    const html = template('./views/index.art', {
        data: {
            name: 'Tom',
            age: 20
        }  
    });
    
    <!--前端页面-->
    <div>
        <span>{{data.name}}</span>
        <span>{{data.age}}</span>
    </div>
    

    10.2 模板语法

    • art-template 同时支持两种模板语法:标准语法原始语法
    • 标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力。

    标准语法:{{数据}}
    原始语法:<%=数据%>

    1. 输出

      <!--标准语法-->
      <h2>{{value}}</h2>
      <h2>{{a ? b : c}}</h2>
      <h2>{{a + b}}</h2>
      
      <!--原始语法-->
      <h2><%= value %></h2>
      <h2><%= a ? b : c %></h2>
      <h2><%= a + b %></h2>
      
    2. 原文输出

      如果数据中携带 HTML 标签,默认模板引擎不会解析标签,会将其转义后输出。

      • 标准语法:{{@ 数据 }}
      • 原始语法:<%- 数据 %>
    3. 条件判断

      在模板中可以根据条件来决定显示哪块 HTML 代码

      <!-- 标准语法 -->
      {{if 条件}}
          ...
      {{/if}}
      
      {{if v1}}
          ...
      {{else if v2}}
          ...
      {{/if}}
      <!--原始语法-->
      <% if (value) { %> ... <% } %>
      <% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>
      

      示例:

      {{ if age > 18 }}
          年龄大于 18
      {{ else if age < 15 }}
          年龄小于 15
      {{ else }}
          年龄不符合要求
      {{/if}}
      
      <% if (age > 18) { %> 
          年龄大于 18
      <% } else if (age < 15) { >%
          年龄小于 15
      <% } else { >%
          年龄不符合要求
      <% } %>
      
    4. 循环

      • 标准语法:{{each 数据}} ... {{/each}}
      • 原始语法:<% for() { %> ... <% } %>
      <!--标准语法-->
      {{each target}}
          {{$index}} {{$value}}
      {{/each}}
      <!--原始语法-->
      <% for(var i = 0; i < target.length; i++) { %>
          <% i %> <%= target[i] %>
      <% } %> 
      

      示例:

      <!--标准语法-->
      <ul>
          {{each users}}
              <li>
                  {{$value.name}}
                  {{$value.age}}
                  {{$value.sex}}
              </li>
          {{/each}}
      </ul>
      <!--原始语法-->
      <ul>
          <% for (var i = 0; i < users.length; i++) %>
              <li>
                  <%= users[i].name %>
                  <%= users[i].age %>
                  <%= users[i].sex %>
              </li>
          <% } %>
      </ul>
      
    5. 子模板

      使用子模板可以将网站公共区块(头部、尾部)抽离到单独的文件中。

      • 标准语法:{{include '子模板路径'}}
      • 原始语法:<%include('子模板路径')%>
      <!--标准语法-->
      {{ include './header.art' }}
      <!--原始语法-->
      <% include('./header.art') %>
      
    6. 模板继承

      使用模板继承可以将网站 HTML 骨架抽离到单独的文件中,其他页面模板可以继承骨架文件。

      <!doctype html>
      <html>
          <head>
              <meta charset="utf-8">
              <title>HTML骨架模板</title>
              <!-- 挖坑 -->
              {{block 'head'}}{{/block}}
          </head>
          <body>
              <!-- 挖坑 -->
              {{block 'content'}}{{/block}}
          </body>
      </html>
      
      <!-- 继承 -->
      {{extend './layout.art'}}
      <!-- 填充 -->
      {{block 'head'}} <link rel="stylesheet" href=custom.css"> {{/block}}
      {{block 'content'}} <p>This is just an awesome page.</p> {{/block}}
      
    7. 模板配置

      • 向模板中导入变量 template.defaults.imports.变量名 = 变量值
      const template = require('art-template');
      const path = require('path');
      const dateFormat = require('dateformat');
      const views = path.join(__dirname, 'views', '01.art');
      // 导入模板变量
      template.defaults.imports.dateFormat = dateFormat;
      
      const html = template(views, {
          time: new Date()
      })
      

      模板文件

      {{ dateFormat(time, 'yyyy-mm-dd') }}
      
      • 设置模板根目录 template.defaults.root = 根目录
      // 设置模板的根目录
      template.defaults.root = path.join(__dirname, 'views');
      const html = template('01.art', {
          time: new Data()  
      });
      
      • 设置模板默认后缀 template.defaults.extname = '.art'
      template.defaults.root = path.join(__dirname, 'views');
      template.defaults.extname = '.art';
      const html = template('01', {
          time: new Date()
      })
      

    十一、第三方模块

    11.1 router

    功能:实现路由

    使用步骤:

    1. 获取路由对象
    2. 调用路由对象提供的方法创建路由
    3. 启用路由,使路由生效
    const getRouter = require('router');
    const router = getRouter();
    
    router.get('/add', (req, rsp) => {
        rsp.end('add page!');
    })
    server.on('request', (req, rsp) => {
        router(req, rsp);
    })
    

    11.2 serve-static

    功能:实现静态资源访问服务

    步骤:

    1. 引入 serve-static 模块获取创建静态资源服务功能的方法
    2. 调用方法创建静态资源服务并指定静态资源服务目录
    3. 启用静态资源服务功能
    const serveStatic = require('serve-static');
    const serve = serveStatic('public');
    app.on('request', (req, rsp) => {
        serve(req, rsp)
    })
    app.listen(3000)
    

    11.3 dateFormat

    功能:日期格式化。

    1. 引入 dateformat 模块获取格式化对象
    2. 格式化日期
    const dateFormat = require('dateformat');
    dateFormat(Date.now, 'yyyy-mm-dd');
    

    11.4 bcrypt

    功能:字符串加密。

    • 哈希加密是单程加密方式,由加密后的内容无法得到原来的内容
    • 在加密的密码中加入随机字符串可以增加密码被破解的难度
    // 导入 bcrypt 模块
    const bcrypt = require('bcrypt');
    // 生成随机字符串 gen => generate 生成 salt(盐)
    // genSalt 方法接收一个数值作为参数
    // 数值越大,生成的随机字符串复杂度越高;数值越小,生成的随机字符串复杂度月底
    // 默认值是 10
    // 返回生成的随机字符串
    let salt = await bcrypt.genSalt(10);
    // 使用随机字符串对密码进行加密
    let pass = await bcrypt.hash('明文密码', salt);
    
    // 密码比对
    let isEqual = await bcrypt.compare('明文密码', '加密密码');
    

    11.5 Joi

    功能:JavaScript 对象的规则描述语言和验证器

    // 导入 joi 模块
    const Joi = require('joi');
    // 创建验证规则
    const schema = {
        username: Joi.string().alphanum().min(3).max(30).required().error(new Error('错误信息')),
        password: Joi.string.regex(/^[a-zA-Z0-9]{3,30}$/),
        access_token: [Joi.string(), Joi.number()],
        birthyear: Joi.number().integer().min(1900).max(2013),
        email: Joi.string().email()
    };
    Joi.validate({username: 'abc', birthyear: 1994}, schema);
    

    11.6 formidable

    功能:解析表单,支持 get 请求参数,post 请求参数、文件上传。

    // 引入 formidable 模块
    const formidable = require('formidable');
    // 创建表单解析对象
    const form = new formidable.IncomingForm();
    // 设置文件上传路径
    form.uploadDir = '/my/dir';
    // 是否保留表单上传文件的扩展名
    form.keepExtensions = true;
    
    // 对表单进行解析
    form.parse(req, (error, fields, files) => {
        // fields 存储普通请求参数
        // files 存储上传文件的信息
    })
    

    11.7 mongoose-sex-page

    功能:数据分页

    const pagination = require('mongoose-sex-page');
    // display 要显示的页码数,当有很多页时,方便显示 
    pagination(集合构造函数).page(1).size(20).display(8).exec();
    
    // 返回的数据
    {
        "page": 1,  // 当前页
        "size": 2,  // 每页显示数据条数
        "total": 8,  // 总的数据条数
        "records": [
            // 查询出来的具体数据
            {
                "_id": "531723610337asi39217s2",
                "title": "文章标题"
            }
        ],
        "pages": 4,   // 总的页数
        "display": [1, 2, 3, 4]   // 客户端显示的页码
    }
    

    11.8 morgan

    功能:控制台打印信息

    npm install morgan

    // 导入 morgan 模块
    const morgan = require('morgan');
    
    // 获取系统环境变量,返回值是对象
    if (process.env.NODE_ENV == 'development') {
        console.log('当前是开发环境');
        // 在开发环境中,将客户端发送到服务端的请求信息打印到控制台中
        app.use(morgan('dev'));
    } else {
        console.log('当前是生产环境');
    }
    

    11.9 config

    功能:允许开发者将不同运行环境下的应用配置信息抽离到单独的文件中,模块内部自动判断当前应用的运行环境,并读取对应的配置信息,极大节省应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码中修改配置信息。

    使用步骤:

    1. 使用 npm install config 命令下载模块
    2. 在项目的根目录下新建 config 文件夹
    3. 在 config 文件夹下新建 default.json、development.json、production.json 文件。
    4. 在项目中通过 require 方法,将模块进行导入
    5. 使用模块内部提供的 get 方法获取配置信息
    {
        "title": '博客系统 - 开发环境',
        "db": {
            "user": "",
            "pwd": "",
            "host": "",
            "port": "27017",
            "name": "blog"
        }
    }
    
    // 导入config模块
    const config = require('config');
    mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`, {useNewUrlParser: true })
    	.then(() => console.log('数据库连接成功'))
    	.catch(() => console.log('数据库连接失败'))
    

    将敏感配置信息存储在环境变量中。

    1. 在 config 文件夹中创建 custom-environment-variables.json 文件;
    2. 配置项属性的值填写系统环境变量的名字
    3. 项目运行时 config 模块查找系统环境变量,并读取其值作为当前配置项属于的值

    十二、Express 框架

    12.1 Express 框架简介

    Express 是一个基于 Node 平台的 web 应用开发框架,它提供了一系列的强大特性,帮助开发者创建各种 Web 应用。

    使用 npm install express 命令下载。

    12.2 Express 框架特性

    • 提供了方便简洁的路由定义方式
    • 对获取 HTTP 请求参数进行了简化处理
    • 对模板引擎支持程度高,方便渲染动态 HTML 页面
    • 提供了中间件机制有效控制 HTTP 请求
    • 拥有大量第三方中间件对功能进行扩展

    12.3 原生 Node.js 与 Express 框架对比

    12.3.1 路由
    app.on('request', (req, rsp) => {
        // 获取客户端的请求路径
        let { pathname } = url.parse(req.url);
        // 对请求路径进行判断,不同的路径地址响应不同的内容
        if (pathname == '/' || pathname == 'index') {
            rsp.end('欢迎来到首页');
        } 
        else if (pathname == '/list') {
            rsp.end('欢迎来到列表页');
        }
        else if (pathname == '/about') {
            rsp.end('欢迎来到关于我们页面');
        }
        else { 
            rsp.end('抱歉,您访问的页面出游了');
        }
    })
    
    // 当客户端以 get 方式访问
    app.get('/', (req, rsp) => {
        // 对客户端作出响应
        rsp.send('欢迎来到首页');
    });
    
    // 当客户端以 post 方式访问
    app.post('/add', (req, rsp) => {
        rsp.send('使用post方式请求/add路由');
    });
    
    12.3.2 获取请求参数
    app.on('request',  (req, rsp) => {
        // 获取 GET 参数
        let { query } = url.parse(req.url, true);
        // 获取 POST 参数
        let postData = '';
        req.on('data', (chunk) => {
            postData += chunk;
        });
        req.on('end', () => {
            console.log(querystring.parse(postData));
        })
    })
    
    app.get('/', (req, rsp) => {
        // 获取 GET 参数
        console.log(req.query);
    })
    app.post('/', (req, rsp) => {
        // 获取 POST 参数
        console.log(req.body);
    })
    

    12.4 中间件

    中间件是一堆方法,可以接收客户端发来的请求,可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理。

    中间件主要由两部分构成:

    1. 中间件方法
    2. 请求处理函数

    中间件方法由 Express 提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求。

    // get、post 方法是 Express 提供的,'处理函数'是开发人员提供的
    app.get('请求路径', '处理函数');   
    app.post('请求路径', '处理函数'); 
    

    可以针对同一个请求设置多个中间件,对同一个请求进行多次处理。

    // next 是一个权限控制函数
    app.get('/request', (req, rsp, next) => {
        req.name = 'Tom';
        next();
    });
    app.get('/request', (req, rsp) => {
        rsp.send(req.name);
    });
    

    默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。

    可以调用 next 方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件。

    12.4.1 app.use

    app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求。

    app.use((req, rsp, next) => {
        console.log(req.url);
        next();
    })
    

    app.use 第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求。

    app.use('/list', (req, rsp, next) => {
        console.log(req.url);
        next();
    })
    
    12.4.2 中间件应用
    1. 路由保护,客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。

      app.use('/admin', (req, rsp, next) => {
          let isLogin = false;
          
          if (isLogin) {
              next();
          } else {
              rsp.send('未登录');
          }
      })
      app.get('/admin', (req, rsp) => {
          rsp.send('已登录');
      })
      
    2. 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,

      // 网站公告
      app.use((req, rsp, next) => {
          rsp.send('当前网站正在维护...');
      })
      app.use('/admin', (req, rsp, next) => {
          let isLogin = false;
          
          if (isLogin) {
              next();
          } else {
              rsp.send('未登录');
          }
      })
      app.get('/admin', (req, rsp) => {
          rsp.send('已登录');
      })
      
    3. 自定义 404 页面。在所有路由的最下面定义接收所有请求的中间件。

      app.use('/admin', (req, rsp, next) => {
          let isLogin = false;
          
          if (isLogin) {
              next();
          } else {
              rsp.send('未登录');
          }
      })
      app.get('/admin', (req, rsp) => {
          rsp.send('已登录');
      })
      app.use((req, rsp, next) => {
          rsp.status(404).send('当前访问的页面不存在');
      })
      
    12.4.3 错误处理中间件

    在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败、数据库连接失败。错误处理中间件是一个集中处理错误的地方。

    app.get('/index', (req, rsp) => {
        throw new Error('程序发生了未知错误');
    })
    // 错误处理中间件
    app.use((err, req, rsp, next) => {
        rsp.status(500).send(err.message);
    })
    

    以上只能捕获到同步处理程序抛出的错误。

    当程序出现错误时,调用 next() 方法,并且将错误信息通过参数的形式传递给 next() 方法,即可触发错误处理中间件。

    app.get('/', (req, rsp, next) => {
        fs.readFile('/file-does-not-exsit', (err, data) => {
            if (err) {
                next(err);
            } else {
                rsp.send(data);
            }
        })
    })
    
    12.4.4 捕获错误

    在 node.js 中,异步 API 的错误信息都是通过回调函数获取的,支持 Promise 对象的异步 API 发生错误时可以通过 catch 方法捕获。

    那么普通异步函数执行如果发生错误时要如何捕获错误呢?

    try catch 可以捕获异常函数以及其他同步代码在执行过程中发生的错误,但是不能捕获其他类型的 API 发生的错误。

    app.get('/list', async (req, rsp, next) => {
        try {
            await User.find({ name: 'Tom' }) 
        } catch(ex) {
            next(ex);
        }
    })
    // 错误处理中间件
    app.use((err, req, rsp, next) => {
        rsp.status(500).send(err.message);
    })
    

    12.5 Express 请求处理

    12.5.1 构建模块化路由

    home.js

    const express = require('express');
    // 创建路由对象
    const home = express.Router();
    
    // 在 home 路由下继续创建路由
    home.get('/index', (req, rsp) => {
        rsp.send('欢迎来到博客展示页面');
    });
    module.exports = home;
    

    admin.js

    const express = require('express');
    // 创建路由对象
    const admin = express.Router();
    
    // 在 admin 路由下继续创建路由 
    home.get('/index', (req, rsp) => {
        rsp.send('欢迎来到博客展示页面');
    });
    module.exports = admin;
    

    app.js

    const home = require('./route/home.js');
    const admin = require('./route/admin.js');
    // 将路由和请求路径进行匹配
    app.use('/home', home);
    app.use('/admin', admin);
    
    12.5.2 GET 参数的获取

    Express 框架中使用 req.query 即可获取 GET 参数,框架内部会将 GET 参数转换为对象并返回。

    // 接收地址栏中问号后面的参数
    // 例如:http://localhost:3000/?name=Tom&age=30
    app.get('/', (req, rsp) => {
        console.log(req.query);  // { 'name': 'Tom', 'age': '30' }  
    });
    
    12.5.3 POST 参数的获取

    Express 中接收 post 请求参数需要借助第三方包 body-parser。

    // 引入 body-parser 模块
    const bodyParser = require('body-parser');
    // 配置 body-parser 模块
    // extended = false 时会使用 querystring 系统模块对参数进行处理
    // extended = true 时使用第三方模块 qs 进行处理
    app.use(bodyParser.urlencoded({ extended: false }));
    // 接收请求
    app.post('/add', (req, rsp) => {
        // 接收请求参数
        console.log(req.body);  
    });
    

    urlencoded() 方法内部会检查方法请求体中是否包含参数,如果包含,会接收请求参数,并转换为对象类型,再为 req 请求对象添加一个 body 属性,并将对象赋值给 body。

    app.use(fn({a: 1}));
    
    function fn(obj) {
        return function (req, rsp, next) {
            if (obj.a == 1) {
                console.log(req.url);
            } else {
                console.log(req.method);
            }
            next();
        }
    }
    
    12.5.4 Express 路由参数
    app.get('/find/:id', (req, rsp) => {
        console.log(req.params);  // {id: 123}
        rsp.send(req.params);
    });
    

    localhost:3000/find/123

    在请求地址中,123 对应着 id 参数,id 是占位的参数名,123 是传的参数值。

    注意:

    1. 参数不用 ? 进行连接
    2. 如果不传递参数,是不能匹配的,如果请求地址是 localhost:3000/find 将不能匹配。

    12.6 静态资源的处理

    通过 Express 内置的 express.static 可以方便地托管静态文件,例如:img、CSS、JS 文件。

    app.use(express.static('public'));
    

    这样设置后,public 目录下的所有文件都可以访问了。

    12.7 express-art-template 模板引擎

    • 为了使 art-template 模板引擎能够更好的和 Express 框架配合,模板引擎官方在原 art-template 模板引擎的基础上封装了 express-art-template。
    • 使用 npm install art-template express-art-template 命令进行安装。
    // 当渲染后缀为 art 的模板时,使用 express-art-template
    // 参数 1:模板后缀
    // 参数 2:使用的模板引擎
    app.engine('art', require('express-art-template'));
    // 设置模板存放目录
    app.set('views', path.join(__dirname, 'views'));
    // 设置模板的默认后缀。渲染模板时不写后缀,默认拼接 .art 后缀
    app.set('view engine', 'art');
    app.get('/', (req, rsp) => {
        // 渲染模板
        rsp.render('index', {
            msg: 'message'
        });
    })
    

    12.8 app.locals 对象

    将变量设置到 app.locals 对象下面,这个数据在所有的模板中都可以获取到。

    app.locals.users = [{
        name: 'Tom',
        age: 20
    }, {
        name: 'Jack',
        age: 18
    }]
    

    十三、cookie 与 session

    cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务端存储数据。

    • cookie 中的数据是以域名的形式进行区分
    • cookie 中的数据是有过期时间的,超过时间数据会被浏览器自动删除
    • cookie 中的数据会随着请求被自动发送到服务器端

    session:实际上就是一个对象,存储在服务端的内存中,在 session 对象中也可以存储多条数据,每一条数据都是一个 sessionid 作为唯一标识。

    在 node.js 中需要借助 express-session 实现 session 功能。

    // 导入 express-session 模块
    const session = require('express-session');
    // 配置 session
    app.use(session({ secret: 'secret key' }));
    

    删除 session 和 cookie:

    // 删除 session
    req.session.destroy(function() {
        // 删除 cookie
        rsp.clearCookie('connect.sid');
        // 重定向到登录页面
        rsp.redirect('/admin/login');
    }
    
  • 相关阅读:
    WP8.1 UI 编程 四、图形
    分治法 全排列问题的一个Java实现
    合并排序算法时间复杂度分析
    js如何实现复制粘贴功能
    关于<meta name="applicable-device"content="pc">
    ready
    css文字强制大写输入
    关于input,button标签在苹果手机上显示阴影解决办法
    【C#】中用if判断值是否是否为空
    视频格式MP4,需要转码
  • 原文地址:https://www.cnblogs.com/dins/p/13744252.html
Copyright © 2011-2022 走看看