---
url: /notes/前端/前后端交互/index.md
---
## Node基础

### node安装

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

* 浏览器（软件）能够运行JavaScript代码，浏览器就是JavaScript代码的运行环境
* Node（软件）能够运行JavaScript代码，Node就是JavaScript代码的运行环境

**官网**

https://nodejs.org/en/

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

![image-20220420142628981](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201426024.png)

**node环境安装失败方法**

![image-20220420142718430](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201427471.png)

![image-20220420142735735](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201427786.png)

![image-20220420142748587](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201427637.png)

### node.js的组成

![image-20220420142826895](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201428937.png)

### node.js基础语法

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

在Node环境下执行代码，使用Node命令执行后缀为\*\*.js\*\*的文件即可

在当前文件夹 \*\*shift+\*\***鼠标右键** powershell自动处于当前文件夹

**shift+tab** 补全

**clear** 清除

### **Node.js全局对象global**

在**浏览器**中全局对象是**window**，在**Node**中全局对象是**global**

Node中全局对象下有以下方法，可以在任何地方使用，global可以省略。

* console.log()   在控制台中输出
* setTimeout()   设置超时定时器
* clearTimeout() 清除超时时定时器
* setInterval()   设置间歇定时器
* clearInterval()  清除间歇定时器

### node.js模块化开发

**javascript开发弊端**

![image-20220420142946838](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201429881.png)

![image-20220420142957990](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201429032.png)

**规范**

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

![image-20220420143032115](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201430154.png)

**模块成员导出**

```js
（1）
// 在模块内部定义变量
let version = 14.01;
// 在模块内部定义方法
const name = (name) => {
    console.log(`Hello,${name}`);
};
// 向模块外部导出数据
exports.version = version;
exports.name = name;

（2）另一种方式
module.exports.version = version;
module.exports.name = name;
```

`exports`是`module.exports`的**别名**（地址引用关系），导出对象最终以**module.exports**为准

**模块成员的导入**

```js
// 在t2.js模块中导入模块a
let a = require('./t1.js');
// 输出t2模块中的version变量
console.log(a.version);
// 调用t2模块中的name方法 并输出其返回值
console.log(a.name('胡梓卓'));
```

![image-20220420143156033](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201431071.png)

### 系统模块

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

![image-20220420143222387](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201432415.png)

系统模块fs 文件操作

```js
const fs = require('fs');
```

**读取文件内容**

```js
读取文件内容
fs.readFile('文件路径/文件名称' [, '文件编码'], callback);
```

```js
const fs = require('fs');
fs.readFile('./t1.js', 'utf8', (error, doc) => {
    if (error == null) {
        console.log(doc);
    } else {
        console.log(err);
    }
})
```

**写入文件内容**

```js
写入文件内容
fs.writeFile('文件路径/文件名称' [, '文件编码'], callback);
```

```js
const fs = require('fs');
const str = '文件t1.js正在写入数据';
fs.writeFile('.t1.js', str, (error) => {
    if (error != null) {
        console.log(error);
        return;
    }
    console.log('文件写入成功');
});
```

**系统模块path 路径操作**

![image-20220420143624374](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201436414.png)

**路径拼接语法**

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

```js
// 导入path模块
const path = require('path');
// 路径拼接
let filepath = path.join('html', 'css', 'js', 'javascript');
console.log(filepath);
```

**相对路径和绝对路径**

* 大多数情况下使用**绝对路径**，因为相对路径有时候相对的是命令行工具的当前工作目录
* 在读取文件或者设置文件路径时都会选择绝对路径
* 使&#x7528;**\_\_dirname**获取当前文件所在的绝对路径

在当前目录的上一级目录执行 **node ./前端/test.js**

```js
const fs = require('fs');
const path = require('path');
console.log(__dirname);
console.log(path.join(__dirname, 't2.js'));
fs.readFile(path.join(__dirname, 't2.js'), 'utf8', (error, doc) => {
    if (error != null) {
        console.log('报错如下 ');
        console.log(error);
    } else {
        console.log('文件内容如下');
        console.log(doc);
    }
});
```

### 第三方模块

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

两种存在形式

* 以js文件的形式存在，提供实现项目具体功能的API接口。
* 以命令行工具形式存在，辅助项目开发

**获取第三方模块**

**`npmjs.com`**：第三方模块的存储和分发仓库

npm (node package manager) ： node的第三方模块管理工具

* 下载：**`npm install`** **模块名称**
* 卸载：**`npm unintall package`** **模块名称**

**全局安装与本地安装**

* 命令行工具：全局安装
* 库文件：本地安装

**nodemon**

nodemon是一个命令行工具，用以辅助项目开发。

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

使用步骤

1. 使用\*\*`npm install nodemon –g`\*\* 下载它
2. 在命令行工具中用\*\*`nodemon`\*\*命令替代node命令执行文件

**`ctrl + c 终止此操作`**

**nrm**

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

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

**使用步骤**

1. 使用\*\*`npm install nrm –g`\*\* 下载它
2. 查询可用下载地址列表 **`nrm ls`**
3. 切换npm下载地址 **`nrm use`** **下载地址名称**

![image-20220420143958204](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201439252.png)

## 第三方模块 Gulp

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

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

用机器代替手工，提高开发效率。

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

### Gulp使用

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

**Gulp提供的方法**

* gulp.**src()**：获取任务要处理的文件
* gulp.**dest()**：输出文件
* gulp.**task()**：建立gulp任务
* gulp.**watch()**：监控文件的变化

执行gulpfile.js文件，安装**npm install gulp-cli -g**命令行工具 全局安装

执行：**gulp** **任务名**

```js
//引入gulp模块
const gulp = require('gulp');
//使用gulp.task建立任务 1.任务名称 2.任务的回调函数
gulp.task('first', () => {
    console.log('第一次使用');
    //gulp.scr获取要处理的文件
    gulp.src('./src/css/base.css')
        // 将处理后的文件输出到dist目录
        .pipe(gulp.dest('dist/css'));
});
```

### Gulp插件

**下载网址：**[**npms - npm (npmjs.com)**](https://www.npmjs.com/package/npms)

**gulp-htmlmin** ：html文件压缩

```js
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
gulp.task('htmlmin', () => {
    gulp.src('./src/*.html')
        //压缩html文件中的代码
        .pipe(htmlmin({ collapseWhitespace: true }))
        .pipe(gulp.dest('dist'))
});
```

* **gulp-csso** ：压缩css

```js
const csso = require('gulp-csso');
const less = require('gulp-less');
const gulp = require('gulp');
gulp.task('cssmin', () => {
gulp.src(['./src/css/*.less', './src/css/*.css'])
        .pipe(less())
        .pipe(csso())
        .pipe(gulp.dest('dist/css'))
});
```

**gulp-babel** ：JavaScript语法转化

```js
const babel = require('gulp-babel');
const gulp = require('gulp');
gulp.task('jsmin', () => {
    gulp.src('./src/js/*.js')
        .pipe(babel({
            //判断当前代码运行环境，将代码转换为当前环境支持的代码
            presets: ['@babel/env']
        }))
        .pipe(gulp.dest('dist/js'))
})
```

**gulp-less**: less语法转化

```js
const less = require('gulp-less');
const gulp = require('gulp');
gulp.task('cssmin', () => {
    gulp.src('./src/css/*.less')
        .pipe(less())
        .pipe(gulp.dest('dist/css'))
})
```

**gulp-uglify** ：压缩混淆JavaScript

```js
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const gulp = require('gulp');
gulp.task('jsmin', () => {
    gulp.src('./src/js/*.js')
        .pipe(babel({
            //判断当前代码运行环境，将代码转换为当前环境支持的代码
            presets: ['@babel/env']
        }))
        .pipe(uglify())
        .pipe(gulp.dest('dist/js'))
});
```

**gulp-file-include** 公共文件包含

```js
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const fileinclude = require('gulp-file-include');
gulp.task('htmlmin', () => {
    gulp.src('./src/*.html')
        //抽取头部
        .pipe(fileinclude())
        //压缩html文件中的代码
        .pipe(htmlmin({ collapseWhitespace: true }))
        .pipe(gulp.dest('dist'))
});
```

**browsersync** 浏览器实时同步

```js
// 构建任务
gulp.task('default', ['htmlmin', 'cssmin', 'jsmin', 'copy']);
gulp.task('default', series(parallel('htmlmin', 'cssmin', 'jsmin', 'copy')));
```

## package.json

**node\_modules文件夹的问题**

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

**package.json文件的作用**

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

使用\*\*`npm init -y`\*\*命令生成。

**项目依赖**

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

**开发依赖**

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

### **package-lock.json文件的作用**

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

![image-20220420144707430](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201447476.png)

**`npm run build`**

## Node.js模块中的加载机制

**模块查找规则**

**当模块拥有路径但没有后缀时**

![image-20220420144752801](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201447845.png)

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

**当模块没有路径且没有后缀时**

![image-20220420145301780](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201453847.png)

1. Node.js会假设它是**系统模块**
2. Node.js会去**node\_modules**文件夹中
3. 首先看是否有该名字的JS文件
4. 再看是否有该名字的文件夹
5. 如果是文件夹看里面是否有index.js
6. 如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件
7. 否则找不到报错

## 服务器端基础概念

URL 统一资源定位符

![image-20220420145438124](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201454183.png)

## 创建Web服务器

```js
// 创建网站服务器的模块
const http = require('http');
// app对象 网站服务器对象
const app = http.createServer();
//客户端 请求
app.on('request', (req, res) => {
    res.writeHead('200', {
        'content-type': 'text/plain;charset=utf8'
    });
    if (req.url == '/index' || req.url == '/') {
        res.end('首页');
    } else if (req.url == '/list') {
        res.end('列表页');
    } else {
        res.end('无此页面 ');
    }
    /*
        if (req.method == 'POST') {
            res.end('post请求处理');
        } else if (req.method == 'GET') {
            res.end('get请求处理');
        }
        */
});
app.listen(5000);
console.log('网站服务器启动成功，监听5000端口');
```

## HTTP协议

**概念**

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

![image-20220420145539191](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201455237.png)

**报文**

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

![image-20220420145559443](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201455504.png)

**请求报文**

```js
1. 请求方式 （Request Method）
	§ GET     请求数据
	§ POST   发送数据
2. 请求地址 （Request URL）
 app.on('request', (req, res) => {
     req.headers  // 获取请求报文
     req.url      // 获取请求地址
     req.method   // 获取请求方法
 });
```

**响应报文**

**1.** **HTTP状态码**

* 200     请求成功
* 404     请求的资源没有被找到
* 500     服务器端错误
* 400     客户端请求有语法错误

**2.** **内容类型**

* text/html
* text/css
* application/javascript
* image/jpeg
* application/json

```js
app.on('request', (req, res) => {
     // 设置响应报文
     res.writeHead(200, {
         'Content-Type': 'text/html;charset=utf8‘
     });
 });
```

## HTTP请求与响应处理

**请求参数**

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

### GET请求参数

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

```js
const http = require('http');
 // 导入url系统模块 用于处理url地址
 const url = require('url');
 const app = http.createServer();
 app.on('request', (req, res) => {
     // 将url路径的各个部分解析出来并返回对象
         // true 代表将参数解析为对象格式
     let {query} = url.parse(req.url, true);
     console.log(query);
 });
 app.listen(3000);
```

### POST请求参数

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

```js
// 导入系统模块querystring 用于将HTTP参数转换为对象格式
 const querystring = require('querystring');
 app.on('request', (req, res) => {
     let postData = '';
     // 监听参数传输事件
     req.on('data', (chunk) => postData += chunk;);
     // 监听参数传输完毕事件
     req.on('end', () => { 
         console.log(querystring.parse(postData)); 
     }); 
 });
```

### **路由**

![image-20220420145829272](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201458339.png)

```js
// 当客户端发来请求的时候
 app.on('request', (req, res) => {
     // 获取客户端的请求路径
     let { pathname } = url.parse(req.url);
     if (pathname == '/' || pathname == '/index') {
         res.end('欢迎来到首页');
     } else if (pathname == '/list') {
         res.end('欢迎来到列表页页');
     } else {
        res.end('抱歉, 您访问的页面出游了');
     }
 });
```

### **静态资源**

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

```js
// 创建网站服务器的模块
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
// app对象 网站服务器对象
const app = http.createServer();
//客户端 请求
app.on('request', (req, res) => {
    let pathname = url.parse(req.url).pathname;
    pathname = pathname == '/' ? 'default.html' : pathname;
    let realPath = path.join(__dirname, 'public', pathname);
    let type = mime.getType(realPath);
    fs.readFile(realPath, (error, result) => {
        if (error != null) {
            res.writeHead(404, {
                'content-type': 'text/html;charset=utf8'
            })
            res.end('文件读取失败');
            return;
        }
        res.writeHead(200, {
            'content-type': type
        })
        res.end(result);
    });
});
app.listen(5000);
console.log('网站服务器启动成功，监听5000端口');
```

### **动态资源**

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

http://www.itcast.cn/article?id=1

http://www.itcast.cn/article?id=2

### **客户端请求途径**

**1. GET方式**

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

**2. POST方式**

* Form表单提交

![image-20220420150013257](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201500308.png)

## Node.js异步编程

### 同步API, 异步API

```js
// 路径拼接
 const public = path.join(__dirname, 'public');
 // 请求地址解析
 const urlObj = url.parse(req.url);
 // 读取文件
 fs.readFile('./demo.txt', 'utf8', (err, result) => {
     console.log(result);
 });
```

**同步API**：只有当前API**执行完成后，才能继续执行下一个API**

```js
console.log('before'); 
console.log('after');
```

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

```js
console.log('before');
setTimeout(
   () => { console.log('last');
}, 2000);
console.log('after');
```

![image-20220420150159112](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201501167.png)

**同步API, 异步API的区别（ 获取返回值** **）**

同步API可以**从返回值**中拿到API执行的**结果**, 但是异步API是不可以的

```js
  // 同步
  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 ();
```

**回调函数**

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

```js
// getData函数定义
 function getData (callback) {}
  // getData函数调用
 getData (() => {});
```

**使用回调函数获取异步API执行结果**

```js
function getMsg (callback) {
    setTimeout(function () {
        callback ({ msg: 'Hello Node.js' })
    }, 2000);
}
getMsg (function (msg) { 
    console.log(msg);
});
```

**同步API, 异步API的区别（代码执行顺序）**

**同步API**从上到下依次执行，前面代码会阻塞后面代码的执行

```js
for (var i = 0; i < 100000; i++) { 
    console.log(i);
}
console.log('for循环后面的代码');
```

**异步API**不会等待API执行完成后再向下执行代码

```js
console.log('代码开始执行'); 
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0); 
console.log('代码结束执行');
```

**代码执行顺序分析**

```js
console.log('代码开始执行');
setTimeout(() => {
    console.log('2秒后执行的代码');
}, 2000); 
setTimeout(() => {
    console.log('"0秒"后执行的代码');
}, 0);
console.log('代码结束执行');
```

![image-20220420150522314](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201505381.png)

**Node.js中的异步API**

```js
fs.readFile('./demo.txt', (err, result) => {});

 var server = http.createServer();
 server.on('request', (req, res) => {});
如果异步API后面代码的执行依赖当前异步API的执行结果，但实际上后续代码在执行的时候异步API还没有返回结果，这个问题要怎么解决呢？
fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');
```

需求：依次读取A文件、B文件、C文件

```js
const fs = require('fs');
fs.readFile('./t1.js', 'utf8', (err, result) => {
    console.log(result)
    fs.readFile('./t2.js', 'utf8', (err, result) => {
        console.log(result)
        fs.readFile('./gulp/gulpfile.js', 'utf8', (err, result) => {
            console.log(result)
        })
    })
});
```

### Promise

```js
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '张三'})
        }else {
            reject('失败了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result); // {name: '张三'})
       .catch(error => console.log(error); // 失败了)
```

```js
const { log } = require('console');
const fs = require('fs');
function f1() {
    return new Promise((resolve, reject) => {
        fs.readFile('./gulp/gulpfile.js', 'utf8', (error, result) => {
            resolve(result);
        })
    });
}
function f2() {
    return new Promise((resolve, reject) => {
        fs.readFile('./test/css/index.css', 'utf8', (error, result) => {
            resolve(result);
        })
    });
}
function f3() {
    return new Promise((resolve, reject) => {
        fs.readFile('./SuperSlide2.1-demo-00/SuperSlide2.1-demo-00/demo.html', 'utf8', (error, result) => {
            resolve(result);
        })
    });
}
f1().then((r1) => {
        console.log('1');
        console.log(r1);
        return f2();
    })
    .then((r2) => {
        console.log('2');
        console.log(r2);
        return f3();
    })
    .then((r3) => {
        console.log('3');
        console.log(r3);
    })
```

### 异步函数

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

```js
const fn = async () => {};
async function fn () {}
```

**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返回结果**

```js
const fs = require('fs');
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
async function run() {
    let r1 = await readFile('../Web_pro/t1.js', 'utf8');
    let r2 = await readFile('../Web_pro/t2.js', 'utf8');
    let r3 = await readFile('../Web_pro/仿淘宝固定侧边栏.html', 'utf8');
    console.log(r1);
    console.log(r2);
    console.log(r3);
}
run();
```

## MongoDB

**数据库概述及环境搭建**

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

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

常见的数据库软件有：mysql、mongoDB、oracle。

**MongoDB数据库下载安装**

**下载地址**

https://www.mongodb.com/download-center/community

**MongoDB可视化软件**

[MongoDB Compass Download | MongoDB](https://www.mongodb.com/try/download/compass)

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

![image-20220420152136013](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201521079.png)

**数据库相关概念**

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

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

**Mongoose第三方包**

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

## **数据库连接**

```js
使用mongoose提供的connect方法即可连接数据库。
 mongoose.connect('mongodb://localhost/playground')
     .then(() => console.log('数据库连接成功'))
     .catch(err => console.log('数据库连接失败', err));
```

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

## **创建数据库**

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

## MongoDB增删改查操作

### **创建集合**

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

```js
// 设定集合规则
 const courseSchema = new mongoose.Schema({
     name: String,
     author: String,
     isPublished: Boolean
 });
  // 创建集合并应用规则
 const Course = mongoose.model('Course', courseSchema); // courses
```

### **创建文档**

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

分为两步：

1. 创建集合实例。
2. 调用实例对象下的**save\*\*\*\*方法**将数据保存到数据库中。

```js
 // 创建集合实例
 const course = new Course({
     name: 'Node.js course',
     author: '黑马讲师',
     tags: ['node', 'backend'],
     isPublished: true
 });
  // 将数据保存到数据库中
 course.save();

Course.create({name: 'JavaScript基础', author: '黑马讲师', isPublish: true}, (err, doc) => { 
     //  错误对象
    console.log(err)
     //  当前插入的文档
    console.log(doc)
});
Course.create({name: 'JavaScript基础', author: '黑马讲师', isPublish: true})
      .then(doc => console.log(doc))
      .catch(err => console.log(err))
```

### mongoDB数据库导入数据

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

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

[Download MongoDB Command Line Database Tools | MongoDB](https://www.mongodb.com/try/download/database-tools?tck=docs_databasetools)

### **查询文档**

```js
//  根据条件查找文档（条件为空则查找所有文档）
Course.find().then(result => console.log(result))
// 返回文档集合
[{
    _id: 5c0917ed37ec9b03c07cf95f,
    name: 'node.js基础',
    author: '黑马讲师‘
},{
     _id: 5c09dea28acfb814980ff827,
     name: 'Javascript',
     author: '黑马讲师‘
}]

-------------------------------------------------------------------
//  根据条件查找文档
Course.findOne({name: 'node.js基础'}).then(result => console.log(result))
// 返回文档
 {
    _id: 5c0917ed37ec9b03c07cf95f,
    name: 'node.js基础',
    author: '黑马讲师‘
}
-------------------------------------------------------------------
 //  匹配大于 小于
 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 -_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))
```

### **删除文档**

```js
 // 删除单个
Course.findOneAndDelete({}).then(result => console.log(result))

 // 删除多个
User.deleteMany({}).then(result => console.log(result))
```

### **更新文档**

```js
// 更新单个
User.updateOne({查询条件}, {要修改的值}).then(result => console.log(result))

// 更新多个
User.updateMany({查询条件}, {要更改的值}).then(result => console.log(result))
```

### **mongoose验证**

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

* **required**: \[true,自定义错误信息] 必传字段
* **minlength**：\[3,自定义错误信息] 字符串最小长度
* **maxlength**: \[20,自定义错误信息] 字符串最大长度
* **min**: \[2,自定义错误信息] 数值最小为2
* **max**: \[100,自定义错误信息] 数值最大为100
* **enum**: \['html'**,** 'css'**,** 'javascript'**,** 'node.js']
* **trim**: true 去除字符串两边的空格
* **validate**: 自定义验证器

```js
password: {
        type: String,
        validate: {
            validator: (v) => {
                return v && v.length < 13
            },
            message: '错误信息'
        }
    }
```

* **default**: 默认值

**获取错误信息**：**error.errors\['字段名称'].message**

```js
.catch((error) => {
        const err = error.errors;
        for (var i in err) {
            console.log(err[i]['message'])
        }
    })
```

### **集合关联**

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

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

![image-20220420154745180](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201547256.png)

```js
// 用户集合
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' }
}));
//联合查询
Post.find()
      .populate('author')
      .then((err, result) => console.log(result));
```

## 模板引擎

**模板引擎基础概念**

模板引擎是第三方模块。

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

```js
// 未使用模板引擎的写法
 var ary = [{ name: '张三', age: 20 }];
 var str = '<ul>';
 for (var i = 0; i < ary.length; i++) { 
    str += '<li>\
        <span>'+ ary[i].name +'</span>\
        <span>'+ ary[i].age +'</span>\
    </li>';
 }
 str += '</ul>'; 

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

### **art-template模板引擎**

1. 在命令行工具中使用     **npm install art-template** 命令进行下载

2. 使用**const** template **=**     require\*\*(**'art-template'**)\*\*引入模板引擎

3. 告诉模板引擎要拼接的数据和模板在哪 const html = template(‘模板路径’, 数据);

4. 使用模板语法告诉模板引擎，模板与数据应该如何进行拼接

art-template代码示例

```js
// 导入模板引擎模块
 const template = require('art-template');
 // 将特定模板与特定数据进行拼接
 const html = template('./views/index.art',{
    data: {
        name: '张三',
        age: 20
    }
 }); 

 <div>
    <span>{{data.name}}</span>
    <span>{{data.age}}</span>
 </div>
```

### 模板引擎语法

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

**标准语法：** **`{{数据}}`**

**原始语法：`<%=数据%>`**

**输出**

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

* 标准语法：**`{{ 数据 }}`**
* 原始语法：**`<%= 数据 %>`**

```js
 <!-- 标准语法 -->
 <h2>{{value}}</h2>
 <h2>{{a ? b : c}}</h2>
 <h2>{{a + b}}</h2>
  <!-- 原始语法 -->
 <h2><%= value %></h2>
 <h2><%= a ? b : c %></h2>
 <h2><%= a + b %></h2>
```

**原文输出**

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

* **标准语法：`{{@数据 }}`**
* **原始语法：`<%- 数据 %>`**

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

**条件判断**

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

**循环**

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

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

**子模版**

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

* **标准语法：`{{include '模板'}}`**
* **原始语法：`<%include('模板') %>`**

**模板继承**

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

![image-20220420155614712](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201556800.png)

**模板继承示例**

```html
<!doctype html>
 <html>
     <head>
         <meta charset="utf-8">
         <title>HTML骨架模板</title>
         {{block 'head'}}{{/block}}
     </head>
     <body>
         {{block 'content'}}{{/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}}
```

**模板配置**

1. 向模板中导入**变量** template.defaults.imports.变量名 = 变量值;

   ```js
   const dateForamt = require('dateformat');
   template.defaults.imports.dateForamt = dateForamt;
   let html = template(views, {
       data: {
           name: '胡梓卓',
           age: 22
       },
       time: new Date()
   });

   {{dateForamt(date,'yyyy-mm-dd HH:MM:ss')}}
   ```

2. 设置模板**根目录** template.defaults.root = 模板目录

3. 设置模板**默认后缀** template.defaults.extname = '.art'

## **第三方模块** **router**

功能：实现路由

使用步骤：

1. 获取路由对象
2. 调用路由对象提供的方法创建路由
3. 启用路由，使路由生效

```js
const getRouter = require('router')
const router = getRouter();
router.get('/add', (req, res) => {
    res.end('Hello World!')
}) 
server.on('request', (req, res) => {
    router(req, res)
})
```

## **第三方模块** **serve-static**

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

步骤：

1. 引入serve-static模块获取创建静态资源服务功能的方法
2. 调用方法创建静态资源服务并指定静态资源服务目录
3. 启用静态资源服务功能

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

## Express

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

**框架特性**

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

**原生Node.js与Express框架对比之路由**

```js
app.on('request', (req, res) => {
     // 获取客户端的请求路径
     let { pathname } = url.parse(req.url);
     // 对请求路径进行判断 不同的路径地址响应不同的内容
     if (pathname == '/' || pathname == 'index') {
        res.end('欢迎来到首页');
     } else if (pathname == '/list') {
        res.end('欢迎来到列表页页');
     } else if (pathname == '/about') {
        res.end('欢迎来到关于我们页面')
     } else {
        res.end('抱歉, 您访问的页面出游了');
     }
 });
```

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

**原生Node.js与Express框架对比之获取请求参数**

```js
app.on('request', (req, res) => {
    // 获取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)
    })); 
 });
```

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

### Express初体验

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

```js
 // 引入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);
```

## 中间件

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

![image-20220420160135935](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201601028.png)

中间件主要由两部分构成，**中间件方法**以及**请求处理函数**。

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

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

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

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

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

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

### **app.use中间件用法**

```js
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();
 });
```

**中间件应用**

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

### **错误处理中间件**

```js
在程序执行的过程中，不可避免的会出现一些无法预料的错误，比如文件读取失败，数据库连接失败。
错误处理中间件是一个集中处理错误的地方。
 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);
         }
     });
});
```

### **捕获错误**

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

异步函数执行如果发生错误要如何捕获错误呢？
try catch 可以捕获异步函数以及其他同步代码在执行过程中发生的错误，但是不能捕获其他类型的API发生的错误。
 app.get("/", async (req, res, next) => {
     try {
         await User.find({name: '张三'})
     }catch(ex) {
         next(ex);
     }
 });
```

## Express请求处理

### **构建模块化路由**

```js
const express = require('express') 
 // 创建路由对象
 const home = express.Router();
 // 将路由和请求路径进行匹配
 app.use('/home', home);
  // 在home路由下继续创建路由
 home.get('/index', () => {
          //  /home/index
         res.send('欢迎来到博客展示页面');
 });
```

![image-20220420160453717](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201604818.png)

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

### GET参数的获取

```js
Express框架中使用req.query即可获取GET参数，框架内部会将GET参数转换为对象并返回。
 // 接收地址栏中问号后面的参数
 // 例如: http://localhost:3000/?name=zhangsan&age=30
 app.get('/', (req, res) => {
    console.log(req.query); // {"name": "zhangsan", "age": "30"}
 });
```

### POST参数的获取

```js
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);
 }) 
```

### Express路由参数

```js
app.get('/find/:id', (req, res) => { 
     console.log(req.params); // {id: 123} 
 });

localhost:3000/find/123
```

### 静态资源的处理

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

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

现在，public 目录下面的文件就可以访问了。

* http://localhost:3000/images/kitten.jpg
* http://localhost:3000/css/style.css
* http://localhost:3000/js/app.js
* http://localhost:3000/images/bg.png
* http://localhost:3000/hello.html

## express-art-template模板引擎

**模板引擎**

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

```js
// 当渲染后缀为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');
 }); 
```

**app.locals对象**

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

```js
app.locals.users = [{
     name: '张三',
     age: 20
 },{
     name: '李四',
     age: 20
}]
```

## Ajax

### Ajax运行原理与实现

![image-20220420163307863](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201633012.png)

#### 实现步骤

![image-20220420163331006](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201633086.png)

#### 服务器端响应的数据格式

在真实的项目中，服务器端大多数情况下会以 JSON 对象作为响应数据的格式。当客户端拿到响应数据时，要将 JSON 数据和 HTML 字符串进行拼接，然后将拼接的结果展示在页面中。

在 http 请求与响应的过程中，无论是请求参数还是响应内容，如果是对象类型，最终都会被转换为对象字符串进行传输。

![image-20220420163406583](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201634648.png)

#### 请求参数传递

![image-20220420163444964](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201634054.png)

#### 请求报文

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

![image-20220420164453933](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201644014.png)

**请求参数的格式**

![image-20220420164517463](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201645540.png)

#### 获取服务器端的响应

**Ajax状态码**

在创建ajax对象，配置ajax对象，发送请求，以及接收完服务器端响应数据，这个过程中的每一个步骤都会对应一个数值，这个数值就是ajax状态码。

0：请求未初始化(还没有调用open())

1：请求已经建立，但是还没有发送(还没有调用send())

2：请求已经发送

3：请求正在处理中，通常响应中已经有部分数据可以用了

4：响应已经完成，可以获取并使用服务器的响应了

![image-20220420164550394](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201645459.png)

**onreadystatechange** 事件

当 Ajax 状态码发生变化时将自动触发该事件。

在事件处理函数中可以获取 Ajax 状态码并对其进行判断，当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。

![image-20220420164610967](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201646034.png)

![image-20220420164623830](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201646903.png)

#### Ajax错误处理

![image-20220420164641436](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201646523.png)

#### 低版本IE浏览器的缓存问题

问题：在低版本的 IE 浏览器中，Ajax 请求有严重的缓存问题，即在请求地址不发生变化的情况下，只有第一次请求会真正发送到服务器端，后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了，客户端依然拿到的是缓存中的旧数据。

解决方案：在请求地址的后面加请求参数，保证每一次请求中的请求参数的值不相同。

![image-20220420164706430](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201647500.png)

### Ajax异步编程

#### **同步异步概述**

![image-20220420164743389](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201647484.png)

#### Ajax封装

![image-20220420164813414](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201648490.png)

### 模板引擎

作用：使用模板引擎提供的模板语法，可以将数据和 HTML 拼接起来。

官方地址： https://aui.github.io/art-template/zh-cn/index.html

![image-20220420164846322](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201648400.png)

#### 验证邮箱唯一性

```html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>验证邮箱地址是否已经注册</title>
	<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
	<style type="text/css">
		p:not(:empty) {
			padding: 15px;
		}
		.container {
			padding-top: 100px;
		}
	</style>
</head>
<body>
	<div class="container">
		<div class="form-group">
			<label>邮箱地址</label>
			<input type="email" class="form-control" placeholder="请输入邮箱地址" id="email">
		</div>
		<!-- 错误 bg-danger 正确 bg-success -->
		<p id="info"></p>
	</div>
	<script src="/js/ajax.js"></script>
	<script>
		// 获取页面中的元素
		var emailInp = document.getElementById('email');
		var info = document.getElementById('info');

		// 当文本框离开焦点以后
		emailInp.onblur = function () {
			// 获取用户输入的邮箱地址
			var email = this.value;
			// 验证邮箱地址的正则表达式
			var reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/;
			// 如果用户输入的邮箱地址不符合规则
			if (!reg.test(email)) {
				// 给出用户提示
				info.innerHTML = '请输入符合规则的邮箱地址';
				// 让提示信息显示为错误提示信息的样式
				info.className = 'bg-danger';
				// 阻止程序向下执行
				return;
			}

			// 向服务器端发送请求
			ajax({
				type: 'get',
				url: 'http://localhost:3000/verifyEmailAdress',
				data: {
					email: email
				},
				success: function (result) {
					console.log(result);
					info.innerHTML = result.message;
					info.className = 'bg-success';
				},
				error: function (result) {
					console.log(result)
					info.innerHTML = result.message;
					info.className = 'bg-danger';
				}
			});

		}
	</script>
</body>
</html>

---------------------------------------------------------------
// 邮箱地址验证
app.get('/verifyEmailAdress', (req, res) => {
	// 接收客户端传递过来的邮箱地址
	const email = req.query.email;
	// 判断邮箱地址注册过的情况
	if (email == 'itheima@itcast.cn') {
		// 设置http状态码并对客户端做出响应
		res.status(400).send({message: '邮箱地址已经注册过了, 请更换其他邮箱地址'});
	} else {
		// 邮箱地址可用的情况
		// 对客户端做出响应
		res.send({message: '恭喜, 邮箱地址可用'});
	} 
});
```

#### 搜索框内容自动提示

![image-20220420165415686](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201654761.png)

```html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>搜索框输入文字自动提示</title>
	<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
	<style type="text/css">
		.container {
			padding-top: 150px;
		}
		.list-group {
			display: none;
		}
	</style>
</head>
<body>
	<div class="container">
		<div class="form-group">
			<input type="text" class="form-control" placeholder="请输入搜索关键字" id="search">
			<ul class="list-group" id="list-box">
				
			</ul>
		</div>
	</div>
	<script src="/js/ajax.js"></script>
	<script src="/js/template-web.js"></script>
	<script type="text/html" id="tpl">
		{{each result}}
			<li class="list-group-item">{{$value}}</li>
		{{/each}}
	</script>
	<script>
		// 获取搜索框
		var searchInp = document.getElementById('search');
		// 获取提示文字的存放容器
		var listBox = document.getElementById('list-box');
		// 存储定时器的变量
		var timer = null;
		// 当用户在文本框中输入的时候触发
		searchInp.oninput = function () {
			// 清除上一次开启的定时器
			clearTimeout(timer);
			// 获取用户输入的内容
			var key = this.value;
			// 如果用户没有在搜索框中输入内容
			if (key.trim().length == 0) {
				// 将提示下拉框隐藏掉
				listBox.style.display = 'none';
				// 阻止程序向下执行
				return;
			}

			// 开启定时器 让请求延迟发送
			timer = setTimeout(function () {
				// 向服务器端发送请求
				// 向服务器端索取和用户输入关键字相关的内容
				ajax({
					type: 'get',
					url: 'http://localhost:3000/searchAutoPrompt',
					data: {
						key: key
					},
					success: function (result) {
						// 使用模板引擎拼接字符串
						var html = template('tpl', {result: result});
						// 将拼接好的字符串显示在页面中
						listBox.innerHTML = html;
						// 显示ul容器
						listBox.style.display = 'block';
					}
				})
			}, 800)
			
		}
	</script>
</body>
</html>
----------------------------------------
// 输入框文字提示
app.get('/searchAutoPrompt', (req, res) => {
	// 搜索关键字
	const key = req.query.key;
	// 提示文字列表
	const list = [
		'黑马程序员',
		'黑马程序员官网',
		'黑马程序员顺义校区',
		'黑马程序员学院报名系统',
		'传智播客',
		'传智博客前端与移动端开发',
		'传智播客大数据',
		'传智播客python',
		'传智播客java',
		'传智播客c++',
		'传智播客怎么样'
	];
	// 搜索结果
	let result = list.filter(item => item.includes(key));
	// 将查询结果返回给客户端
	res.send(result);
});
```

![image-20220420165627574](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201656658.png)

```html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>搜索框输入文字自动提示</title>
	<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
	<style type="text/css">
		.container {
			padding-top: 150px;
		}
	</style>
</head>
<body>
	<div class="container">
		<div class="form-inline">
			<div class="form-group">
				<select class="form-control" id="province"></select>
			</div>
			<div class="form-group">
				<select class="form-control" id="city">
					<option>请选择城市</option>
				</select>
			</div>
			<div class="form-group">
				<select class="form-control" id="area">
					<option>请选择县城</option>
				</select>
			</div>
		</div>
	</div>
	<script src="/js/ajax.js"></script>
	<script src="/js/template-web.js"></script>
	<!-- 省份模板 -->
	<script type="text/html" id="provinceTpl">
		<option>请选择省份</option>
		{{each province}}
			<option value="{{$value.id}}">{{$value.name}}</option>
		{{/each}}
	</script>
	<!-- 城市模板 -->
	<script type="text/html" id="cityTpl">
		<option>请选择城市</option>
		{{each city}}
			<option value="{{$value.id}}">{{$value.name}}</option>
		{{/each}}
	</script>
	<!-- 县城模板 -->
	<script type="text/html" id="areaTpl">
		<option>请选择县城</option>
		{{each area}}
			<option value="{{$value.id}}">{{$value.name}}</option>
		{{/each}}
	</script>
	<script>
		// 获取省市区下拉框元素
		var province = document.getElementById('province');
		var city = document.getElementById('city');
		var area = document.getElementById('area');
		// 获取省份信息
		ajax({
			type: 'get',
			url: 'http://localhost:3000/province',
			success: function (data) {
				// 将服务器端返回的数据和html进行拼接
				var html = template('provinceTpl', {province: data});
				// 将拼接好的html字符串显示在页面中
				province.innerHTML = html;
			}
		});

		// 为省份的下拉框添加值改变事件
		province.onchange = function () {
			// 获取省份id
			var pid = this.value;

			// 清空县城下拉框中的数据
			var html = template('areaTpl', {area: []});
			area.innerHTML = html;

			// 根据省份id获取城市信息
			ajax({
				type: 'get',
				url: '/cities',
				data: {
					id: pid
				},
				success: function (data) {
					var html = template('cityTpl', {city: data});
					city.innerHTML = html;
				}
			})
		};

		// 当用户选择城市的时候
		city.onchange = function () {
			// 获取城市id
			var cid = this.value;
			// 根据城市id获取县城信息
			ajax({
				type: 'get',
				url: 'http://localhost:3000/areas',
				data: {
					id: cid
				},
				success: function(data) {
					var html = template('areaTpl', {area: data});
					area.innerHTML = html;
				}
			})
		}
	</script>
</body>
</html>
-----------------------------------------
// 获取省份
app.get('/province', (req, res) => {
	res.json([{
		id: '001',
		name: '黑龙江省'
	},{
		id: '002',
		name: '四川省'
	},{
		id: '003',
		name: '河北省'
	},{
		id: '004',
		name: '江苏省'
	}]);
});

// 根据省份id获取城市
app.get('/cities', (req, res) => {
	// 获取省份id
	const id = req.query.id;
	// 城市信息
	const cities = {
		'001': [{
			id: '300',
			name: '哈尔滨市'
		}, {
			id: '301',
			name: '齐齐哈尔市'
		}, {
			id: '302',
			name: '牡丹江市'
		}, {
			id: '303',
			name: '佳木斯市'
		}],
		'002': [{
			id: '400',
			name: '成都市'
		}, {
			id: '401',
			name: '绵阳市'
		}, {
			id: '402',
			name: '德阳市'
		}, {
			id: '403',
			name: '攀枝花市'
		}],
		'003': [{
			id: '500',
			name: '石家庄市'
		}, {
			id: '501',
			name: '唐山市'
		}, {
			id: '502',
			name: '秦皇岛市'
		}, {
			id: '503',
			name: '邯郸市'
		}],
		'004': [{
			id: '600',
			name: '常州市'
		}, {
			id: '601',
			name: '徐州市'
		}, {
			id: '602',
			name: '南京市'
		}, {
			id: '603',
			name: '淮安市'
		}]
	}
	// 响应
	res.send(cities[id]);
});

// 根据城市id获取县城
app.get('/areas', (req, res) => {
	// 获取城市id
	const id = req.query.id;
	// 县城信息
	const areas = {
		'300': [{
			id: '20',
			name: '道里区',
		}, {
			id: '21',
			name: '南岗区'
		}, {
			id: '22',
			name: '平房区',
		}, {
			id: '23',
			name: '松北区'
		}],
		'301': [{
			id: '30',
			name: '龙沙区'
		}, {
			id: '31',
			name: '铁锋区'
		}, {
			id: '32',
			name: '富拉尔基区'
		}]
	};
	// 响应
	res.send(areas[id] || []);
});
```

## FormData对象

1. 模拟HTML表单，相当于将HTML表单映射成表单对象，自动将表单对象中的数据拼接成请求参数的格式。

2. 异步上传二进制文件

**FormData对象的使用**

![image-20220420165731314](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201657399.png)

**注意：**

Formdata 对象**不能用于** **get** **请求**，因为对象需要被传递到 send 方法中，而 get 请求方式的请求参数只能放在请求地址的后面。

服务器端 **bodyParser** **模块不能解析** **formData** **对象表单数据**，我们需要使用 **formidable** **模块进行解析**

**FormData 对象的实例方法**

![image-20220420165804142](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201658228.png)

注意：set 方法与 append 方法的区别是，在属性名已存在的情况下，set 会覆盖已有键名的值，append会保留两个值。

**FormData 二进制文件上传**

![image-20220420165831830](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201658918.png)

**FormData 文件上传进度展示**

![image-20220420165853764](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201658849.png)

**FormData 文件上传图片即时预览**

在我们将图片上传到服务器端以后，服务器端通常都会将图片地址做为响应数据传递到客户端，客户端可以从响应数据中获取图片地址，然后将图片再显示在页面中。

![image-20220420165913806](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201659883.png)

## 同源政策

**什么是同源**

如果两个页面拥有相同的**协议**、**域名**和**端口**，那么这两个页面就属于**同一个源**，其中只要有一个不相同，就是不同源。

http://www.example.com/dir/page.html

http://www.example.com/dir2/other.html：同源

http://example.com/dir/other.html：不同源（域名不同）

http://v2.www.example.com/dir/other.html：不同源（域名不同）

http://www.example.com:81/dir/other.html：不同源（端口不同）

https://www.example.com/dir/page.html：不同源（协议不同）

**同源政策的目的**

同源政策是为了保证用户信息的安全，防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie，B网站是不能访问的。

随着互联网的发展，同源政策也越来越严格，在不同源的情况下，其中有一项规定就是无法向非同源地址发送Ajax 请求，如果请求，浏览器就会报错。

### Ajax请求限制

Ajax 只能向自己的服务器发送请求。

比如现在有一个A网站、有一个B网站，

A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求，

B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求，

但是 A 网站是不能向 B 网站发送 Ajax请求的，同理，B 网站也不能向 A 网站发送 Ajax请求。

### **使用** **JSONP** **解决同源限制问题**

jsonp 是 json with padding 的缩写，它**不属于** **Ajax** **请求**，但它可以模拟 Ajax 请求。

![image-20220420171012287](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201710384.png)

### JSONP代码优化

1. 客户端需要将函数名称传递到服务器端。
2. 将 script     请求的发送变成动态请求。
3. 封装 jsonp     函数，方便请求发送。
4. 服务器端代码优化之     res.jsonp 方法。

```js
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<button id="btn1">点我发送请求</button>
	<button id="btn2">点我发送请求</button>
	<script type="text/javascript">
		// 获取按钮
		var btn1 = document.getElementById('btn1');
		var btn2 = document.getElementById('btn2');
		// 为按钮添加点击事件
		btn1.onclick = function () {
			jsonp({
				// 请求地址
				url: 'http://localhost:3001/better',
				data: {
					name: 'lisi',
					age: 30
				},
				success: function (data) {
					console.log(123)
					console.log(data)
				}
			})
		}

		btn2.onclick = function () {
			jsonp({
				// 请求地址
				url: 'http://localhost:3001/better',
				success: function (data) {
					console.log(456789)
					console.log(data)
				}
			})
		}

		function jsonp (options) {
			// 动态创建script标签
			var script = document.createElement('script');
			// 拼接字符串的变量
			var params = '';

			for (var attr in options.data) {
				params += '&' + attr + '=' + options.data[attr];
			}
			
			// myJsonp0124741
			var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
			// 它已经不是一个全局函数了
			// 我们要想办法将它变成全局函数
			window[fnName] = options.success;
			// 为script标签添加src属性
			script.src = options.url + '?callback=' + fnName + params;
			// 将script标签追加到页面中
			document.body.appendChild(script);
			// 为script标签添加onload事件
			script.onload = function () {
				document.body.removeChild(script);
			}
		}

		
	</script>
</body>
</html>
```

### **CORS** **跨域资源共享**

CORS：全称为 Cross-origin resource sharing，即跨域资源共享，它允许浏览器向跨域服务器发送 Ajax 请求，克服了 Ajax 只能同源使用的限制。

![image-20220420171113002](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201711097.png)

### 访问非同源数据，服务器端解决方案

![image-20220420171132513](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201711615.png)

### **withCredentials属性**

在使用Ajax技术发送跨域请求时，默认情况下**不会在请求中携带cookie信息**。

**withCredentials**：指定在涉及到跨域请求时，**是否携带cookie信息**，默认值为false

**Access-Control-Allow-Credentials：true** 允许客户端发送请求时携带cookie

## jQuery Ajax

### $.ajax()

作用：发送**Ajax**请求

![image-20220420171343126](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201713223.png)

作用：发送**jsonp**请求

![image-20220420171358734](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201713818.png)

#### **serialize方法**

![image-20220420171419534](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201714619.png)

#### $.get() $.post()

![image-20220420171436587](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201714687.png)

#### 全局事件

只要页面中有Ajax请求被发送，对应的全局事件就会被触发

![image-20220420171458009](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201714086.png)

**Nprogress**

官宣：纳米级进度条，使用逼真的涓流动画来告诉用户正在发生的事情！

![image-20220420171514932](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201715016.png)

## RESTful风格的API

## XML基础

XML 的全称是 extensible markup language，代表可扩展标记语言，它的作用是传输和存储数据。

![image-20220420171627604](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/202204201716690.png)

**XML DOM**

XML DOM 即 XML 文档对象模型，是 w3c 组织定义的一套操作 XML 文档对象的API。浏览器会将 XML 文档解析成文档对象模型。
