Nodejs

Node.js一万字

Node.js与Gulp

1. 服务器端开发

1.1 为什么要学习服务器端开发
  • 能够和后端程序员更加紧密的配合

  • 网站业务逻辑前置,学习前端技术需要后端技术支撑(Ajax)

  • 扩宽知识视野,能够站在更高的角度审视整个项目

1.2 服务器端开发要做的事情
  • 实现网站的业务逻辑

  • 数据的增删改查

1.3 为什么选择 Node
  • 能够使用 JavaScript 语法开发后端应用

  • 一些公司要求前端工程师掌握 Node 开发

  • 生态系统活跃,有大量开源库可以使用

  • 前端开发工具大多基于 Node 开发

1.4 Node 是什么
  • Node 是一个基于 Chrome V8 引擎的 JavaScript 代码运行环境。
1.5 Node.js 的组成
  • JavaScript 由三部分组成,ECMAScript,DOM,BOM。

  • Node.js 是由 ECMAScript 及 Node 环境提供的一些附加 API 组成的,包括文件、网络、路径等等一些更加强大的 API。

1.6 Node.js 模块化
  • JavaScript 在使用时存在两大问题,文件依赖和命名冲突。

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

  • Node.js 规定一个 JavaScript 文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到

  • 模块内部可以使用 exports 对象进行成员导出, 使用 require 方法导入其他模块。

//a.js
// 在模块内部定义变量
let version = "1.0";
// 在模块内部定义方法
const sayHi = (name) => `你好,${name}`;
const add = (n1, n2) => n1 + n2;
// 向模块外部导出数据
exports.version = version;
exports.sayHi = sayHi;
exports.add = add;
// b.js
// 在b.js模块中导入模块a.js
let a = require("./a.js");
// 输出a模块中的version变量
console.log(a.version); //1.0
// 调用a模块中的sayHi方法 并输出其返回值
console.log(a.sayHi("hello a.js")); //你好,hello a.js
console.log(a.add(1, 2)); //3
  • exports 是 module.exports 的别名(地址引用关系),导出对象最终以 module.exports 为准
module.exports.version = version;
module.exports.sayHi = sayHi;

2. 系统模块

2.1 什么是系统模块
  • Node 运行环境提供的 API. 因为这些 API 都是以模块化的方式进行开发的, 所以我们又称 Node 运行环境提供的 API 为系统模块
2.2 系统模块 fs 文件操作
  • f:file 文件 ,s:system 系统,文件操作系统。
// 引入文件系统  fs 模块
const fs = require("fs");

// 读取文件内容 需要硬盘操作文件,耗时
// fs.readFile('文件路径/文件名称','文件编码', callback);

// 读取上一级css目录下中的base.css
fs.readFile("../css/base.css", "UTF-8", (err, doc) => {
  console.log(err);
  if (err === null) {
    // 如果文件读取发生错误,参数err的值为错误对象,否则err的值为null
    // doc参数为文件内容
    // 错误优先的回调函数
    console.log(doc);
    console.log("文件读取成功");
    // output file contents in console
  }
});

// 写入文件内容 需要硬盘操作文件,耗时
// fs.writeFile('文件路径/文件名称','数据',callback)

const contents = 'console.log("Hello World");';
fs.writeFile("./module.exports.js", contents, (err) => {
  if (err != null) {
    console.log(err); //如果出错,打印提示信息
    console.log("文件写入失败");
    return;
  }
});

// 检查是否写入
fs.readFile("./module.exports.js", "utf-8", (err, doc) => {
  if (err === null) {
    console.log(doc);
    console.log("文件写入成功");
  }
});
2.3 系统模块 path 路径操作
  • 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
  • 在读取文件或者设置文件路径时都会选择绝对路径
  • 使用__dirname 获取当前文件所在的绝对路径
// 路径拼接
// 因为linux操作系统只用/来做路径分隔符
// path.join('路径1','路径2','路径3',...)
// 引入path模块
const path = require("path");
// 路径拼接
let finialPath = path.join("itcast", "a", "b", "base.css");
// 输出结果 windows系统下输出\分隔符
console.log(finialPath); //itcast\a\b\base.css

// 相对路径VS绝对路径
// 在node.js中,大多数情况下使用绝对路径
// 使用__dirname获取当前文件所在的绝对路径
fs.writeFile(path.join(__dirname, "helloworld.js"), contents, (err) => {
  if (err != null) {
    console.log(err);
    console.log("文件写入失败");
  }
});

fs.readFile(path.join(__dirname, "helloworld.js"), "utf-8", (err, doc) => {
  console.log(err);
  console.log(doc);
  console.log("文件写入成功");
});

3.第三方模块

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

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

    • 以 js 文件的形式存在,提供实现项目具体功能的 API 接口。
    • 以命令行工具形式存在,辅助项目开发
3.2 获取第三方模块
  • npm (node package manager) : node 的第三方模块管理工具
npm install package 	# 下载
npm unintall package 	# 卸载
3.3 第三方模块 nodemon
  • nodemon 是一个命令行工具,用以辅助项目开发。
  • 在 Node.js 中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐。
npm install nodemon –g 	#全局安装
nodemon app.js
3.4 第三方模块 nrm

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

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

npm install nrm –g 		#全局安装
nrm ls 					#查询可用下载地址列表
nrm use address name    #切换npm下载地址
nrm use taobao			#切换下载地址为淘宝镜像源
3.5 第三方模块 Gulp
  • 基于 node 平台开发的前端构建工具
  • 将机械化操作编写成任务, 想要执行机械化操作时执行一个命令行命令任务就能自动执行了
  • 用机器代替手工,提高开发效率。
3.6 Gulp 能做什么
  • 项目上线,HTML、CSS、JS 文件压缩合并
  • 语法转换(es6、less …)
  • 公共文件抽离
  • 修改文件浏览器自动刷新
3.7 Gulp 使用
  • 使用 npm install gulp 安装 gulp
  • 在项目根目录下建立 gulpfile.js 文件
  • 重构项目的文件夹结构 src 目录放置源代码文件 dist 目录放置构建后文件
  • 在 gulpfile.js 文件中编写任务.
  • 在命令行工具中执行 gulp 任务
npm install gulp -g		#全局安装gulp
npm install gulp-cli -g	#全局安装gulp命令行工具
3.8 Gulp 中提供的方法
  • gulp.src():获取任务要处理的文件
  • gulp.dest():输出文件
  • gulp.task():建立 gulp 任务
  • gulp.watch():监控文件的变化
  • 用这些方法创建你得第一个 Gulp 任务
//引入gulp模块
const gulp = require("gulp");
// 使用gulp.task()方法建立任务
gulp.task("first", () => {
  // 获取要处理的文件
  gulp
    .src("./src/css/base.css")
    // 将处理后的文件输出到dist目录
    .pipe(gulp.dest("./dist/css"));
});
3.9 Gulp 插件
  • gulp-file-include: 公共文件抽取
  • gulp-htmlmin :html 压缩
  • gulp-less: less 语法转化
  • gulp-csso : css 压缩
  • gulp-babel @babel/core :JavaScript 语法转化
  • gulp-uglify :JavaScript 压缩混淆
//安装到全局环境-g 安装到开发依赖--save--dev 默认则安装到项目依赖
npm install gulp-htmlmin gulp-csso gulp-babel @babel/core gulp-less gulp-uglify gulp-file-include
3.10 Gulp 脚本
 const gulp = require('gulp');
// 引入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中的公共代码 fileinclude()
// 2.压缩html  htmlmin({ collapseWhitespace: true })
gulp.task("htmlmin", async () => {
  gulp
    .src("./src/*.html")
    .pipe(fileinclude())
    .pipe(htmlmin({ collapseWhitespace: true }))
    .pipe(gulp.dest("./dist"));
});
// css任务
// 1.less语法转换
// 2.css代码压缩
gulp.task("cssmin", async () => {
  gulp
    .src(["./src/css/*.less", "./src/css/*.css"]) //可以接收一个数组作为参数,获取多种类型的文件
    .pipe(less()) //转换
    .pipe(csso()) //压缩
    .pipe(gulp.dest("./dist/css")); //输出
});
// js任务
// 1.es6语法转换
// 2.js代码压缩
gulp.task("jsmin", async () => {
  gulp
    .src("./src/js/*.js")
    .pipe(
      babel({
        // 判断当前代码环境,并对代码进行转换
        presets: ["@babel/env"],
      })
    )
    .pipe(uglify())
    .pipe(gulp.dest("./dist/js"));
});
// 拷贝任务
// 复制文件夹
gulp.task("copy", async () => {
  gulp.src("./src/images/*").pipe(gulp.dest("./dist/images"));
  gulp.src("./src/lib/*").pipe(gulp.dest("./dist/lib"));
});
// 构建任务 调用其他定义好的任务
gulp.task("default", gulp.series("htmlmin", "cssmin", "jsmin", "copy"));
//command
gulp

4.package.json 文件

4.1 node_modules 文件夹的问题

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

4.2 package.json 文件的作用

项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github 地址、当前项目依赖了哪些第三方模块等。
使用 npm init -y 命令生成。

{
  "name": "gulp-test",
  "version": "1.0.0",
  "main": "gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-csso": "^4.0.1",
    "gulp-file-include": "^2.2.2",
    "gulp-htmlmin": "^5.0.1",
    "gulp-less": "^4.0.1",
    "gulp-uglify": "^3.0.2"
  },
  "dependencies": {
    "formidable": "^1.2.2"
  },
  "keywords": [],
  "description": ""
}
4.3 项目依赖

在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖
使用 npm install 包名命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中

npm install <modules name>
4.4 开发依赖

在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖
使用 npm install 包名 --save-dev 命令将包添加到 package.json 文件的 devDependencies 字段中

npm install <modules name> --save--dev
npm install <modules name> -D	#简写
4.5 package-lock.json 文件的作用

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

5 模块查找规则

5.1 当模块拥有路径但没有后缀时
require('./find');
  • require 方法根据模块路径查找模块,如果是完整路径,直接引入模块。
  • 如果模块后缀省略,先找同名 JS 文件再找同名 JS 文件夹
  • 如果找到了同名文件夹,找文件夹中的 index.js
  • 如果文件夹中没有 index.js 就会去当前文件夹中的 package.json 文件中查找 main 选项中的入口文件
  • 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到
5.2 当模块没有路径且没有后缀时
require('find');
  • Node.js 会假设它是系统模块
  • Node.js 会去 node_modules 文件夹中
  • 首先看是否有该名字的 JS 文件
  • 再看是否有该名字的文件夹
  • 如果是文件夹看里面是否有 index.js
  • 如果没有 index.js 查看该文件夹中的 package.json 中的 main 选项确定模块入口文件
    否则找不到报错

Node.js 与 Web

1. 服务器端基础概念

1.1 网站的组成

网站应用程序主要分为两大部分:客户端和服务器端。
客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用 HTML、CSS、JavaScript 构建。
服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。

1.2 Node 网站服务器

能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应。

1.3 IP 地址

互联网中设备的唯一标识。
IP 是 Internet Protocol Address 的简写,代表互联网协议地址.

1.4 域名

由于 IP 地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。
http://www.itheima.com => http://124.165.219.100/
虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为 ip 才能访问到指定的网站服务器。

1.5 端口

端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。

1.6 URL

统一资源定位符,又叫 URL(Uniform Resource Locator),是专为标识 Internet 网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是 URL。

2. 创建 web 服务器

2.1 创建 web 服务器
// 引入http模块
const http = require("http");
// 创建web服务器对象
const app = http.createServer();
// 当客户端发送请求的时候
app.on("request", (req, res) => {
  // 响应
  res.end("<h1>hi,user</hi>");
});
// 监听3000端口
app.listen(3000);
console.log("服务器已启动,监听3000端口,请访问\nlocalhost:3000");

3. HTTP 协议

3.1 HTTP 协议的概念

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

3.2 报文

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

3.3 请求报文
  1. 请求方式 (Request Method)

    • GET 请求数据
    • POST 发送数据
    app.on("request", (req, res) => {
      if (req.method == "POST") {
        res.end("post");
      } else if (req.method == "GET") {
        res.end("get");
      }
    });
    
  2. 请求地址 (Request URL)

app.on("request", (req, res) => {
  console.log(req.headers["accept"]); // 获取请求报文
  console.log(req.url); // 获取请求地址
});
3.4 响应报文
  1. HTTP 状态码
  2. 内容类型
app.on('request', (req, res) => {
	// 设置响应报文
    res.writeHead(200, {
    'Content-Type': 'text/html;charset=utf8‘
    });
    if (req.url == "/index" || req.url == "/") {
    res.end("<h2>欢迎来到首页</h2>");
    } else if (req.url == "/list") {
    res.end("welcome to listpage");
    } else {
    res.end("not found");
    }
});

4. HTTP 请求与响应处理

4.1 请求参数

客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。

4.2 GET 请求参数

参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20
参数获取需要借助系统模块 url,url 模块用来处理 url 地址

// 引入http模块
const http = require("http");
// 引入url模块——url模块专用解析方法url.parse()
const url = require("url");
// 创建web服务器对象
const app = http.createServer();
app.on("request", (req, res) => {
  // 请求参数
  console.log(req.url);
  // 解析查询参数{采用对象的解构赋值}
  let { query, pathname } = url.parse(req.url, true);
  console.log(query.name);
  console.log(query.age);
  console.log(pathname);
});
app.listen(3000);
4.3 POST 请求参数

参数被放置在请求体中进行传输
获取 POST 参数需要使用 data 事件和 end 事件
使用 querystring 系统模块将参数转换为对象格式

// 引入http模块
const http = require("http");
// 引入字符串处理模块
const querystring = require("querystring");
// 创建web服务器对象
const app = http.createServer();
app.on("request", (req, res) => {
  let postParams = "";
  //数据传输时
  req.on("data", (params) => {
    postParams += params;
  });
  //数据传完时
  req.on("end", () => {
    console.log(querystring.parse(postParams));
  });
  res.end("ok");
});
4.4 路由

路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。

// 当客户端发来请求的时候
app.on("request", (req, res) => {
  // 1.获取请求方式
  const method = req.method.toLowerCase();
  // 2.获取请求地址
  const { pathname } = url.parse(req.url);
  // 3.设置响应报文
  res.writeHead(200, {
    "content-type": "text/html;charset=utf-8",
  });
  // 根据请求方式和地址响应资源
  if (method == "get") {
    if (pathname == "/" || pathname == "/index") {
      res.end("欢迎来到首页");
    } else if (pathname == "/list") {
      res.end("欢迎来到列表");
    } else {
      res.end("您访问的页面不存在");
    }
  } else if (method == "post") {
    res.end("登录成功");
  }
});
4.5 静态资源

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

// 引入http模块
const http = require("http");
// 引入相关模块
const url = require("url");
const path = require("path");
const fs = require("fs");
const mime = require("mime");
// 创建web服务器对象
const app = http.createServer();
// 当客户端发送请求的时候
app.on("request", (req, res) => {
  // 1.获取用户的请求路径
  let { pathname } = url.parse(req.url);
  // 2.把默认参数/指向首页
  pathname = pathname == "/" ? "/default.html" : pathname;
  // 3.转换为资源所在的绝对路径
  let realPath = path.join(__dirname + "/public" + pathname);
  // 4.记录资源文件类型
  let type = mime.getType(realPath);
  // 5.调用fs模块读取文件,并返回静态资源
  fs.readFile(realPath, (error, result) => {
    // res.end(realPath);
    if (error != null) {
      res.writeHead(404, {
        "content-type": "text/html;charset=utf-8",
      });
      res.end("文件读取失败");
      return;
    }
    res.writeHead(200, {
      "content-type": type,
    });
    res.end(result);
  });
});
// 监听3000端口
app.listen(3000);
// 提示服务已经开启
console.log("服务器已启动,监听3000端口,请访问localhost:3000");
4.6 动态资源

相同的请求地址不同的响应资源,这种资源就是动态资源。

4.7 客户端请求途径
  1. GET 方式

    浏览器地址栏
    link 标签的 href 属性
    script 标签的 src 属性
    img 标签的 src 属性
    Form 表单提交

  2. POST 方式

    Form 表单提交

5. Node.js 异步编程

5.1 同步 API, 异步 API 的区别 (执行顺序)

同步 API:只有当前 API 执行完成后,才能继续执行下一个 API

// 同步任务
for (let i = 0; i < 1000; i++) {
  console.log(i);
}
console.log("for循环后的代码");
// 1-999
// for循环后的代码

异步 API:当前 API 的执行不会阻塞后续代码的执行

console.log("before");
setTimeout(() => {
  console.log("last");
}, 0);
console.log("after");
//before
//after
//last

5.2 同步 API, 异步 API 的区别 (获取返回值)
// 同步
function sum(n1, n2) {
  return n1 + n2;
}
const result = sum(10, 20);
// 异步
function getMsg() {
  setTimeout(function () {
    return { msg: "Hello Node.js" };
  }, 2000);
}
const msg = getMsg();

5.3 回调函数

自己定义函数让别人去调用。

function getData(callback) {
  callback("123");
}
getData(function (n) {
  console.log(n);
  console.log("callback函数被调用了");
}); //调用getData函数,传递参数是一个回调函数
// 123
// callback函数被调用了

5.4 使用回调函数获取异步 API 执行结果
function getMsg(callback) {
  setTimeout(function () {
    callback({ msg: "Hello Node.js" });
  }, 2000);
}
getMsg(function (msg) {
  console.log(msg);
});

5.5 Node.js 中的异步 API
//Node.js中的异步API
// 1.文件读写
false.readFile("./demo.txt", callback);
// 2.事件监听
var server = http.cteateServer();
// 事件处理函数就是回调函数
server.on("request", callback);
// 异步API之间的相互依赖

5.6 回调地狱

当要求异步任务之间有先后执行顺序的时候,对异步任务里的回调函数进行嵌套,会发生可怕的回调地狱问题,导致程序极难维护。

// 回调地狱问题
const fs = require("fs");
fs.readFile("./a.txt", "utf-8", (err, result1) => {
  if (err != null) {
    console.log("读取失败");
  }
  console.log(result1);
  fs.readFile("./b.txt", "utf-8", (err, result2) => {
    console.log(result2);
    fs.readFile("./c.txt", "utf-8", (err, result3) => {
      console.log(result3);
    });
  });
});

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

  • Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值。

new Promise( function(resolve, reject) {...} /* executor */  );

  • executor 是带有 resolvereject 两个参数的函数 。Promise 构造函数执行时立即调用executor 函数, resolvereject 两个函数作为参数传递给executor(executor 函数在 Promise 构造函数返回所建 promise 实例对象前被调用)。
const fs = require("fs");
// 1.将每个异步API都用一个Promise对象进行包裹,并传递两个参数
// 2.再把Promise对象中的两个参数返回给最外层的function
function p1() {
  return new Promise((resolve, reject) => {
    fs.readFile("./a.txt", "utf-8", (err, result1) => {
      if (err != null) {
        reject("读取失败");
      } else {
        resolve(result1);
      }
    });
  });
}
function p2() {
  return new Promise((resolve, reject) => {
    fs.readFile("./b.txt", "utf-8", (err, result1) => {
      if (err != null) {
        reject("读取失败");
      } else {
        resolve(result1);
      }
    });
  });
}
// 链式调用
p1() //调用 p1()
  .then((r1) => {
    console.log(r1);
    return p2(); //成功则return p2()并执行
  })
  .then((r2) => {
    console.log(r2);
  });

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

  • async 关键字

  1. 普通函数定义前加 async 关键字 普通函数变成异步函数

  2. 异步函数默认返回 promise 对象

  3. 在异步函数内部使用 return 关键字进行结果返回 结果会被包裹的 promise 对象中 return 关键字代替了 resolve 方法

  4. 在异步函数内部使用 throw 关键字抛出程序异常

  5. 调用异步函数再链式调用 then 方法获取异步函数执行结果

  6. 调用异步函数再链式调用 catch 方法获取异步函数执行的错误信息

  • await 关键字
  1. await 关键字只能出现在异步函数中

  2. await promise 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);
}
console.log(run());
//Promise { <pending> } 这个打印是同步任务,最先打印
// p1
// p2
// p3

5.9 promisify 方法
  • promisify()方法 对异步 API 进行包装,使其返回 promise 对象,需要引入 util 模块
const fs = require("fs");
// promisify()方法 对异步API进行包装,使其返回promise对象,需要引入util模块
const promisify = require("util").promisify;
// 此时readFile方法将返回一个promise对象
const readFile = promisify(fs.readFile);
// 定义异步任务
async function run() {
  //获取返回值
  let r1 = await readFile("./a.txt", "utf-8");
  let r2 = await readFile("./b.txt", "utf-8");
  let r3 = await readFile("./c.txt", "utf-8");
  console.log(r1);
  console.log(r2);
  console.log(r3);
}
// 调用异步任务
run();

MongoDB数据库

1. 数据库概述及环境搭建

1.1 为什么要使用数据库
  • 动态网站中的数据都是存储在数据库中的
  • 数据库可以用来持久存储客户端通过表单收集的用户信息
  • 数据库软件本身可以对数据进行高效的管理
1.2 什么是数据库
  • 数据库即存储数据的仓库,可以将数据进行有序的分门别类的存储。它是独立于语言之外的软件,可以通过数据库软件提供的API去操作数据。
  • 常见的数据库软件有:mysql、mongoDB、oracle。
1.3 MongoDB数据库下载安装

下载地址:https://www.mongodb.com/download-center/community

1.4 MongoDB可视化软件

MongoDB Compass 可视化操作软件,是使用图形界面操作数据库的一种方式。

1.5 数据库相关概念

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

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

使用Node.js操作MongoDB数据库需要依赖Node.js第三方包mongoose

$npm install mongoose -g
1.7 启动MongoDB

在命令行工具中运行下面的命令即可启动MongoDB,否则MongoDB将无法连接。

$net start mongodb
1.8 数据库连接

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

mongoose.connect('mongodb://localhost/playground', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
    .then(() => console.log('数据库连接成功'))
    .catch(err => console.log('数据库连接失败', err));
1.9 创建数据库

在MongoDB中不需要显式创建数据库,如果正在使用的数据库不存在,MongoDB会自动创建。

2. MongoDB增删改查操作

2.1 创建集合

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

// 1.创建集合规则
const courseSchema = new mongoose.Schema({
  name: String,
  author: String,
  isPublished: Boolean,
});
// 2.创建符合集合规则的构造函数
const Course = mongoose.model("Course", courseSchema); //courses
2.2 创建文档

创建文档实际上就是向集合中插入数据。分为两步:

  1. 应用集合规则创建集合实例。
  2. 调用实例对象下的save()方法将数据保存到数据库中。
// 创建集合实例
const course = new Course({
  name: "Node.js course",
  author: "itheima",
  tags: ["node", "backend"],
  isPublished: true,
});
// 将数据保存到数据库中
course.save();

也可使用集合构造函数本身的create方法创建文档

  • create(object,callback) 回调函数形式
Course.create(
  {
    name: "javascript基础",
    author: "pink",
    isPublished: false,
  },
  (err, result) => {
    console.log(err);
    console.log(result);
  }
);
  • create(object).then().catch() promise对象形式
Course.create({
  name: "javascript应用",
  author: "pink",
  isPublished: true,
})
  .then((doc) => console.log(doc))
  .catch((err) => console.log(err));
2.3 mongoDB数据库导入数据

找到mongodb数据库的安装目录,将安装目录下的bin目录放置在环境变量中。

$mongoimport –d 数据库名称 –c 集合名称 –file 要导入的数据文件
2.4 查询文档
//  根据条件查找文档(条件为空则查找所有文档)
Course.find().then(result => console.log(result))
//	通过字段查找
Course.findOne({name: 'node.js基础'}).then(result => console.log(result))
//  匹配大于 小于
User.find({age: {$gt: 20, $lt: 50}}).then(result => console.log(result))
//  匹配包含
User.find({hobbies: {$in: ['敲代码']}}).then(result => console.log(result))
//  选择要查询的字段  
User.find().select('name email').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))
2.5 删除文档
 // 删除单个
Course.findOneAndDelete({_id: "5c09f267aeb04b22f8460968"}).then(result => console.log(result))
 // 删除多个
User.deleteMany({}).then(result => console.log(result))
2.6 更新文档
// 更新单个
User.updateOne({查询条件}, {要修改的值}).then(result => console.log(result))
// 更新多个
User.updateMany({查询条件}, {要更改的值}).then(result => console.log(result))
2.7 mongoose验证

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

required: true 必传字段
minlength:3 字符串最小长度
maxlength: 20 字符串最大长度
min: 2 数值最小为2
max: 100 数值最大为100
enum: ['html', 'css', 'javascript', 'node.js']
trim: true 去除字符串两边的空格
validate: 自定义验证器
default: 默认值
//定义集合规则
const postSchema = new mongoose.Schema({
  // 定义验证规则
  title: {
    type: String,
    // 此字段是否必须,必须
    required: [true, '\n请传入文章标题'],
    minlength: [2, '\n文章长度不能小于2'],
    maxlength: [5, '\n文章长度不能超过5'],
    // 去除字符串两边的空格
    trim: true,
  },
  age: {
    type: Number,
    min: 18,
    max: 100,
  },
  publishData: {
    type: Date,
    // 默认值
    default: Date.now,
  },
  category: {
    type: String,
    // 枚举,列出当前字段可以拥有的值
    enum: {
      values: ['html', 'css', 'javascript', 'node.js'],
      message: '\n传入的分类名称在列表中不存在',
    },
  },
  author: {
    type: String,
    // 自定义规则
    validate: {
      validator: (v) => {
        // 返回布尔值
        return v && v.length > 4;
      },
      // 自定义错误信息
      message: '\n传入的值不符合验证规则',
    },
  },
});
// 创建集合
const Post = mongoose.model('Post', postSchema); //Posts

获取错误信息:error.errors[‘字段名称’].message

Post.create({ title: 'aa', age: 60, category: 'htm', author: 'pik' })
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    // 获取错误信息对象
    const erro = error.errors;
    // 循环错误信息对象
    for (attr in erro) {
      console.log(erro[attr]['message']);
    }
  });
2.7 集合关联

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

使用id对集合进行关联
使用populate方法进行关联集合查询

// 定义用户集合规则
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
});
// 定义文章集合规则
const postSchema = new mongoose.Schema({
  title: {
    type: String,
  },
  author: {
   // 使用ID将文章集合和作者集合进行关联
    type: mongoose.Schema.Types.ObjectId,
    ref: "User",
  },
});
// 创建用户集合
const User = mongoose.model("User", userSchema);
// 创建文章集合
const Post = mongoose.model("Post", postSchema);
//联合查询
Post.find()
      .populate('author')
      .then((err, result) => console.log(result));

模板引擎artTemplate

1. 模板引擎的基础概念

1.1 模板引擎

模板引擎是第三方模块。
让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护。

 <!-- 使用模板引擎的写法 --> 
 <ul>
    {{each ary}}
        <li>{{$value.name}}</li>
        <li>{{$value.age}}</li>
    {{/each}}
 </ul>
1.2 art-template模板引擎

在命令行工具中使用 npm install art-template 命令进行下载
使用const template = require(‘art-template’)引入模板引擎
告诉模板引擎要拼接的数据和模板在哪 const html = template(‘模板路径’, 数据);
使用模板语法告诉模板引擎,模板与数据应该如何进行拼接

1.3 art-template代码示例
 // 导入模板引擎模块
 const template = require('art-template');
 // 将特定模板与特定数据进行拼接
 const html = template('./views/index.art',{
    data: {
        name: '张三',
        age: 20
    }
 }); 
./views/index.art
<div>
    <span>{{data.name}}</span>
    <span>{{data.age}}</span>
 </div>

2. 模板引擎语法

2.1 模板语法

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

2.2 输出

将某项数据输出在模板中,标准语法和原始语法如下:

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

<!-- 标准语法 -->
<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.3 原文输出

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

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

<!-- 标准语法 -->
<h2>{{@ value }}</h2>
<!-- 原始语法 -->
<h2><%- value %></h2>

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

2.5 循环

标准语法:{{each 数据}} {{/each}}
原始语法:<% for() { %> <% } %>

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

2.6 子模版

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

标准语法:{{include ‘模板’}}
原始语法:<%include(‘模板’) %>

<!-- 标准语法 -->
{{include './header.art'}}
<!-- 原始语法 -->
<% include('./header.art') %>

2.7 模板继承

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

2.8 模板继承示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    {{block 'head'}}{{/block}}
</head>
<body>
    {{block 'body'}}{{/block}}
    {{block 'time'}}{{/block}}
</body>
</html>
<!--index.art 首页模板-->
{{extend './layout.art'}}
{{block 'head'}} <link rel="stylesheet" href="custom.css"> {{/block}}
{{block 'content'}} <p>This is just an awesome page.</p> {{/block}}
<!-- 子模板可以嵌套进去 -->
{{block 'time'}}{{include './common/date.art'}}{{/block}}

2.9 模板配置

向模板中导入变量 template.defaults.imports.变量名 = 变量值;
设置模板根目录 template.defaults.root = 模板目录
设置模板默认后缀 template.defaults.extname = ‘.art’

// 日期格式化模块
const dateformat = require('dateformat');
// 给模板引擎导入日期格式模块
template.defaults.imports.dateformat = dateformat;
// 设置模板根目录
template.defaults.root = path.join(__dirname, 'views');
// 设置默认模板后缀
template.defaults.extname = '.art';

3. 案例

3.1 案例介绍 – 学生档案管理

目标:模板引擎应用,强化node.js项目制作流程。

知识点:http请求响应、数据库、模板引擎、静态资源访问。

3.2 制作流程

建立项目文件夹并生成项目描述文件

创建网站服务器实现客户端和服务器端通信

连接数据库并根据需求设计学员信息表

创建路由并实现页面模板呈递

实现静态资源访问

实现学生信息添加功能

实现学生信息展示功能

$npm ls -g --depth=0 		#列出全局安装的第三方模块
$npm install -g treer 		#安装treer第三方模块
$treer -i "node_modules" 	#排除node_modules文件夹,生成目录
├─app.js 主服务
├─package-lock.json 依赖锁定文件
├─package.json 依赖描述文件
├─x.txt 项目描述文件
├─views 视图模板
|   ├─index.art
|   └-list.art
├─route 路由
|   └index.js
├─public 静态资源
|   ├─css
|   |  ├─list.css
|   |  └-main.css
├─model 模块
|   ├─connect.js
|   └-student.js
//connect.js
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost/playground', { useNewUrlParser: true })
	.then(() => console.log('数据库连接成功'))
	.catch(() => console.log('数据库连接失败'));

//students.js
const mongoose = require('mongoose');
// 创建学生集合规则
const studentsSchema = new mongoose.Schema({
	name: {
		type: String,
		required: true,
		minlength: 2,
		maxlength: 10
	},
	age: {
		type: Number,
		min: 10,
		max: 50
	},
	sex: {
		type: String
	},
	email: String,
	hobbies: [String],
	collage: String,
	enterDate: {
		type: Date,
		default: Date.now
	}
});
// 创建学生信息集合
const Student = mongoose.model('Student', studentsSchema);
// 将学生信息集合进行导出
module.exports.Student = Student;

// app.js
// 引入http模块
const http = require('http');
// 引入模板引擎;
const template = require('art-template');
// 引入path模块;
const path = require('path');
// 引入静态资源访问模块
const serveStatic = require('serve-static');
// 引入处理日期的第三方模块
const dateformat = require('dateformat');
// 配置template的根目录
template.defaults.root = path.join(__dirname, 'views');
// 给模板引擎导入日期格式模块
template.defaults.imports.dateformat = dateformat;
// 创建网站服务器
const app = http.createServer();
// 引入路由功能模块
const router = require('./route/index.js');
// 引入数据库连接模块;
require('./model/connect.js');
// 实现静态资源访问服务
const serve = serveStatic(path.join(__dirname, 'public'));
// 当客户端访问服务器端的时候
app.on('request', (req, res) => {
	// 启用路由功能
	router(req, res, () => { });
	// 启用静态资源访问服务功能
	serve(req, res, () => { });
});
// 监听80端口
app.listen(80);
console.log('服务器启动成功');

3.3 第三方模块 router

功能:实现路由
使用步骤:
获取路由对象
调用路由对象提供的方法创建路由
启用路由,使路由生效

// route.js
// 引入router模块
const getRouter = require('router');
// 获取路由对象
const router = getRouter();
// 引入模板引擎
const template = require('art-template');
// 引入querystring模块
const querystring = require('querystring');
// 引入用户集合文件
const Student_model = require('../model/student');
const Student = Student_model.Student;
// 引入url模块
const url = require('url');
// 呈现默认页提示信息
router.get('/', (req, res) => {
	let html = `
	<p>您来到了互联网的荒原:</p>
	<p>/add 	学生档案信息添加页面</p>
	<p>/list 	学生档案信息列表页面</p>
	`;
	res.writeHead(200, {
		"content-type": "text/html;charset=utf-8"
	});
	res.end(html);
});
// 呈递学生档案信息添加页面
router.get('/add', (req, res) => {
	let html = template('index.art', {});
	res.end(html);
});

// 呈递学生档案信息列表页面
router.get('/list', async (req, res) => {
	// 查询学生信息
	let students = await Student.find();
	let html = template('list.art', {
		students: students
	});
	res.end(html);
});
// 实现学生信息删除功能路由
router.get('/remove', async (req, res) => {
	let { query } = url.parse(req.url, true);
	let temp = query.id.replace(/"|'/g, "");
	await Student.findOneAndDelete({ _id: temp });
	res.writeHead(301, {
		Location: '/list'
	});
	res.end();
});
// 实现学生信息添加功能路由
router.post('/add', (req, res) => {
	// 接收post请求参数
	let formData = '';
	req.on('data', param => {
		formData += param;
	});
	req.on('end', async () => {
		// 将用户的提交信息解析为对象结构
		let student = querystring.parse(formData);
		// 如果未设置用户名则不添加数据
		if (student.name == '') {
			res.writeHead(400, {
				"content-type": "text/html;charset=utf-8",
			});
			res.end('请填写用户名');
		}
		if (student.name) {
			await Student.create(student);
			res.writeHead(301, {
				Location: '/list'
			});
			res.end();
		}
	});
});
module.exports = router;

3.4 第三方模块 serve-static

功能:实现静态资源访问服务
步骤:
引入serve-static模块获取创建静态资源服务功能的方法
调用方法创建静态资源服务并指定静态资源服务目录
启用静态资源服务功能

const serveStatic = require('serve-static')
const serve = serveStatic('public')
server.on('request', () => { 
    serve(req, res)
})
server.listen(3000);

3.5 添加学生信息功能步骤分析

在模板的表单中指定请求地址与请求方式
为每一个表单项添加name属性
添加实现学生信息功能路由
接收客户端传递过来的学生信息
将学生信息添加到数据库中
将页面重定向到学生信息列表页面

3.6 学生信息列表页面分析

从数据库中将所有的学生信息查询出来
通过模板引擎将学生信息和HTML模板进行拼接
将拼接好的HTML模板响应给客户端

Express框架

1. Express框架简介及初体验

1.1 Express框架是什么

Express是一个基于Node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种Web应用。我们可以使用 npm install express 命令进行下载。

1.2 Express框架特性

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

1.3 Express框架路由
// 当客户端以get方式访问/时
app.get('/', (req, res) => {
    // 对客户端做出响应
    res.send('Hello Express');
});
// 当客户端以post方式访问/add路由时
app.post('/add', (req, res) => {
    res.send('使用post方式请求了/add路由');
});

1.4 Express框架获取请求参数
app.get('/', (req, res) => {
    // 获取GET参数
    console.log(req.query);
});

app.post('/', (req, res) => {
    // 获取POST参数
    console.log(req.body);
}) 

1.5 Express初体验

使用Express框架创建web服务器及其简单,调用express模块返回的函数即可。

// 引入Express框架
const express = require('express');
// 使用框架创建web服务器
const app = express();
// 当客户端以get方式访问/路由时
app.get('/', (req, res) => {
    // 对客户端做出响应 send方法会根据内容的类型自动设置请求头
    res.send('Hello Express'); // <h2>Hello Express</h2> {say: 'hello'}
});
// 程序监听3000端口
app.listen(3000);

2. 中间件

2.1 什么是中间件

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

中间件主要由两部分构成,中间件方法以及请求处理函数。
中间件方法由Express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求。

 app.get('请求路径', '处理函数')   // 接收并处理get请求
 app.post('请求路径', '处理函数')  // 接收并处理post请求

可以针对同一个请求设置多个中间件,对同一个请求进行多次处理。
默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。
可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件。

 app.get('/request', (req, res, next) => {
     req.name = "张三";
     next();
 });
 app.get('/request', (req, res) => {
     res.send(req.name);
 });

2.2 app.use中间件用法

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

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

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

app.use('/admin', (req, res, next) => {
    console.log(req.url);
    next();
});

2.3 中间件应用

路由保护,客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。
网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。
自定义404页面

2.4 错误处理中间件

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

app.use((err, req, res, next) => {
    res.status(500).send('服务器发生未知错误');
})

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

app.get("/", (req, res, next) => {
fs.readFile("/file-does-not-exist", (err, data) => {
if (err) {
next(err);
}
});
});


2.5 捕获错误

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

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

app.get("/", async (req, res, next) => {
    try {
        await User.find({name: '张三'})
    }catch(ex) {
        next(ex);
    }
});

3. Express请求处理

3.1 构建模块化路由
 // home.js
 const home = express.Router(); 
 home.get('/index', () => {
     res.send('欢迎来到博客展示页面');
 });
 module.exports = home;

 // admin.js
 const home = express.Router(); 
 home.get('/index', () => {
     res.send('欢迎来到博客列表页面');
 });
 module.exports = list;

 // app.js
 const home = require('./route/home.js');
 const admin = require('./route/admin.js');
 app.use('/home', home);
 app.use('/admin', admin);

3.2 GET参数的获取

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

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

3.3 POST参数的获取

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

 // 引入body-parser模块
 const bodyParser = require('body-parser');
 // 配置body-parser模块
 app.use(bodyParser.urlencoded({ extended: false }));
 // 接收请求
 app.post('/add', (req, res) => {
    // 接收请求参数
    console.log(req.body);
 }) 
3.4 Express路由参数
 app.get('/find/:id', (req, res) => { 
     console.log(req.params); // {id: 123} 
 });
3.5 静态资源的处理

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

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

4. express-art-template模板引擎

4.1 express-art-template

为了使art-template模板引擎能够更好的和Express框架配合,模板引擎官方在原art-template模板引擎的基础上封装了express-art-template。
使用npm install art-template express-art-template命令进行安装。

  // 当渲染后缀为art的模板时 使用express-art-template
 app.engine('art', require('express-art-template'));
  // 设置模板存放目录
 app.set('views', path.join(__dirname, 'views'));
  // 渲染模板时不写后缀 默认拼接art后缀
 app.set('view engine', 'art');
 app.get('/', (req, res) => {
     // 渲染模板
     res.render('index');
 }); 
4.2 app.locals 对象

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

app.locals.users = [{
    name: '张三',
    age: 20
},{
    name: '李四',
    age: 20
}]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值