文章目录
- 1.基础
- 2. Buffer(缓冲器)介绍与创建
- 3. fs模块(file system)
- 4. path 模块
- 5. http模块
- 6. Node.js 模块化
- 7 包管理
- 8. nvm
- 9. express
- 10. Mongodb
- 11. 接口
- 12. 会话控制
- 13. 模块工具使用
- 14. console
【前端目录贴】
参考视频: 尚硅谷Node.js零基础视频教程,nodejs新手到高手
1.基础
1.1. 基础命令
node -v
查看版本号node hello.js
运行js文件
1.2. 注意事项
Node.js中不能使用
BOM
和DOM
的API
console.log('hello world');
console.log(window);//报错
console.log(document);//报错
说明:浏览器中的javascript语法包括如下内容组成部分
node.js语法的组成部分
2. Buffer(缓冲器)介绍与创建
2.1 概念
- Buffer 是一个类似于数组的
对象
,用于表示固定长度的字节序列- Buffer 本质是一段内存空间,专门用来处理
二进制数据
。
2.2 特点
- Buffer 大小固定且无法调整
- Buffer 性能较好,可以直接对计算机内存进行操作
- 每个元素的大小为 1 字节(byte)
2.3 使用
- 创建 Buffer
Node.js 中创建 Buffer 的方式主要如下几种
- Buffer.alloc
- Buffer.allocUnsafe
- Buffer.from
// 创建了一个长度为 10 字节的 Buffer,相当于申请了 10 字节的内存空间,每个字节的值为 0
let buf_1 = Buffer.alloc(10) //=>结果为<Buffer 00 00 00 00 00 00 00 00 00 00>
//创建了一个长度为 10 字节的 Buffer,buffer 中可能存在旧的数据, 可能会影响执行结果,所以叫unsafe
let buf_2 = Buffer.allocUnsafe(10);
//通过字符串创建 Buffer
let buf_3 = Buffer.from('hello');
//通过数组创建 Buffer
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
- Buffer 与字符串的转化
toString
方法将 Buffer 转为字符串 (toString 默认是按照 utf-8 编码方式进行转换的。)
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
console.log(buf_4.toString())
- Buffer 的读写
Buffer 可以直接通过 [] 的方式对数据进行处理。
注意:
如果修改的数值超过255
,则超过 8 位数据会被舍弃
一个 utf-8 的中文字符一般
占 3 个字节
//读取
console.log(buf_3[1]);
//修改
buf_3[1] = 97;
//查看字符串结果
console.log(buf_3.toString());
3. fs模块(file system)
fs 全称为 file system
,称之为 文件系统
,是 Node.js 中的 内置模块
,可以对计算机中的磁盘进行操
作。
3.1 文件写入
文件写入就是将
数据
保存到文件
中,我们可以使用如下几个方法来实现该效果
方法 | 说明 |
---|---|
writeFile | 异步写入 |
writeFileSync | 同步写入 |
appendFile / appendFileSync | 追加写入 |
createWriteStream | 流式写入 |
- writeFile 异步写入
语法:
fs.writeFile(file, data[, options], callback)
参数说明:
- file 文件名
- data 待写入的数据
- options 选项设置 (可选)
- callback 写入回调
返回值:
undefined
// require 是 Node.js 环境中的'全局'变量,用来导入模块
var fs = require('fs');
// 参数说明:
// file 文件名
// data 待写入的数据
// options 选项设置 (可选)
// callback 写入回调
fs.writeFile('./fileBak/aa.txt','三人行',err => {//不存在,则创建
//如果写入失败,则调用回调函数,会传入错误对象,如写入成功,会传入null
if (err) {
console.log(err);
return;
}
console.log('写入成功');
});
- writeFileSync同步写入
语法:
fs.writeFileSync(file, data[, options])
参数与 fs.writeFile 大体一致,只是没有 callback 参数
返回值:undefined
// require 是 Node.js 环境中的'全局'变量,用来导入模块
var fs = require('fs');
// 参数说明:
// file 文件名
// data 待写入的数据
// options 选项设置 (可选)
fs.writeFileSync('./fileBak/bb.txt','三人行');
console.log('写入完成')
Node.js 中的磁盘操作是由其他 线程 完成的,结果的处理有两种模式:
同步处理
JavaScript 主线程 会等待 其他线程的执行结果,然后再继续执行主线程的代码,
效率较低
异步处理
JavaScript 主线程 不会等待 其他线程的执行结果,直接执行后续的主线程代码,
效率较好
- appendFile / appendFileSync追加写入
appendFile
作用是在文件尾部追加内容,appendFile
语法与writeFile
语法完全相同
语法:
fs.appendFile(file, data[, options], callback)
fs.appendFileSync(file, data[, options])
返回值: 二者都为undefined
// require 是 Node.js 环境中的'全局'变量,用来导入模块
// require 是 Node.js 环境中的'全局'变量,用来导入模块
var fs = require('fs');
// 参数说明:
// file 文件名
// data 待写入的数据
// options 选项设置 (可选)
// callback 写入回调
for (let i = 0; i < 10; i++) {
// fs.appendFile('./fileBak/aa.txt','三人行',err => {//不存在,则创建,异步追加
fs.appendFileSync('./fileBak/aa.txt','三人行');
}
console.log(1)
- createWriteStream流式写入
语法:
fs.createWriteStream(path[, options])
参数说明:
- path 文件路径
- options 选项配置(
可选
)- 返回值:
Object
// require 是 Node.js 环境中的'全局'变量,用来导入模块
var fs = require('fs');
// 参数说明:
// file 文件名
// options 选项设置 (可选)
var ws = fs.createWriteStream('./fileBak/cc.txt');
ws.write('11');
ws.write('22');
ws.end();
console.log(1)
程序打开一个文件是需要消耗资源的
,流式写入可以减少打开关闭文件的次数。
流式写入方式适用于大文件写入或者频繁写入
的场景, writeFile 适合于写入频率较低的场景
3.2 文件读取
文件读取顾名思义,就是通过程序从文件中取出其中的数据,我们可以使用如下几种方式
方法 | 说明 |
---|---|
readFile | 异步读取 |
readFileSync | 同步读取 |
createReadStream | 流式读取 |
- readFile 异步读取
语法:
fs.readFile(path[, options], callback)
参数说明:
- path 文件路径
- options 选项配置
- callback 回调函数
- 返回值:
undefined
var fs = require('fs');
fs.readFile('./fileBak/aa.txt',(err,data) => {
if (err) {
throw err
}
console.log(data);
})
fs.readFile('./fileBak/aa.txt','utf-8',(err,data) => {
if (err) {
throw err
}
console.log(data);
})
- readFileSync 同步读取
语法:
fs.readFileSync(path[, options])
参数说明:
path 文件路径
options 选项配置
返回值:string | Buffer
var fs = require('fs');
var data1 = fs.readFileSync('./fileBak/aa.txt');
console.log(data1)// data1.toString()会将buffer转换; toString 方法将 Buffer 转为字符串 (toString 默认是按照 utf-8 编码方式进行转换的。)
var data2 = fs.readFileSync('./fileBak/aa.txt','utf-8');
console.log(data2)
- createReadStream 流式读取
语法:
fs.createReadStream(path[, options])
参数说明:
- path 文件路径
- options 选项配置(
可选
)- 返回值:
Object
var fs = require('fs');
var readStream = fs.createReadStream('./fileBak/aa.txt','utf-8');
readStream.on('data',data3=>{
console.log(data3)
console.log(data3.length)
});
//读取完毕后,执行end回调
readStream.on('end',()=>{
console.log('读取完后');
})
3.3 文件移动与重命名
在 Node.js 中,我们可以使用
rename
或 renameSync` 来移动或重命名 文件或文件夹
语法:
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)
参数说明:
- oldPath 文件当前的路径
- newPath 文件新的路径
- callback 操作后的回调(同步)
var fs = require('fs');
fs.rename('./fileBak/aa.txt','./fileBak/aa2.txt',err => {
if(err) throw err;
console.log('移动完成');
});
var fs = require('fs');
fs.renameSync('./fileBak/aa2.txt','./fileBak/aa.txt');
3.4 文件删除
在 Node.js 中,我们可以使用 unlink 或 unlinkSync 来删除文件
语法:
fs.unlink(path, callback)
fs.unlinkSync(path)
参数说明:
- path 文件路径
- callback 操作后的回调
var fs = require('fs');
fs.unlink('./fileBak/aa.txt',err => {
if(err) throw err;
console.log('删除完成');
});
var fs = require('fs');
fs.unlinkSync('./fileBak/aa.txt');
3.5 文件夹操作
借助 Node.js 的能力,我们可以对文件夹进行 创建
、 读取
、 删除
等操作
方法 | 说明 |
---|---|
mkdir / mkdirSync | 创建文件夹 |
readdir / readdirSync | 读取文件夹 |
rmdir / rmdirSync | 删除文件夹 |
- mkdir / mkdirSync 创建文件夹
在 Node.js 中,我们可以使用
mkdir
或mkdirSync
来创建文件夹
语法:
fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])
参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
var fs = require('fs');
fs.mkdir('./fileBak/cc',err => {//如果存在会报错
if(err) throw err;
console.log('创建成功');
})
var fs = require('fs');
fs.mkdirSync('./fileBak/bbD');
var fs = require('fs');
//异步递归创建文件夹
fs.mkdir('./fileBak/1/2/3',{recursive:true},err => {
if (err) {
throw err;
}
console.log('同步递归创建文件夹');
});
var fs = require('fs');
//同步递归创建文件夹
fs.mkdirSync('./fileBak/1/2/3',{recursive:true});
- readdir / mkdirSync 读取文件夹
在 Node.js 中,我们可以使用 readdir 或 readdirSync 来读取文件夹
语法:
fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])
参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
var fs = require('fs');
fs.readdir('./fileBak/1',(err,data) => {
if (err) {
throw err;
}
console.log(data);
});
var fs = require('fs');
var data = fs.readdirSync('./fileBak/1');
console.log(data)
- rmdir / mkdirSync 删除文件夹
在 Node.js 中,我们可以使用 rmdir 或 rmdirSync 来删除文件夹
语法:
fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])
参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
var fs = require('fs');
//异步删除文件夹
// fs.rmdir('./fileBak/cc', err => {
// if(err) throw err;
// console.log('删除成功');
// });
//异步递归删除文件夹
fs.rmdir('./fileBak/1', {recursive: true}, err => {
if(err) {
console.log(err);
}
console.log('递归删除')
});
3.6 查看资源状态
在 Node.js 中,我们可以使用 stat 或 statSync 来查看资源的详细信息
语法:
fs.stat(path[, options], callback)
fs.statSync(path[, options])
参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
var fs = require('fs');
//异步获取状态
// fs.stat('./fileBak/aa.txt', (err, data) => {
// if(err) throw err;
// console.log(data);
// });
//同步获取状态
let data = fs.statSync('./fileBak/aa.txt');
console.log(data)
console.log(data.isFile())//是否是文件 true
console.log(data.isDirectory())//是否是目录 false
3.7 相对路径问题
fs 模块对资源进行操作时,路径的写法有两种:
- 相对路径
*
./座右铭.txt
当前目录下的座右铭.txt
*
座右铭.txt
等效于上面的写法
*
../座右铭.txt
当前目录的上一级目录中的座右铭.txt- 绝对路径
*
D:/Program Files
windows 系统下的绝对路径
*
/usr/bin Linux
系统下的绝对路径
相对路径中所谓的
当前目录
,指的是命令行的工作目录
,而并非是文件的所在目录
所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG
3.8 __dirname
__dirname
与require
类似,都是 Node.js 环境中的’全局’变量,__dirname
保存着 当前文件所在目录的绝对路径 ,可以使用 __dirname 与文件名拼接成绝对路径
let data = fs.readFileSync(__dirname + '/data.txt');
3.9 练习复制文件脚本
var fs = require('fs');
fs.readFile('./fileBak/1.png', (err, data) => {
if (err) {
throw err;
return;
}
console.log('开始读取....')
fs.writeFile('./fileBak/2.png', data, err => {
if (err) {
throw err;
return;
}
console.log('开始导出...');
console.log('结束导出...');
})
console.log('结束读取....')
});
// fs.writeFile('./fileBak/2.png', 'f', err => {
// if (err) {
// throw err;
// return;
// }
// console.log('开始导出...');
// console.log('结束导出...');
// })
4. path 模块
path 模块提供了 操作路径
的功能,我们将介绍如下几个较为常用的几个 API:
API | 说明 |
---|---|
path.resolve | 拼接规范的绝对路径 常用 |
path.sep | 获取操作系统的路径分隔符 |
path.parse | 解析路径并返回对象 |
path.basename | 获取路径的基础名称 |
path.dirname | 获取路径的目录名 |
path.extname | 获得路径的扩展名 |
var path = require('path');
//获取路径分隔符
console.log(path.sep);
//拼接绝对路径
console.log(path.resolve(__dirname,'test'));
//解析路径
var pathname = 'F:\\front-end\\[B站]尚硅谷Nodejs\\fileBak\\aa.txt';
console.log('解析路径',path.parse(pathname));//{root:'F:\\', dir: 'F:\\front-end\\[B站]尚硅谷Nodejs\\fileBak',
// base: 'aa.txt', ext: '.txt', name: 'aa' }
//获取路径基础名称
console.log('获取路径基础名称',path.basename(pathname));//aa.txt
//获取路径的目录名
console.log('获取路径的目录名',path.dirname(pathname));// F:\front-end\[B站]尚硅谷Nodejs\fileBak
//获取路径的扩展名
console.log('获取路径的扩展名',path.extname(pathname));//.txt
console.log('文件的绝对路径',__filename);//全局变量
5. http模块
5.1 使用 nodejs 创建 HTTP 服务
//1. 导入 http 模块
var http = require('http');
//2. 创建服务对象 create 创建 server 服务
// request 意为请求. 是对请求报文的封装对象, 通过 request 对象可以获得请求报文的数据
// response 意为响应. 是对响应报文的封装对象, 通过 response 对象可以设置响应报文
var server=http.createServer((request, response)=>{
response.setHeader('content-type','text/html;charset=utf-8');//解决中文乱码
response.end('Hello HTTP server,锅子')
});
//3. 监听端口, 启动服务
server.listen(9000,()=>{
console.log('服务已经启动,端口9000监听中...');
});
// http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
http.createServer
里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
注意事项:
- 命令行
ctrl + c
停止服务- 当服务启动后,更新代码 必须
重启
服务才能生效- 响应内容中文乱码的解决办法
response.setHeader('content-type','text/html;charset=utf-8');
5.2 HTTP 请求报文
注意事项:
- request.url 只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容
- request.headers 将请求信息转化成一个对象,并将属性名都转化成了『小写』
- 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为『 / 』
- 关于 favicon.ico:这个请求是属于浏览器自动发送的请求
//1. 导入 http 模块
var http = require('http');
//2. 创建服务对象 create 创建 server 服务
// request 意为请求. 是对请求报文的封装对象, 通过 request 对象可以获得请求报文的数据
// response 意为响应. 是对响应报文的封装对象, 通过 response 对象可以设置响应报文
var server = http.createServer((request, response) => {
// console.log('request',request);//request 对象
// console.log('request.method',request.method);//request 对象
let {url, method} = request; //对象的解构赋值
console.log(url);
console.log(method);
response.setHeader('content-type', 'text/html;charset=utf-8');//解决中文乱码
if (url === '/register' && method === 'GET') {
response.write("<h1>注册页面</h1>");
} else if (url === "/login" && method === "GET") {
response.write("<h1>登录页面</h1>");
} else {
response.write("<h1>404 Not Found</h1>")
}
response.end('Hello HTTP server,锅子');
});
//3. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动,端口9000监听中...');
});
// http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
5.3 HTTP 响应报文
write 和 end 的两种使用情况:
//1. write 和 end 的结合使用 响应体相对分散
response.write('xx');
response.write('xx');
response.write('xx');
response.end(); //每一个请求,在处理的时候必须要执行 end 方法的
//2. 单独使用 end 方法 响应体相对集中
response.end('xxx');
//1. 导入 http 模块
var http = require('http');
//2. 创建服务对象 create 创建 server 服务
// request 意为请求. 是对请求报文的封装对象, 通过 request 对象可以获得请求报文的数据
// response 意为响应. 是对响应报文的封装对象, 通过 response 对象可以设置响应报文
var server = http.createServer((request, response) => {
response.setHeader('Content-Type','application/octet-stream');
response.end('<!DOCTYPE html>\n' +
'<html lang="en">\n' +
'<head>\n' +
' <meta charset="UTF-8">\n' +
' <title>Document</title>\n' +
' <style>\n' +
' td {\n' +
' padding: 20px 40px;\n' +
' }\n' +
'\n' +
' table tr:nth-child(odd) {\n' +
' background: #aef;\n' +
' }\n' +
'\n' +
' table tr:nth-child(even) {\n' +
' background: #fcb;\n' +
' }\n' +
'\n' +
' table, td {\n' +
' border-collapse: collapse;\n' +
' }\n' +
' </style>\n' +
'</head>\n' +
'<body>\n' +
'<table border="1">\n' +
' <tr>\n' +
' <td></td>\n' +
' <td></td>\n' +
' <td></td>\n' +
' </tr>\n' +
' <tr>\n' +
' <td></td>\n' +
' <td></td>\n' +
' <td></td>\n' +
' </tr>\n' +
' <tr>\n' +
' <td></td>\n' +
' <td></td>\n' +
' <td></td>\n' +
' </tr>\n' +
' <tr>\n' +
' <td></td>\n' +
' <td></td>\n' +
' <td></td>\n' +
' </tr>\n' +
'</table>\n' +
' <script>\n' +
' var listOf = document.querySelectorAll(\'td\');\n' +
' for (let i = 0; i < listOf.length; i++) {\n' +
' var element = listOf[i];\n' +
' // element.onclick = function () {\n' +
' // console.log(\'你好啊\');\n' +
' // };\n' +
' element.addEventListener(\'click\', function () {\n' +
' this.style.background=\'red\'\n' +
' });\n' +
'\n' +
' }\n' +
' </script>\n' +
'</body>\n' +
'</html>');
});
//3. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动,端口9000监听中...');
});
// http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
5.4 静态资源服务
静态资源
是指 内容长时间不发生改变的资源 ,例如图片,视频,CSS文件,JS文件,HTML文件,字体文件等
动态资源
是指 内容经常更新的资源 ,例如百度首页,网易首页,京东搜索列表页面等
//1. 导入 http 模块
var http = require('http');
var fs = require('fs');
//2. 创建服务对象 create 创建 server 服务
// request 意为请求. 是对请求报文的封装对象, 通过 request 对象可以获得请求报文的数据
// response 意为响应. 是对响应报文的封装对象, 通过 response 对象可以设置响应报文
var server = http.createServer((request, response) => {
response.setHeader('Content-Type','text/html;charset=utf-8');
//获取请求的方法已经路径
let {url,method} = request;
//文件夹路径
let rootDir = __dirname + '/fileBak';
//拼接文件路径
let filePath = rootDir + url;
fs.readFile(filePath,(err,data)=>{
//判断
if(err){
//如果出现错误,响应404状态码
response.statusCode = 404;
response.end('<h1>404 Not Found</h1>');
}else{
//响应文件内容
response.end(data);
}
})
});
//3. 监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动,端口9000监听中...');
});
// http.createServer 里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
5.5 设置资源类型(mime类型)
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源:
mime 类型结构: [type]/[subType]
常见:text/html text/css image/jpeg image/png application/json
对于未知的资源类型,可以选择
application/octet-stream
类型,浏览器在遇到该类型的响应
时,会对响应体内容进行独立存储,也就是我们常见的下载
效果
5.6 GET 和 POST 请求场景小结
GET 请求的情况:
- 在地址栏直接输入 url 访问
- 点击 a 链接
- link 标签引入 css
- script 标签引入 js
- img 标签引入图片
- form 标签中的 method 为 get (不区分大小写)
- ajax 中的 get 请求
POST 请求的情况:
- form 标签中的 method 为 post(不区分大小写)
- AJAX 的 post 请求
6. Node.js 模块化
6.1 介绍
- 什么是模块化与模块 ?
将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为
模块化
,其中拆分出的每个文件就是一个模块
,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用
- 什么是模块化项目 ?
编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
- 模块化好处
- 防止命名冲突
- 高复用性
- 高维护性
6.2 初体验
*******************me.js
function tiemo() {
console.log('贴膜...');
}
//暴露数据
module.exports = tiemo;
*******************index.js
var tiemo2 = require('./me');
tiemo2();
console.log(tiemo2);
6.3 暴漏数据
模块暴露数据的方式有两种:
module.exports = value
exports.name = value
无论如何修改到处对象,最终导出的都是module.exports
的值.
*******************me.js
function tiemo() {
console.log('贴膜...');
}
function niejiao() {
console.log('捏脚...');
}
//暴露数据
module.exports = {
niejiao,
tiemo
};
*******************index.js
//方式1
var me = require('./me');
console.log(me)
me.niejiao();
//方式2:()解构))
//var {niejiao} = require('./me');
//var {niejiao:AA} = require('./me');//重命名
//console.log(niejiao)
//niejiao();
6.4 导入模块
在模块中使用 require 传入文件路径即可引入文件
require 使用的一些注意事项:
- 对于自己创建的模块,导入时路径建议写
相对路径
,且不能省略./ 和../
js
和json
文件导入时可以不用写后缀,c/c++
编写的 node 扩展文件也可以不写后缀,但是一般用不到- 如果导入其他类型的文件,会以
js
文件进行处理- 如果导入的路径是个文件夹,则会
首先
检测该文件夹下package.json
文件中main
属性对应的文件,如果存在则导入,反之如果文件不存在会报错。如果 main 属性不存在,或者package.json
不存在,则会尝试导入文件夹下的index.js 和index.json
,如果还是没找到,就会报错- 导入 node.js 内置模块,
或第三方模块
时,直接 require 模块的名字(包名
)即可,无需加./ 和 ../
6.5 导入自定义模块的基本流程
这里我们介绍一下
require
导入自定义模块
的基本流程
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)。通过
arguments.callee.toString()
查看自执行函数- 缓存模块的值
- 返回
module.exports
的值
6.6 CommonJS 规范
module.exports 、 exports
以及require
这些都是CommonJS
模块化规范中的内容。而 Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScript 与 ECMAScript
6.7 ES6模块和CommonJS模块比对
在JavaScript中,
import
和require
都是用来导入模块的方式,但它们属于不同的模块系统,并且主要在ES6模块(ECMAScript 2015及以后版本)和CommonJS模块系统中使用。
- ES6模块(使用
import
和export
)
语法:使用
import
从其他模块导入绑定(变量、函数、类等),使用export
导出绑定以便其他模块可以导入。
静态结构:import
和export
的语句在编译时解析,这有助于JavaScript引擎进行更好的静态分析和优化(如树摇(tree-shaking
))。
语法更简洁:与CommonJS
的require
和module.exports
相比,import
和export
的语法更简洁、更直观。
默认导出与具名导出:ES6模块支持默认导出(每个模块只能有一个)和具名导出(可以导出多个)。
浏览器支持:原生支持ES6模块的现代浏览器可以直接使用import
和export
。
- CommonJS模块(使用
require
和module.exports
)
语法:使用
require
函数来导入模块,使用module.exports
或exports
来导出模块中的成员。
动态结构:require
函数在运行时执行,这意呀着它可以动态地解析依赖。
语法较复杂:与ES6模块的import
和export
相比,CommonJS的require和module.exports语法略显复杂。
Node.js:Node.js最初实现了CommonJS模块规范,但自Node.js v12起,它开始原生支持ES6模块(尽管需要通过特定的配置或使用.mjs扩展名)。
- 区别总结
语法:import/export vs require/module.exports/exports。
静态与动态:ES6模块是静态的,CommonJS模块是动态的。
浏览器支持:ES6模块在现代浏览器中受到原生支持,而CommonJS模块主要用于Node.js环境(尽管可以通过工具如Browserify或Webpack在浏览器中使用)。
使用场景:新项目倾向于使用ES6模块,因为它们提供了更好的静态分析、更简洁的语法和更广泛的浏览器支持。然而,由于Node.js的广泛使用和大量现有代码库的存在,CommonJS模块仍然非常普遍。
- 混用
在Node.js中,你可以通过特定的配置(如type: "module"在package.json中)来让项目支持ES6模块,但这意味着你需要将所有模块都迁移到ES6模块语法。然而,在单个项目中混用ES6模块和CommonJS模块通常是不推荐的,因为它可能会导致意外的行为和不一致性。
6.8 ES6暴漏数据
在ES6模块中,你可以使用export关键字来导出函数、对象或原始值,以便其他模块可以使用它们。有两种主要的导出方式:
默认导出
(每个模块只能有一个)和具名导出
(可以导出多个)
- 默认导出
// math.js
export default function sum(a, b) {
return a + b;
}
- 具名导出
// math-utils.js
export function multiply(a, b) {
return a * b;
}
export function subtract(a, b) {
return a - b;
}
- 统一导出
var name = '张三'
var solgan = '相信明天会更好'
function getTel() {
return '15935150771';
}
function getHobby() {
return ['抽烟', '喝酒', '烫头']
}
export {name, solgan, getTel, getHobby}//不是对象
- 导入默认导出
// app.js
import sum from './math.js';
//等价于,import { default as xxx } from "/xxx",默认暴漏相当于{ default : a }
console.log(sum(2, 3)); // 输出: 5
- 导入具名导出
// app.js
import { multiply, subtract } from './math-utils.js';
console.log(multiply(2, 3)); // 输出: 6
console.log(subtract(5, 3)); // 输出: 2
你也可以使用as关键字为导入的绑定重命名:
import { multiply as mul, subtract } from './math-utils.js'; console.log(mul(2, 3)); // 输出: 6
- 导入整个模块
虽然不常见,但你也可以使用星号(*)语法来导入整个模块的内容,但这通常会导致命名冲突,因此不推荐这样做。
// 注意:不推荐这样做,因为它会将math-utils.js中导出的所有内容导入到一个名为mathUtils的对象中
import * as mathUtils from './math-utils.js';
console.log(mathUtils.multiply(2, 3)); // 输出: 6
6.9 暴漏数据注意事项
浏览器默认不支持
ES6
模块和commonjs模块
- 在使用ES6模块时,请确保你的环境(如浏览器或Node.js)支持它们。
- 在Node.js中,你需要将文件扩展名从.js更改为.mjs,或者在
package.json
中设置"type": "module"
,以便Node.js将.js文件作为ES6模块处理。(将ES6模块
语法支持在node
运行)- ES6模块是静态解析的,这意味着在编译时就会确定模块的依赖关系,这有助于进行静态分析和优化(如树摇)。
- 与CommonJS模块不同,ES6模块中的代码只会在被导入时执行一次(即,模块是单例的)。
- 浏览器通过
<script type="module">
标签来识别和处理 ES6 模块。如果你只是使用<script>
标签而不指定type="module"
,那么浏览器会将其视为传统的脚本,并忽略 ES6 模块的特性。(将ES6模块
语法支持在浏览器
运行)<script type="module"> import sum from './me.js'; console.log(sum(1, 2)); </script>
type="module"
是 HTML<script>
标签的一个属性,它告诉浏览器该<script>
标签包含的是一个 JavaScript ES6 模块。在 HTML 文档中引入 JavaScript 代码时,通常使用<script>
标签,而默认情况下,浏览器会将<script>
标签内的代码作为传统的脚本(即非模块化的 JavaScript)来执行。
- 使用browserify插件,编译后进行过打开(将
Node模块
语法支持在浏览器
运行)npm i browserify -g browserify index.js -o build.js ---------** <script type="text/javascript" src="build.js" ></script>
7 包管理
7.1 介绍
- 包是什么
『包』英文单词是 package ,代表了一组特定功能的源码集合
- 包管理工具
管理『包』的应用软件,可以对「包」进行
下载安装
,更新
,删除
,上传
等操作借助包管理>工具,可以快速开发项目,提升开发效率包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以掌握好包管理工具非常重要
- 常用的包管理工具
下面列举了前端常用的包管理工具
npm
yarn
cnpm
7.2 npm
npm 全称
Node Package Manager
,翻译为中文意思是『Node 的包管理工具』
npm 是 node.js 官方内置的包管理工具,是必须要掌握住的工具
7.2.1 npm的安装 npm -v
node.js 在安装时会 自动安装 npm
,所以如果你已经安装了 node.js,可以直接使用 npm
可以通过 npm -v
查看版本号测试,如果显示版本号说明安装成功,反之安装失败
7.2.2 初始化: npm init (下载包前要初始化)
创建一个空目录,然后以此目录作为工作目录 启动命令行工具
,执行 npm init
npm init
命令的作用是将文件夹初始化为一个『包』,交互式创建 package.json 文件
package.json
是包的配置文件,每个包都必须要有package.json
package.json 内容示例:{ "name": "1-npm", #包的名字 "version": "1.0.0", #包的版本 "description": "", #包的描述 "main": "index.js", #包的入口文件 "scripts": { #脚本配置 "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", #作者 "license": "ISC" #开源证书 }
初始化的过程中还有一些注意事项:
- package name (
包名
) 不能使用中文、大写,默认值是文件夹的名称
,所以文件夹名称也不能使用中文和大写- version (
版本号
)要求x.x.x
的形式定义,x
必须是数字,默认值是1.0.0
- ISC 证书与 MIT 证书功能上是相同的,关于开源证书扩展阅读
http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html
package.json
可以手动创建与修改- 使用
npm init -y
或者npm init --yes
极速创建 package.json
7.2.3 搜索包
- 搜索包的方式有两种
- 命令行 『npm s/search 关键字』 eg:
npm s uniq
- 网站搜索 网址是 https://www.npmjs.com/
- 搜索包版本号
npm view xxx versions
eg:npm view less-loader versions
7.2.4 下载&删除安装包
# 格式
npm install <包名> `(多个包可以用空格分隔开)`
npm i <包名> `(多个包可以用空格分隔开)`
# 示例
npm install uniq
npm i uniq
# 指定版本号
## 格式
npm i <包名@版本号>
## 示例
npm i jquery@1.11.2
## 局部删除
npm remove uniq
npm r uniq
## 全局删除
npm remove -g nodemon
运行之后文件夹下会增加两个资源
node_modules
文件夹 存放下载的包package-lock.json
包的锁文件 ,用来锁定包的版本
安装 uniq 之后, uniq 就是当前这个包的一个
依赖包
,有时会简称为依赖
比如我们创建一个包名字为 A,A 中安装了包名字是 B,我们就说B 是 A 的一个依赖包
,也会说
A 依赖 B
7.2.4.1 安装包报错处理
- ssl认证问题
npm set strict-ssl=false
7.2.5 require 导入 npm 包基本流程
- 在当前文件夹下 node_modules 中寻找同名的文件夹
- 在上级目录中下的 node_modules 中寻找同名的文件夹,直至找到磁盘根目录
总结
- require导入内置模块
require('http')
- require导入自定义模块
require('./aa.js')
- require导入npm包
require('uniq')
7.2.6 生产环境与开发环境
开发环境是程序员
专门用来写代码
的环境,一般是指程序员的电脑,开发环境的项目一般只能程序员自己访问
生产环境是项目代码正式运行
的环境,一般是指正式的服务器电脑,生产环境的项目一般每个客户都可以访问
7.2.7 生产依赖与开发依赖
我们可以在安装时设置选项来区分 依赖的类型
,目前分为两类:
类型 | 命令 | 补充 |
---|---|---|
生产依赖 | npm i -S uniq npm i --save uniq | -S 等效于 --save, -S 是默认选项 包信息保存在 package.json 中 dependencies 属性 |
开发依赖 | npm i -D less npm i --save-dev less | -D 等效于 --save-dev 包信息保存在 package.json 中 devDependencies 属性 |
举个例子方便大家理解,比如说做蛋炒饭需要
大米 , 油 , 葱 , 鸡蛋 , 锅 , 煤气 , 铲子
等
其中锅 , 煤气 , 铲子
属于开发依赖,只在制作阶段使用
而大米 , 油 , 葱 , 鸡蛋
属于生产依赖,在制作与最终食用都会用到
所以开发依赖
是只在开发阶段使用的依赖包,而生产依赖
是开发阶段和最终上线运行阶段都用到
的依赖包
7.2.8 全局安装
- 我们可以执行安装选项
-g
进行全局安装
npm i -g nodemon # nodemon http.js 热启动命令,
注意,也可以局部安装,但是使用nodemon需要使用
npx nodemon http.js
临时运行安装在项目中的 nodemon,而不需要全局安装
全局安装完成之后就可以在命令行的
任何位置
运行nodemon
命令,该命令的作用是 自动重启 node 应用程序
说明:
全局安装的命令不受工作目录位置影响
可以通过npm root -g
可以查看全局安装包的位置
不是所有的包都适合全局安装
, 只有全局类的工具才适合,可以通过 查看包的官方文档来确定
安装方式
,这里先不必太纠结
7.2.9 安装包依赖
在项目协作中有一个常用的命令就是 npm i ,通过该命令可以依据 package.json
和 package-lock.json
的依赖声明安装项目依赖
node_modules 文件夹大多数情况都不会存入版本库
npm i
npm install
npm install --legacy-peer-deps忽略 peer dependencies 的版本冲突
7.2.10 安装指定版本的包
项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包,可以使用下面的命令的
npm i <包名@版本号>
npm i jquery@1.11.2
7.2.11 配置命令别名
通过配置命令别名可以更简单的执行命令
配置 package.json
中的 scripts
属性
{
.
.
.
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
.
.
}
- 配置完成之后,可以使用别名执行命令
npm run server
npm run start
不过start
别名比较特别,使用时可以省略run
npm start
补充说明:
npm start 是项目中常用的一个命令,一般用来启动项目
npm run
有自动向上级目录查找的特性,跟require
函数也一样
对于陌生的项目,我们可以通过查看scripts
属性来参考项目的一些操作
7.2.10 安装cnpm
方式一
:通过 npm 来安装 cnpm 工具
# 直接安装cnpm命令, 原来npm命令并不变,使用cnpm才会去淘宝进行加载
npm install -g cnpm --registry=https://registry.npmmirror.com
命令和npm一样,
# 初始化:cnpm init;
# 安装: cnpm i uniq;
# 删除: cnpm r uniq;
方式二
:npm 配置淘宝镜像 (npm配置淘宝镜像比cnpm命令快一些
)
# 修改npm的镜像配置--直接配置(使用npm即用的淘宝镜像)
npm config set registry https://registry.npmmirror.com/
# 修改npm的镜像配置--工具配置(使用npm即用的淘宝镜像)
npm i -g nrm #安装 nrm
nrm use taobao #修改镜像
npm config list #检查是否配置成功(选做) 检查 registry 地址是否为 https://registry.npmmirror.com/ , 如果 是 则表明成功
nrm ls #也可查看, *号的是当前镜像链接
方式二
:npm 使用工具进行切换(nrm)
npm install -g nrm
nrm ls //查看镜像源, *表示正在使用的
npm ---------- https://registry.npmjs.org/
yarn --------- https://registry.yarnpkg.com/
tencent ------ https://mirrors.cloud.tencent.com/npm/
cnpm --------- https://r.cnpmjs.org/
* taobao ------- https://registry.npmmirror.com/
npmMirror ---- https://skimdb.npmjs.com/registry/
nrm use taobao //切换
补充说明:
建议使用第二种方式
进行镜像配置,因为后续修改起来会比较方便- 虽然 cnpm 可以提高速度,但是 npm 也可以通过淘宝镜像进行加速,所以
npm 的使用率还 是高于 cnpm
7.3 yarn
7.3.1 yarn介绍
yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方网址:
https://yarnpkg.com/
- yarn特点
yarn 官方宣称的一些特点
- 速度超快:yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快
- 超级安全:在执行代码之前,yarn 会通过算法校验每个安装包的完整性
- 超级可靠:使用详细、简洁的锁文件格式和明确的安装算法,yarn 能够保证在不同系统上无差异的工作
7.3.2 yarn安装
我们可以使用 npm 安装 yarn
npm i -g yarn
7.3.3 yarn常用命令
功能 | 命令 |
---|---|
初始化 | yarn init / yarn init -y |
安装包 | yarn add uniq 生产依赖 yarn add less --dev 开发依赖 yarn global add nodemon 全局安装 |
删除包 | yarn remove uniq 删除项目依赖包 yarn global remove nodemon 全局删除包 |
安装项目依赖 | yarn |
运行命令别名 | yarn <别名> # 不需要添加 run |
7.3.4 yarn 配置淘宝镜像
yarn config set registry https://registry.npmmirror.com/
#可以通过 yarn config list 查看 yarn 的配置项
7.4 npm 和 yarn 选择
大家可以根据不同的场景进行选择
- 个人项目
如果是个人项目,哪个工具都可以
,可以根据自己的喜好来选择- 公司项目
如果是公司要根据项目代码来选择,可以通过锁文件判断
项目的包管理工具
npm 的锁文件为package-lock.json
yarn 的锁文件为yarn.lock
包管理工具 不要混着用,切记,切记,切记
7.6 发布包
我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:
- 创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露
- npm 初始化工具包,package.json 填写包的信息 (包的名字是唯一的)
- 注册账号
https://www.npmjs.com/signup
- 激活账号 (
一定要激活账号
)(chenyj123)- 修改为官方的官方镜像 (命令行中运行
nrm use npm
)- 命令行下 npm login 填写相关用户信息
- 命令行下
npm publish
提交包 👌 (chenyj_math)
7.7 更新包
后续可以对自己发布的包进行更新,操作步骤如下
- 更新包中的代码
- 测试代码是否可用
- 修改
package.json
中的版本号 (一定要修改,远端已经存在之前版本,也可删除重新发布)- 发布更新 (npm publish)
7.8 删除包
执行如下命令删除包
npm unpublish --force
删除包需要满足一定的条件,
https://docs.npmjs.com/policies/unpublish
你是包的作者
发布小于 24 小时
大于 24 小时后,没有其他包依赖,并且每周小于 300 下载量,并且只有一个维护者
(写的哪个版本,就会删除哪个版本)
7.7 扩展内容
在很多语言中都有包管理工具,比如:
除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是『 软件包 』
8. nvm
8.1 介绍&安装使用
- nvm 全称
Node Version Manager
顾名思义它是用来管理 node 版本的工具,方便切换不同版本的Node.js - nvm 的使用非常的简单,跟 npm 的使用方法类似
- 下载安装
首先先下载 nvm,下载地址
https://github.com/coreybutler/nvm-windows/releases
,
选择nvm-setup.exe
下载即可(网络异常的小朋友可以在资料文件夹中获取)
切换镜像源:
nvm node_mirror https://npmmirror.com/mirrors/node/
nvm npm_mirror https://npmmirror.com/mirrors/npm/
8.2 常用命令
9. express
9.1 express 介绍
express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:
https://www.expressjs.com.cn/
简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)
9.2 express 使用
9.2.1 初体验
- 下载
express 本身是一个 npm 包,所以可以通过 npm 安装
npm init
npm i express
- js代码
var express = require('express');
//创建应用
var app = express();
//创建路由规则
app.get('/home',(req,res)=>{
console.log('有人请求服务器1了');
res.end('hello express server');
});
// 监听端口 启动服务
app.listen(3000,()=>{
console.log('服务已经启动, 端口监听为 3000...')
})
- 启动
`node <文件名>`
# 或者
`nodemon <文件名>`
- 然后在浏览器就可以访问 http://127.0.0.1:3000/home 👌
9.2.2 express 路由
官方定义:
路由确定了应用程序如何响应客户端对特定端点的请求
一个路由的组成有 请求方法 , 路径 和 回调函数
组成
express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:app.<method>
(path
,callback
)
var express = require('express');
//创建应用
var app = express();
//创建路由规则
app.get('/home',(req,res)=>{
console.log('有人请求home页了');
res.send('hello express server');
});
app.get('/',(req,res)=>{
console.log('有人请求shou页了');
res.send('hello express server');
});
//创建 post 路由
app.post('/login',(req,res)=>{
res.send('登录成功');
});
//匹配所有的请求方法
app.get('/search',(req,res)=>{
console.log('有人请求search页了');
res.send('hello express server');
});
//匹配所有的请求方法
app.all('/search2', (req, res) => {
res.send('1 秒钟为您找到相关结果约 100,000,000 个');
});
//自定义 404 路由 ,应该放在最下面,路由会从上到下匹配
app.get('*',(req,res)=>{
res.send('<h1>404 Not Found</h1>');
});
// 监听端口 启动服务
app.listen(3000,()=>{
console.log('服务已经启动, 端口监听为 3000...')
})
- 注意:验证post的方法,目前已知有
ajax
及表单
,验证html如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>home</h1>
<form action="http://localhost:3000/login" method="post">
<input type="submit">
</form>
</body>
</html>
9.2.3 获取请求参数
express 框架封装了一些 API 来方便获取请求报文中的数据,并且兼容原生 HTTP 模块的获取方式
app.get('/login',(req,res)=>{
console.log('有人请求login页了');
// 1. 获取报文的方式与原生 HTTP 获取方式是兼容的 【原生操作】
console.log(req.method);//GET
console.log(req.url);// //login?username=sadasd
console.log(req.httpVersion);// 1.1
console.log(req.headers);
// 2. express 独有的获取报文的方式 【express独有操作】
//获取查询字符串
console.log(req.path)//『相对重要』 /login 等价于:require('url').parse(request.url).pathname
console.log(req.query)//『相对重要』{ username: 'sadasd' } 等价于:require('url').parse(request.url,true).query
console.log(req.params.username)//『相对重要』{ username: 'sadasd' }
// 获取指定的请求头
console.log(req.get('host'));
res.send('请求报文的获取');
});
9.2.4 获取路由参数
eg:https://npcitem.jd.hk/10051796795661.html , 取得10051796795661参数
var express = require('express');
//创建应用
var app = express();
// eg: https://npcitem.jd.hk/10051796795661.html
//创建路由规则
app.get('/:id.html',(req,res)=>{
console.log('有人请求home页了');
res.send('商品详情, 商品 id 为'+req.params.id);
});
// 监听端口 启动服务
app.listen(3000,()=>{
console.log('服务已经启动, 端口监听为 3000...')
})
9.2.5 相应设置参数
//获取请求的路由规则
app.get("/response", (req, res) => {
//1. express 中设置响应的方式兼容 HTTP 模块的方式
res.statusCode = 404;
res.statusMessage = 'xxx';
res.setHeader('abc','xyz');
res.write('响应体');
res.end('xxx');
//2. express 的响应方法
res.status(500); //设置响应状态码
res.set('xxx','yyy');//设置响应头
res.send('中文响应不乱码');//设置响应体
//连贯操作
res.status(404).set('xxx','yyy').send('你好朋友')
//3. 其他响应
res.redirect('http://atguigu.com')//重定向
res.download('./package.json');//下载响应
res.json();//响应 JSON
res.sendFile(__dirname + '/home.html') //响应文件内容 只接受绝对路径作为参数,因此不能直接使用相对路径。
});
9.3 express 中间件
中间件(Middleware)本质是一个回调函数
中间件函数 可以像路由回调一样访问 请求对象(request)
, 响应对象(response)
中间件的作用
就是 使用函数封装公共操作,简化代码
9.3.1 定义全局中间件
每一个请求
到达服务端之后 都会执行全局中间件函数
let recordMiddleware = function(request,response,next){
//实现功能代码
//.....
//执行next函数(当如果希望执行完中间件函数之后,仍然继续执行路由中的回调函数,必须调用next)
next();
}
#应用中间件
app.use(recordMiddleware);
9.3.2 多个全局中间件
app.use(function (request, response, next) {
console.log('定义第一个中间件');
next();
})
app.use(function (request, response, next) {
console.log('定义第二个中间件');
next();
})
9.3.4 定义路由中间件
如果 只需要对某一些路由进行功能封装
,则就需要路由中间件
app.get('/路径',`中间件函数`,(request,response)=>{
});
app.get('/路径',`中间件函数1`,`中间件函数2`,(request,response)=>{
});
9.4 静态资源中间件
express 内置处理静态资源的中间件
//引入express框架
const express = require('express');
//创建服务对象
const app = express();
//静态资源中间件的设置,将当前文件夹下的public目录作为网站的根目录
app.use(express.static('./public')); //当然这个目录中都是一些静态资源
//如果访问的内容经常变化,还是需要设置路由
//但是,在这里有一个问题,如果public目录下有index.html文件,单独也有index.html的路由,
//则谁书写在前,优先执行谁
app.get('/index.html',(request,response)=>{
respsonse.send('首页');
});
//监听端口
app.listen(3000,()=>{
console.log('3000 端口启动....');
});
注意事项:
- index.html 文件为默认打开的资源
- 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
- 路由响应动态资源,静态资源中间件响应静态资源
9.5 获取请求体数据body-parser
获取请求体数据 body-parser
- 安装&导包
npm i body-parser
const bodyParser = require('body-parser');
- 获取中间件函数
//处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({extended:false});
//处理 JSON 格式的请求体
let jsonParser = bodyParser.json();
- 设置路由中间件,然后使用
request.body
来获取请求体数据
app.post('/login', urlParser, (request,response)=>{
//获取请求体数据
//console.log(request.body);
//用户名
console.log(request.body.username);
//密码
console.log(request.body.userpass);
response.send('获取请求体数据');
});
9.6 图片防盗链
var express = require('express');
//创建应用
var app = express();
//声明中间函数
function recordMiddleware(req,res,next){
var referer = req.get('referer');// 获取指定的请求头
if (referer) {
var url = new URL(referer);
var hostname = url.hostname;
console.log(hostname);
console.log(hostname !== 'localhost');
if (hostname !== 'localhost') {
console.log('f')
res.status(404).send(`<h1>404 Not found</h1>`);
return;
}
}
console.log('继续执行')
next();
}
app.use(recordMiddleware);
app.use(express.static(__dirname+'/public')); //要放在后面
// 监听端口 启动服务
app.listen(3000,()=>{
console.log('服务已经启动, 端口监听为 3000...')
})
9.7 Router
9.7.2 Router 介绍
express 中的 Router 是一个完整的中间件和路由系统,可以看做是一个小型的 app 对象
。对路由进行模块化,更好的管理路由
9.7.1 Router 使用
- 创建独立的 JS 文件(homeRouter.js)
//1. 导入 express
const express = require('express');
//2. 创建路由器对象
const router = express.Router();
//3. 在 router 对象身上添加路由
router.get('/', (req, res) => {
res.send('首页');
})
router.get('/cart', (req, res) => {
res.send('购物车');
});
//4. 暴露
module.exports = router;
*主文件
const express = require('express');
const app = express();
//5.引入子路由文件
const homeRouter = require('./routes/homeRouter');
//6.设置和使用中间件
app.use(homeRouter);
//app.use('/', indexRouter);//路由(默认)
//app.use('/users', usersRouter);//路由前缀
app.listen(3000,()=>{
console.log('3000 端口启动....');
})
9.8 EJS模板引擎
模板引擎是分离
用户界面和业务数据
的一种技术 (分离html
和js
)
9.8.1 什么是 EJS
EJS 是一个高效的 Javascript 的模板引擎
官网:https://ejs.co/
中文站:https://ejs.bootcss.com/
//1.安装ejs
//2.导入ejs
var ejs = require('ejs');
var fs = require('fs');
//字符串
var china = '中国';
var weather = '大太阳';
var demo = fs.readFileSync('./13_模板引擎/demo.html','utf-8');
// //使用ejs渲染
// // var result = ejs.render('我爱你 <%= china %>',{china:china});
// // var result = ejs.render('我爱你 <%= china %>',{china});
var result = ejs.render(demo,{china,weather});
console.log(result);
9.8.2 EJS常用语法
执行JS代码
<% code %>
输出转义的数据到模板上
<%= code %>
输出非转义的数据到模板上
<%- code %>
9.8.3 express中使用EJS
npm i ejs
- home.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我爱你 <%= china %></h1>
<%=weather%>
</body>
</html>
- 15_模板引擎_express中使用ejs.js
var express = require('express');
var path = require('path');
var app = express();
var china='中国';
var weather = '大太阳';
//1.设置模板引擎
app.set('view engine','ejs');
//2.设置模板文件存放位置
app.set('views',path.resolve(__dirname,'./13_模板引擎'));
app.get('/home2',(req,res)=>{
res.setHeader("Content-Type","text/html;charset=utf-8")
//3.render响应,直接渲染到页面返回
res.render('home',{china,weather});
})
// 监听端口 启动服务
app.listen(3000,()=>{
console.log('服务已经启动, 端口监听为 3000...')
})
9.8.4 express-generator
npm install -g express-generator
全局安装命令
express -h
查看帮助
express -e <目录名>
或--view =ejs <目录名>
当前目录或指定目录初始化可直接创建)
express i
安装依赖
npm start
启动
9.8.5 文件上传
<form method="post" action="./fileDeal" enctype="multipart/form-data">
请输入姓名:<input type="text" name="username">
请上传文件:<input type="file" name="myfile">
<button>提交</button>
</form>
9.8.6 文件处理
- 引入包
npm i formidable@2.1.2
(高版本会报错) - formidable
var express = require('express');
var formidable = require('formidable');
var path = require('path');
var router = express.Router();
router.post('/fileDeal', (req, res) => {
const form = formidable({
//设置文件保存路径
uploadDir: path.resolve(__dirname , './public'),
//保持文件后缀
keepExtensions: true
});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
// console.log(fields)//属性集合
// console.log(files)//文件集合
//服务器保存该图片的访问 URL
// /images/8ad3d5e36012212ba7642c000.jpg
// let url = '/public/' + files.myfile.newFilename;// 将来将此数据保存在数据库中
let url = files.myfile.newFilename;// 将来将此数据保存在数据库中
// res.json({fields, files});
res.end(url);
});
})
10. Mongodb
10.1 介绍
MongoDB 是一个基于分布式文件存储的数据库(
并不是单纯的内存数据库
),官方地址https://www.mongodb.com/
操作语法与 JavaScript 类似,容易上手,学习成本低
Mongodb 中有三个重要概念需要掌握
数据库
(database) 数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存放很多集合
集合
(collection) 集合类似于 JS 中的数组,在集合中可以存放很多文档
文档
(document) 文档是数据库中的最小单位,类似于 JS 中的对象
10.2 下载与启动
下载地址: https://www.mongodb.com/try/download/community
建议选择 zip 类型, 通用性更强配置步骤如下:
- 将压缩包移动到 C:\Program Files 下,然后解压
- 创建 C:\data\db 目录,mongodb 会将数据默认保存在这个文件夹
- 以 mongodb 中 bin 目录作为工作目录,启动命令行
- 运行命令 mongod
- 访问http://localhost:27017 测试 或
启动客户端工具mongo.exe工具
自定义配置启动:
1.新建data log etc 3个目录
2.在etc下新建mongodb.conf配置文件
3.# db存放的目录
dbpath=D:\mongodb-windows-x86_64-5.0.24\mongodb-win32-x86_64-windows-5.0.24\data
# 后台启动需要配置日志输出
logpath=D:\mongodb-windows-x86_64-5.0.24\mongodb-win32-x86_64-windows-5.0.24\log\mongodb.log
4.运行命令启动
mongod --config D:\mongodb-windows-x86_64-5.0.24\mongodb-win32-x86_64-windows-5.0.24\etc\mongodb.conf
10.3 命令行交互
- 数据库操作
显示所有的数据库
show dbs
切换到指定的数据库,如果数据库不存在会自动创建数据库(刚创建不会显示,只有加入数据,才会创建)
use 数据库名
显示当前所在的数据库
db
删除当前数据库
use 库名
db.dropDatabase()
- 集合操作
创建集合
db.createCollection('集合名称')
显示当前数据库中的所有集合
show collections
删除某个集合
db.集合名.drop()
重命名集合
db.集合名.renameCollection('newName')
- 文档操作
插入文档
db.集合名.insert(文档对象);
查询文档
db.集合名.find(查询条件)
_id 是 mongodb 自动生成的唯一编号,用来唯一标识文档
更新文档
db.集合名.update(查询条件,新的文档)
默认全部子弹更新
db.集合名.update({name:'张三'},{$set:{age:19}})
指定字段更新
删除文档
db.集合名.remove(查询条件)
update 方法默认只会更新符合查询条件的第一条文档。这是因为在进行更新操作时,MongoDB默认只会更新匹配的第一条文档,除非你显式指定 multi: true 选项,这样才会更新所有匹配的文档。例如,你可以这样使用 update 方法来更新所有匹配的文档:
db.集合名.update(查询条件, 更新操作, { multi: true })
10.4 mongoose
Mongoose 是一个对象文档模型库,官网
http://www.mongoosejs.net/
方便使用代码操作 mongodb 数据库
npm i mongoose@5.13.15
10.4.1 初步使用
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称,没有会自动创建
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
});
//6. 创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('book', BookSchema);
//7. 新增文档
//7. 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
}, (err, data) => {
//判断是否有错误
if (err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
}
);
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
// 关闭 mongodb 的连接
// setTimeout(() => {
// mongoose.disconnect();
// }, 2000)
10.4.2 字段类型
10.4.3 字段值验证
Mongoose 有一些内建验证器,可以对字段值进行验证
- 必填项
title: {
type: String,
required: true // 设置必填项
},
- 默认值
author: {
type: String,
default: '匿名' //默认值
},
- 枚举值
gender: {
type: String,
enum: ['男','女'] //设置的值必须是数组中的
},
- 唯一值
username: {
type: String,
unique: true
},
## unique 需要 重建集合 才能有效果
let BookSchema = new mongoose.Schema({
name: {
type: String,
required: true, // 表明该属性必须不为空
unique: true// 设置为独一无二的
},
author: {
type: String,
default: '匿名'
},
//类型
style: {
type: String,
//枚举
enum: ['言情','城市','志怪','恐怖']
},
price: Number
});
10.4.4 CURD
数据库的基本操作包括四个,
增加(create),删除(delete),修改(update),查(read)
- 增加
插入一条
SongModel.create({
title:'给我一首歌的时间',
author: 'Jay'
}, function(err, data){
//错误
console.log(err);
//插入后的数据对象
console.log(data);
});
批量插入
PhoneModel.insertMany([
{
brand:'华为',
color:'灰色',
price:2399,
tags:['电量大','屏幕大','信号好']
},
{
brand:'小米',
color:'白色',
price:2099,
tags:['电量大','屏幕大','信号好']
}
],(err,data)=>{
if(err) throw err;
console.log('写入成功');
mongoose.connection.close();
})
- 删除
删除一条数据
SongModel.deleteOne({_id:'5dd65f32be6401035cb5b1ed'}, function(err){
if(err) throw err;
console.log('删除成功');
mongoose.connection.close();
});
批量删除
SongModel.deleteMany({author:'Jay'}, function(err){
if(err) throw err;
console.log('删除成功');
mongoose.connection.close();
});
- 更新
更新一条数据
SongModel.updateOne({author: 'JJ Lin'}, {author: '林俊杰'}, function (err) {
if(err) throw err;
mongoose.connection.close();
});
批量更新数据
SongModel.updateMany({author: 'Leehom Wang'}, {author: '王力宏'}, function (err) {
if(err) throw err;
mongoose.connection.close();
});
- 查询
查询一条数据
SongModel.findOne({author: '王力宏'}, function(err, data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
//根据 id 查询数据
SongModel.findById('5dd662b5381fc316b44ce167',function(err, data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
批量查询数据
//不加条件查询
SongModel.find(function(err, data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
//加条件查询
SongModel.find({author: '王力宏'}, function(err, data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
10.4.5 条件控制
- 运算符
在 mongodb 不能 > < >= <= !== 等运算符,需要使用替代符号
> 使用 $gt
< 使用 $lt
>= 使用 $gte
<= 使用 $lte
!== 使用 $ne
db.students.find({id:{$gt:3}}); id号比3大的所有的记录
- 逻辑运算
$or 逻辑或的情况
db.students.find({KaTeX parse error: Expected 'EOF', got '}' at position 23: …e:18},{age:24}]}̲); `and 逻辑与的情况db.students.find({$and: [{age: {$lt:20}}, {age: {$gt: 15}}]});
正则匹配条件中可以直接使用 JS 的正则语法,通过正则可以进行模糊查询
db.students.find({name:/imissyou/});`
10.4.6 个性化读取
- 字段筛选
//0:不要的字段
//1:要的字段
SongModel.find().select({_id:0,title:1}).exec(function(err,data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
- 数据排序
//sort 排序
//1:升序
//-1:倒序
SongModel.find().sort({hot:1}).exec(function(err,data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
- 数据截取
//skip 跳过 limit 限定
SongModel.find().skip(10).limit(10).exec(function(err,data){
if(err) throw err;
console.log(data);
mongoose.connection.close();
});
10.4.7 图形化管理工具
我们可以使用图形化的管理工具来对 Mongodb 进行交互,这里演示两个图形化工具
Robo 3T 免费 https://github.com/Studio3T/robomongo/releases
Navicat 收费 https://www.navicat.com.cn/
11. 接口
11.1 RESTful API
RESTful API 是一种特殊风格的接口,主要特点有如下几个:
- URL 中的路径表示 资源 ,路径中不能有
动词
,例如create , delete , update
等这些都不能有 - 操作资源要与 HTTP
请求方法
对应 - 操作结果要与 HTTP
响应状态码
对应
扩展阅读: https://www.ruanyifeng.com/blog/2014/05/restful_api.html
11.2 json-server
json-server 本身是一个 JS 编写的工具包,可以快速搭建 RESTful API 服务
官方地址: https://github.com/typicode/json-server
操作步骤:
- 全局安装 json-server
npm i -g json-server
- 创建 JSON 文件(db.json),编写基本结构
- 以 JSON 文件所在文件夹作为
工作目录
,执行如下命令
json-server --watch db.json
{
"song": [
{ "id": 1, "name": "干杯", "singer": "五月天" },
{ "id": 2, "name": "当", "singer": "动力火车" },
{ "id": 3, "name": "不能说的秘密", "singer": "周杰伦" }
]
}
12. 会话控制
所谓会话控制就是
对会话进行控制
HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端,无法区分用户
而产品中又大量存在的这样的需求,所以我们需要通过会话控制
来解决该问题.
常见的会话控制技术有三种:
- cookie
- session
- token
12.1 cookie
- cookie 是什么
cookie 是 HTTP 服务器发送到用户浏览器并保存在本地(
浏览器
)的一小块数据
cookie 是保存在浏览器端的一小块数据
cookie 是按照域名划分保存的
浏览器向服务器发送请求时,会自动将当前域名下
可用的 cookie 设置在请求头中,然后传递给服务器
这个请求头的名字也叫cookie
,所以将cookie 理解为一个 HTTP 的请求头也是可以的
不同浏览器中的 cookie 是相互独立的,不共享
- express中使用cookie
- 设置
//设置 cookie
app.get('/settt-cookie', (req, res) => {
console.log('settt-cookie');
// 不带时效性,会在浏览器关闭的时候,销毁
res.cookie('username', 'wangwu');
// 带时效性
res.cookie('email', '23123456@qq.com', {maxAge: 30 * 1000});
res.end('hello express server');
});
- 删除
//删除cookie
app.get('/removee-cookie', (req, res) => {
res.clearCookie('email');
res.send('Cookie的删除');
});
- 获取
## npm -i cookie-parser
const cookieParser = require('cookie-parser');
//3. 设置 cookieParser 中间件
app.use(cookieParser());
//读取 cookie
app.get('/gettt-cookie', (req, res) => {
console.log(req.cookies);
res.send('Cookie的读取');
});
12.2 session
session 是保存在
服务器端的一块儿数据
,保存当前访问用户的相关信息
实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息
var express = require('express');
//2. 引入 express-session connect-mongo(如果存入mongodb中的话)
const session = require("express-session");
//2. 引入 cookieParser 包
const cookieParser = require('cookie-parser');
const MongoStore = require('connect-mongo');
var app = express();
app.use(cookieParser());
//3. 设置 session 的中间件
app.use(session({
name: 'sid', //设置cookie的name,默认值是:connect.sid
secret: 'atguigu', //参与加密的字符串(又称签名)
saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
resave: true, //是否在每次请求时重新保存session,重新延长session存活周期
store: MongoStore.create({
mongoUrl: 'mongodb://114.115.185.179:27017/bilibili' //数据库的连接配置
}),
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 操作
maxAge: 1000 * 30 // 这一条 是控制 sessionID 的过期时间的!!! (浏览器和服务器session都过期)
},
}))
//创建 session
app.get('/login', (req, res) => {
//设置session
req.session.username = 'zhangsan';
req.session.email = 'zhangsan@qq.com'
res.send('登录成功');
})
//获取 session
app.get('/home', (req, res) => {
console.log('session的信息');
console.log(req.session.username);
console.log(req.cookies);
if (req.session.username) {
res.send(`你好 ${req.session.username}`);
}else{
res.send('登录 注册');
}
})
//销毁 session
app.get('/logout', (req, res) => {
//销毁session
// res.send('设置session');
req.session.destroy(() => {
res.send('成功退出');
});
});
// 监听端口 启动服务
app.listen(3000, () => {
console.log('服务已经启动, 端口监听为 3000...')
})
12.3 session和cookie的区别
cookie 和 session 的区别主要有如下几点
- 存在的位置
- cookie:浏览器端
- session:服务端
- 安全性
- cookie 是以明文的方式存放在客户端的,安全性相对较低
- session 存放于服务器中,所以安全性
相对
较好
- 网络传输量
- cookie 设置内容过多会增大报文体积, 会影响传输效率
- session 数据存储在服务器,只是通过 cookie 传递 id,所以不影响传输效率
- 存储限制
- 浏览器限制单个 cookie 保存的数据不能超过
4K
,且单个域名下的存储数量也有限制 - session 数据存储在服务器中,所以没有这些限制
- 浏览器限制单个 cookie 保存的数据不能超过
12.4 token
token
是服务端生成并返回给 HTTP 客户端的一串加密字符串, token
中保存着 用户信息
实现会话控制,可以识别用户的身份,主要用于移动端 APP
- token 的特点
- 服务端压力更小
- 数据存储在客户端
- 相对更安全
- 数据加密
- 可以避免 CSRF(跨站请求伪造)
- 扩展性更强
服务间可以共享
- 增加服务节点更简单
12.4.1 JWT
JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证 , JWT 使token
的生成与校验更规范
我们可以使用 jsonwebtoken
包 来操作 token
扩展阅读: https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
13. 模块工具使用
压缩文件archiver: https://blog.csdn.net/X1432564581/article/details/106398056/
14. console
- 打印对象完整信息
console.dir(obj, { depth: null, colors: true })
;
depth: null:确保对象完整展开。默认情况下,console.dir() 可能只展开一定深度的对象。
colors: true:在支持的终端中启用颜色。