八. Node.js模块化
1. 介绍
将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为模块化
其中拆分出的每个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用。
(1). 模块化的好处
- 防止命名冲突 2. 高复用性 3. 高维护性
2. 模块暴露数据
创建me.js
//声明函数
function tiemo() {
console.log('贴膜....');
}
//暴露数据
module.exports = tiemo;
创建index.js
//导入模块
const tiemo = require('./me.js');
//调用函数
tiemo();
(1). 暴露数据
模块暴露数据的方式有两种:
- module.exports = value
- exports.name = value
使用时有几点注意:
- module.exports 可以暴露 任意 数据
- 不能使用 exports = value 的形式暴露数据,模块内部 module 与 exports 的隐式关系 exports = module.exports = {} ,require 返回的是目标模块中 module.exports 的值
3. CommonJS 规范
module.exports、exports 以及 require 这些都是 CommonJS 模块化规范中的内容。
而Node.js是实现了CommonJS 模块化规范,二者关系有点像JavaScript与ECMAScript
九. 包管理工具
1. npm
npm全称 Node Package Manager , 翻译为中文意思是 Node的包管理工具
(1). npm基本使用
创建一个空目录,然后以此目录作为工具目录 启动命令行工具,执行 npm init
npm init 命令的作用是将文件夹初始化为一个 包 ,交互式创建 package.json 文件
package.json 是包的配置文件,每个包都必须要有 package.json
package.json 内容实例:
{
"name": "01_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/bl **
**og/2011/05/how_to_choose_free_software_licenses.html **
- package.json 可以手动创建与修改
- 使用 npm init -y 或者 npm init --yes 极速创建 package.json
2. 生产环境与开发环境
开发环境是程序员 专门用来写代码 的环境,一般是指程序员的电脑,开发环境的项目一般 只能程序员自己访问
生产环境是项目 代码正式运行 的环境,一般是指正式的服务器电脑,生产环境的项目一般 每个客户都可以访问
(1). 生产依赖与开发依赖
3. 全局安装
我们可以执行安装选项 -g 进行全局安装
npm i -g nodemon
全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令
该命令的作用是 自动重启 node 应用程序
(1). 修改windows执行策略
windows 默认不允许 npm 全局命令执行脚本文件,所以需要修改执行策略
- 以管理员身份打开powershell命令行
- 键入命令 set-ExecutionPolicy remoteSigned
- 键入 A 然后敲回车
4. 安装指定版本的包
项目中可能会遇到版本不匹配的情况,有事就需要安装指定版本的包,可以使用下面的命令
## 格式
npm i <包名@版本号>
## 示例
npm i jquery@1.11.2
5. 删除依赖
项目中可能需要删除某些不需要的包,可以使用下面的命令
## 局部删除
npm remove uniq
npm r uniq
## 全局删除
npm remove -g nodemon
6. 配置命令别名
通过配置命令别名可以更简单的执行命令
配置package.json中的scripts属性
{
.
.
.
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
.
.
}
配置完成之后,可以使用别名执行命令
npm run server
npm run start
用start的话 可以省略run
7. cnpm
淘宝镜像
(1). 安装
npm install -g cnpm --registry=https://registry.npmmirror.com
(2). npm配置淘宝镜像
用npm也可以使用淘宝镜像,配置的方式有两种
- 直接配置
- 工具配置
直接配置
npm config set registry https://registry.npmmirror.com/
工具配置
使用 nrm 配置 npm 的镜像地址 npm registry manager
- 安装nrm
npm i -g nrm
- 修改镜像
nrm use taobao
- 检查是否配置成功
npm config list
检查 registry 地址是否为 https://registry.npmmirror.com/ , 如果 是 则表明成功
8. yarn
yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方网址:https://yarnpkg.com/
npm i -g yarn
9. 管理发布包
(1). 创建与发布
我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:
- 创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露
- npm 初始化工具包,package.json 填写包的信息 (包的名字是唯一的)
- 注册账号 **https://www.npmjs.com/signup **
- 激活账号 ( 一定要激活账号 )
- 修改为官方的官方镜像 (命令行中运行 nrm use npm )
- 命令行下 npm login 填写相关用户信息
- 命令行下 npm publish 提交包
(2). 更新包
后续可以对自己发布的包进行更新,操作步骤如下
- 更新包中的代码
- 测试代码是否可用
- 修改 package.json 中的版本号
- 发布更新
npm publish
(3). 删除包
执行如下命令删除包
npm unpublish --force
删除包需要满足一定的条件,**https://docs.npmjs.com/policies/unpublish **
你是包的作者
发布小于 24 小时
大于 24 小时后,没有其他包依赖,并且每周小于 300 下载量,并且只有一个维护者
10. nvm
(1). 介绍
nvm 全称 Node Version Manager 顾名思义它是用来管理 node 版本的工具,方便切换不同版本的 Node.js
(2). 下载安装
首先先下载 nvm,下载地址 https://github.com/coreybutler/nvm-windows/releases,
选择 nvm-setup.exe 下载即可(网络异常的小朋友可以在资料文件夹中获取)
(3). 常用命令
十. express框架
1. 介绍
- express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:**https://www.expressjs.com.cn/ **
- 简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)
2. 使用
(1). 下载
npm i express
(2). 使用
//1. 导入 express
const express = require('express');
//2. 创建应用对象
const app = express();
//3. 创建路由规则
app.get('/home', (req, res) => {
res.send('hello express');
})
//4. 监听端口 启动服务
app.listen(4000, () => {
console.log('服务已经启动,端口1000 正在监听中');
})
3. express路由
(1). 什么是路由
官方定义: 路由确定了应用程序如何响应客户端对特定端点的请求
(2). 路由的使用
一个路由的组成有 请求方法 , 路径 和 回调函数 组成
express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:
app.(path,callback)
//导入 express
const express = require('express');
//创建应用对象
const app = express();
//创建 get 路由
app.get('/home', (req, res) => {
res.send('网站首页');
});
//首页路由
app.get('/', (req, res) => {
res.send('我才是真正的首页');
});
//创建 post 路由
app.post('/login', (req, res) => {
res.send('登录成功');
});
//匹配所有的请求方法
app.all('/search', (req, res) => {
res.send('1 秒钟为您找到相关结果约 100,000,000 个');
});
//自定义 404 路由
app.all("*", (req, res) => {
res.send('<h1>404 Not Found</h1>')
});
//监听端口 启动服务
app.listen(3000, () => {
console.log('服务已经启动, 端口监听为 3000');
});
(3). 获取请求参数
express 框架封装了一些 API 来方便获取请求报文中的数据,并且兼容原生 HTTP 模块的获取方式
//导入 express
const express = require('express');
//创建应用对象
const app = express();
//获取请求的路由规则
app.get('/request', (req, res) => {
//1. 获取报文的方式与原生 HTTP 获取方式是兼容的
console.log(req.method);
console.log(req.url);
console.log(req.httpVersion);
console.log(req.headers);
//2. express 独有的获取报文的方式
//获取查询字符串
console.log(req.query); // 『相对重要』
// 获取指定的请求头
console.log(req.get('host'));
res.send('请求报文的获取');
});
//启动服务
app.listen(3000, () => {
console.log('启动成功....')
})
(4). 获取路由参数
路由参数指的是 URL 路径中的参数(数据)
app.get('/:id.html', (req, res) => {
res.send('商品详情, 商品 id 为' + req.params.id);
});
小练习
根据路由参数响应歌手的信息
路径结构如下
/singers/1.html
显示歌手的姓名和图片
{
"singers": [
{
"singer_name": "周杰伦",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp",
"other_name": "Jay Chou",
"singer_id": 4558,
"id": 1
},
{
"singer_name": "林俊杰",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001BLpXF2DyJe2.webp",
"other_name": "JJ Lin",
"singer_id": 4286,
"id": 2
},
]
}
//1. 导入 express
const express = require('express');
//2. 创建应用对象
const app = express();
const { singers } = require('./singers.json');
// console.log(singers);
//3. 创建路由规则
app.get('/singers/:id.html', (req, res) => {
let { id } = req.params;
let result = singers.find(item => {
if (item.id == Number(id)) {
return true;
}
});
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>${result.singer_name}</h1>
<img src="${result.singer_pic}" alt="">
</body>
</html>`);
})
//4. 监听端口 启动服务
app.listen(4000, () => {
console.log('服务已经启动, 端口4000 正在监听中');
})
4. express响应设置
express 框架封装了一些 API 来方便给客户端响应数据,并且兼容原生 HTTP 模块的获取方式
//获取请求的路由规则
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://baidu.com')//重定向
res.download('./package.json');//下载响应
res.json();//响应 JSON
res.sendFile(__dirname + '/home.html') //响应文件内容
});
5. express中间件
(1). 什么是中间件
中间件(Middleware)本质是一个回调函数
中间件函数 可以像路由回调一样访问 请求对象(request) , 响应对象(response)
(2). 中间件的类型
- 全局中间件
- 路由中间件
(3). 定义全局中间件
每一个请求 到达服务端之后 都会执行全局中间件函数
声明中间件函数
let recordMiddleware = function (request, response, next) {
//实现功能代码
//.....
//执行next函数(当如果希望执行完中间件函数之后,仍然继续执行路由中的回调函数,必须调用next)
next();
}
我把中间件理解为一个守卫,一个分流的守卫
应用中间件
app.use(recordMiddleware);
声明时可以直接将匿名函数传递给 use
app.use(function (request, response, next) {
console.log('定义第一个中间件');
next();
})
可以定义多个中间件
(3). 定义路由中间件
如果 只需要对某一些路由进行功能封装 ,则就需要路由中间件
app.get('/路径', `中间件函数`, (request, response) => {
});
app.get('/路径', `中间件函数1`, `中间件函数2`, (request, response) => {
})
(4). 静态资源中间件
express 内置处理静态资源的中间件
//4. 监听端口 启动服务
app.listen(4000, () => {
console.log('服务已经启动, 端口4000 正在监听中');
})
//引入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 文件为默认打开的资源
- 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
- 路由响应动态资源,静态资源中间件响应静态资源
(5). 获取请求体数据body-parser
express 可以使用 body-parser 包处理请求体
第一步:安装
npm i body-parser
第二步:导入 body-parser 包
const bodyParser = require(‘body-parser’);
第三步:获取中间件函数
//处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({ extended: false }));
//处理 JSON 格式的请求体
let jsonParser = bodyParser.json();
第四步:设置路由中间件,然后使用 request.body 来获取请求体数据
app.get('/login', (req, res) => {
res.sendFile(__dirname + '/index.html')
})
app.post('/login', urlParser, (req, res) => {
console.log(req.body);
res.send('获取用户的数据');
})
获取到的请求体数据:
6. Router
(1). 什么是Router
express中的Router是一个完整的中间件和路由系统,可以看做是一个小型的app对象。
(2). Router作用
对路由进行模块化,更好的管理路由
(3). 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.listen(3000, () => {
console.log('3000 端口启动....');
})
7. EJS模板引擎
(1). 什么是模板引擎
模板引擎是分离 用户界面和业务数据 的一种技术
(2). 什么是EJS
EJS是一个高效的 JavaScript 的模板引擎
官网:http://ejs.co/
中文站:http://ejs.bootcss.com/
(3). EJS使用
下载安装EJS
npm i ejs --save
//1.引入ejs
const ejs = require('ejs');
//2.定义数据
let person = ['张三', '李四', '王二麻子'];
//3.ejs解析模板返回结构
//<%= %> 是ejs解析内容的标记,作用是输出当前表达式的执行结构
let html = ejs.render('<%= person.join(",") %>', { person: person });
//4.输出结果
console.log(html);
(4). EJS常用语法
执行JS代码
<% code %>
输出转义的数据到模板上
<%= code %>
输出非转义的数据到模板上
<%- code %>
8
8. express-generator
express-generator文档
这个工具可以快速搭建骨架,不用自己一点点去编写
9. 文件上传功能
(1). 在routers下创建index.js
//显示网页的表单
router.get('/portrait',(req,res)=>{
res.render('portrait')
});
//处理文件上传
router.post('/portrait',(req,res)=>{
res.send('成功')
});
添加get和post方法
(2). 在views下创建portrait.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<h2>文件上传</h2>
<hr>
<form action="/portrait" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
头像:<input type="file" name="portrait"><br>
<hr>
<button>点击提交</button>
</form>
</body>
</html>
当地址栏url为http://localhost:3000/portrait时,会进入到portrait.ejs页面中
注意这里的enctype="multipart/form-data"一定要添加上去,要不然获取不到图片文件,放不到请求体里面
(3). formidable 处理文件请求包
//引入formidable
const formidable = require('formidable');
//处理文件上传
router.post('/portrait', (req, res) => {
//创建 form 表单对象
const form = formidable({ multiples: true });
//解析请求报文
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
console.log(fields);// text radio checkbox select
console.log(files); // file
res.send('ok')
});
});
我们可以看到这个方法中有 fields 和 files 两个参数
在 fields 中主要储存text radio checkbox select 类型的数据
在 files 中主要储存 file 类型的数据
(4). 存储图片
//创建 form 表单对象
const form = formidable({
multiples: true,
//设置上传文件的保存目录
uploadDir: __dirname + '/../public/images',
//保持文件后缀
keepExtensions: true,
});
加入 upliadDir 和 keepExtensions
upliadDir:设置上传文件的保存目录
keepExtensions:开启保持文件后缀功能
可以看到已经拿到图片了,但是将来用户还要访问该图片,所以还要保存图片的url。
加入将来用户要访问这张图片,那访问的url一定是http://localhost:3000/images/6015787a953a61594d413a500.jpg 这样的一个路径
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
// console.log(fields);// text radio checkbox select
// console.log(files); // file
///images/6015787a953a61594d413a500.jpg
//服务器保存该图片的访问 URL
let url = '/images/' + files.portrait.newFilename;
res.send(url)
});
当然,未来这一块肯定是要吧url存入数据库
10. 防盗链
有时候我们在html中引用外部图片地址,会发现图片显示不出来,是因为那个图片网站设置了防盗链。
原理:发送请求会查看请求头的referer是否为设置的域名,如果不是,就禁止访问
app.use((req, res, next) => {
let referer = req.get('referer');
if (referer) {
//实例化
let url = new URL(referer);
//获取 hostname
let hostname = url.hostname;
console.log(hostname);
//判断
if (hostname != '127.0.0.1') {
//响应404
res.status.(404).send('<h1>404 Not Found!</h1>');
return;
}
}
next();
})