在node中,通过require()函数来引入外部的模块,返回一个对象。参数是一个文件的路径。如果使用相对路径,必须以 . 或 .. 开头
let md = require('./xxx.js')
在Node中,一个js文件就是一个模块。
在Node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域。一个模块无法访问其他模块的变量或函数。
function (exports, require, module, __filename, __dirname) {
/*
exports:将变量或函数暴露到外部;
require:函数,引入外部的模块;
module:代表当前模块本身;
module.exports===exports(true)
__filename:当前模块的路径;
__dirname:当前模块所在文件夹的路径;
*/
const math = require("./math");
let x = 1,
y = 2;
console.log(math.add(x, y));
console.log(arguments.callee + "");
}
可以使用exports向外部暴露属性或方法。
可以通过模块标识找到某一个模块,路径是其中一种标识。
模块:
核心模块
node引擎提供的模块,直接引用 模块名字。
文件模块
用户自己创建的。
CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。
CommonJS的包规范由 包结构 和 包描述文件 两个部分组成。
包结构
用于组织包中的各种文件。
包实际上就是一个压缩文件,解压以后还原为目录。符合规范的目录,应该包含如下文件︰
package.json 描述文件
npm init -y // 快速创建package.json文件
package-lock.json 记录node_modules 目录下每个包的下载信息,例如名字、版本号、下载地址等;
bin 可执行二进制文件;
lib js代码;
doc 文档;
test 单元测试;
包描述文件
描述包的相关信息,以供外部读取分析。
步骤:
新建包文件夹,作为包的根目录
在文件夹中创建
package.json 包管理配置文件
{
"name": "szh-utils",
"version": "1.0.1",
"main": "index.js",
"description": "我的第一个工具包",
"keywords": ["szh", "format", "escape"],
"license": "ICS"
}
index.js 入口文件
const dateFormat = require('./dateFormat')
const escape = require('./htmlEscape')
module.exports = {
...dateFormat,
...escape
}
README.md 包的说明文档
#安装
```
npm install szh-utils
```
#导入包
```js
const szh_utils = require('szh-utils')
```
#格式化时间
```js
const dateStr = new Date()
const formatStr = szh_utils.dateFormat(dateStr)
console.log(formatStr);
```
#转义特殊字符
```js
const htmlStr = '测试 转义特殊字符
'
const escapeStr = szh_utils.htmlEscape(htmlStr)
console.log(escapeStr)
const unescapeStr = utils.unHtmlEscape(escapeStr)
console.log(unescapeStr)
```
发布包
到官网注册一个账号
https://www.npmjs.com/
在终端进行登录
npm login
# 依次输入用户名、密码、邮箱
把包发布到npm上
npm publish
删除已发布的包
npm unpublish 包名 --force
对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模之间形成了很好的一个生态系统。
NPM命令
npm – 帮助说明
npm search 包名 – 搜索模块包
npm remove/r 包名 --删除包
npm install/i 包名 --save --安装并添加到依赖中
npm install --根据package.json的配置下载所需要的包
npm install -g --全局安装包 (不止在项目里用的)
cnpm
淘宝镜像安装
npm install -g cnpm --registry=https://taobao.org
cnpm install xxx
切换npm的下包镜像源
# 查看当前的下包镜像源
npm config get registry
# 切换为淘宝的
npm config set registry = https://registry.npm.taobao.org/
nrm
# nrm 可以方便的进行镜像源的切换
# 安装
npm i nrm -g
# 查看
nrm ls
# 切换
nrm use taobao
i5ting_toc
可以将 md 文档转换成 html 页面的小工具。
npm install -g i5ting_toc
i5ting_toc -f md文档的路径 -o
node在使用模块名字来引入模块时,它会首先在当前目录的node_modules中寻找是否含有模块。
如果有则直接使用,没有则去上一级目录的node_modules中寻找,直至找到为止。如果在最后(磁盘根目录)依然没有找到,则会报错。
/*
* Buffer (缓冲区)
* 1.结构和操作方法和数组类似
* 2.数组不能存储二进制的文件,而buffer则是专门用来存储二进制数据的
* 3.它的元素为一个16进制的两位数,存储是二进制数据,显示是16进制形式
* 4.实际上一个元素就表示内存中的一个字节
* 5.实际上buffer中的内存不是通过JavaScript分配的,而是在底层通过C++申请 的
* 6.我们可以直接通过buffer来创建内存中的空间
* 7.使用buffer不需要引入模块,直接使用
* 8.buffer大小确定后不能重新修改,是对底层内存的直接操作
* */
const fs = require("fs");
/*
文件的写入:
1-打开文件
2-写入内容
3-保存关闭文件
*/
/*
* fs.openSync(path, flags[, mode]) 同步文件写入
* 返回一个表示文件描述符的整数
* path: 路径
* flags: 文件类型 r 只读 w 可写
* */
let fd1 = fs.openSync("hello.txt", "w");
// console.log(fd1);
/*
* fs.writeSync(fd, string[, position[, encoding]])
* 返回写入的字节数。
* fd: 文件对象
* string: 写入内容
* position: 可选 写入的起始位置
* encoding: 可选 默认utf-8
* */
let fw1 = fs.writeSync(fd1, "hello node");
// console.log(fw1);
/*
* fs.closeSync(fd)
* 返回 undefined。
* fd: 要关闭的文件
* */
let fc1 = fs.closeSync(fd1);
// console.log(fc1);
/*
* 异步文件写入
* 打开文件: fs.open(path, flags[, mode], callback)
* 没有返回值(异步方法没有返回值,结果在回调函数中返回)
* callback: 回调函数
* err: 错误对象,没有错误则为null
* fd: 文件描述符
* flags: 'a' 以追加模式打开文件
* 写入文件: fs.write(fd, string[, position[, encoding]], callback)
* callback 有三个参数 (err, written, string),
* written 指定传入的字符串被写入多少字节.
* 关闭文件: fs.close(fd, callback)
* callback 只有 err 一个参数
* */
fs.open("hello2.txt", "a", function (err, fd) {
console.log(arguments);
if (!err) {
console.log(fd);
fs.write(fd, "异步写入", function (err, written, string) {
if (!err) {
console.log(written);
console.log(string);
}
fs.close(fd, function (err) {
if (!err) {
console.log("异步文件写入已关闭...");
}
});
});
}
});
/*
* 同步任务不存在异常处理,如果有一个地方出现错误,则整个程序都会出错
* 异步任务可以去进行异常判断,避免因某个地方出错而影响整个程序
* */
/*
* 简单文件写入
* fs.writeFileSync(file, data[, options])
* fs.writeFile(file, data[, options], callback)
* file: 文件路径
* data: 写入数据
* options: 可选,对写入进行配置
* encoding: 默认utf-8
* mode: 默认0o666
* flag: 默认 w
* callback: 回调函数
* */
fs.writeFile("hello3.txt", "简单文件写入", { flag: "a" }, function (err) {
if (err) throw err;
console.log("The file has been saved!");
});
/*
* 流式文件写入
* 同步、异步、简单文件写入不适合大文件写入,性能较差,容易导致内存溢出
* 1-创建一个可写流 fs.createWriteStream(path[, options])
* 返回一个新建的 WriteStream 对象。
* path: 文件路径
* options: 可选
* 2-监听流的打开和关闭状态
* WriteStream对象.once("open",function(){}),once只在打开时执行一次
* WriteStream对象.once("close",function(){})
* WriteStream对象.on("open",function(){}),on 持续事件,过于浪费
* */
//创建
let ws = fs.createWriteStream("hello4.txt");
//监听
ws.once("open", function () {
console.log("监听写入流的打开");
});
ws.once("close", function () {
console.log("监听写入流的关闭");
});
// 持续写入
ws.write("通过写入流进行文件内容的写入...");
ws.write("111");
ws.write("222");
ws.write("333");
//关闭
ws.end();
const fs = require("fs");
/*
* 简单文件读取
* fs.readFileSync(path[, options])
* 如果指定了 encoding 选项,则该函数返回一个字符串,否则返回一个 buffer。
* fs.readFile(path[, options], callback)
* */
let fr1 = fs.readFileSync("hello4.txt");
console.log(fr1);
let fr2 = fs.readFileSync("hello3.txt", "utf-8");
console.log(fr2);
fs.readFile("hello.txt", (err, data) => {
if (!err) {
console.log(data.toString()); // 没有指定编码类型则返回 buffer类型
} else {
throw err;
}
});
fs.readFile("moon.png", (err, data) => {
if (!err) {
console.log(data); // 没有指定编码类型则返回 buffer类型
fs.writeFile("moon2.png", data, (err) => {
if (!err) {
console.log("复制图片成功");
}
});
} else {
throw err;
}
});
/*
* 流式读取
* 适合大文件,分多次读取
* fs.createReadeStream() 创建可读流
* rs.on("data",(data)=>{}) 绑定data事件
* 事件绑定完毕,自动开始读取数据
* */
let rs = fs.createReadStream("moon.png");
let ws = fs.createWriteStream("moon3.png");
rs.once("open", function () {
console.log("可读流打开");
});
rs.once("close", function () {
console.log("可读流关闭");
// 数据读取完毕,关闭可写流
ws.end();
});
ws.once("open", function () {
console.log("可写流打开");
});
ws.once("close", function () {
console.log("可写流关闭");
});
rs.on("data", (data) => {
console.log(data);
console.log(data.length);
ws.write(data);
});
/* pipe() 可以将可读流数据直接输出到可写流中 */
rs.pipe(ws);
//成绩.txt
小红=99 小白=100 小黄=70 小黑=66 小绿=88
//成绩_copy.txt
小红:99
小白:100
小黄:70
小黑:66
小绿:88
const fs = require("fs");
fs.readFile("./file/成绩.txt", "utf-8", (err, data) => {
if (err) throw err;
let str = data.replace(/\s/g, "\r\n");
str = str.replace(/=/g, ":");
fs.writeFile("./file/成绩_copy", str, (err) => {
if (err) throw err;
console.log("转换成功~");
});
});
验证路径是否存在
fs.existsSync(path)
获取文件信息
fs.stat(path,callback)
fs.statSync(path)
删除文件
fs.unlink(path,callback)
fs.unlinkSync(path)
在代码运行的时候,会以执行node命令时所处的目录,动态拼接处被操作文件的完成路径。
解决方法:使用绝对路径。
// __dirname表示当前文件所处的目录
fs.readFile(__dirname + '/file/成绩.txt',(err,data)=>{})
path模块是 Node.js 官方提供的,用来处理路径的模块,它提供了一系列的方法和数学。
const path = require("path")
path.join(): 把多个路径片段拼接为完整的路径字符串;
const pathStr = path.join(__dirname + "./file/hello.txt")
注意: ../会抵消上一层路径。
path.basename(): 获取路径的最后一部分;
path.extname():获取路径中的文件扩展名;
实现思路:
fs``path模块;readFile()读取html文件;/* 引入模块 */
const fs = require("fs");
const path = require("path");
/* 定义正则表达式,匹配style、script标签; */
const regStyle = /", "")
if (data) {
fs.writeFile(path.join(__dirname, "/clock/clock.css"), data, err => {
if (err) return console.log('css文件写入错误' + err.message);
console.log('css文件写入成功');
})
}
}
function resolveJs(model) {
const jsModel = regScript.exec(model)
const data = jsModel[0].replace("", "")
if (data) {
fs.writeFile(path.join(__dirname, "/clock/clock.js"), data, err => {
if (err) return console.log('js文件写入错误' + err.message);
console.log('js文件写入成功');
})
}
}
/* 调用模块fs的` readFile() `读取html文件 */
fs.readFile(__dirname + "/index.html", "utf-8", (err, data) => {
if (err) return console.log(err.message);
resolveHtml(data)
resolveCss(data)
resolveJs(data)
})
注意:writeFile()只会创建文件,不会创建路径。
在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
修改XAMPP Apache路径:
/*将下面两个改成想要存放网站的路径*/
DocumentRoot "D:/XAMPP/htdocs"
<Directory "D:/XAMPP/htdocs">
在 Node.js 中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 Node.js 提供的 http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务。
IP地址
IP 地址就是互联网上每台计算机的唯一地址,因此 IP 地址具有唯一性。
IP 地址的格式:通常用“点分十进制”表示成**(a.b.c.d)**的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例如:用点分十进表示的 IP地址(192.168.1.1)
注意:
- 互联网中每台 Web 服务器,都有自己的 IP 地址,例如:大家可以在 Windows 的终端中运行 ping www.baidu.com 命令,即可查看到百度服务器的 IP 地址;
- 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入 127.0.0.1 这个 IP 地址,就能把自己的电脑当做一台服务器进行访问了。
域名和域名服务器
尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址。
IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器。
注意:
在开发测试期间, 127.0.0.1 对应的域名是 localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别
端口号

步骤:
request事件,监听客户端的请求const http = require("http")
// 创建实例
const server = http.createServer()
// 绑定request
server.on('request', (req, res) => {
console.log('访问 web 服务器');
})
// 启动
server.listen(80, () => {
console.log('running at 80端口');
})
核心思路:把文件的实际存放路径,作为每个资源的请求URL地址。
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
https://www.expressjs.com.cn/
能做什么?
使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器。
Web 网站服务器:专门对外提供 Web 网页资源的服务器。
API 接口服务器:专门对外提供 API 接口的服务器。
express.static()可以非常方便地创建一个静态资源服务器, 例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了。
app.use(express.static('public'))
https://www.npmjs.com/package/nodemon
使用 nodemon 这个工具,它能够监听项目文件 的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
路由指的是客户端的请求与服务器处理函数之间的映射关系。
路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数。
创建路由模块对应的.js文件
调用express.Router()函数创建路由对象
const router = express.Router()
向路由对象上挂载具体的路由
router.get('/user/list', (req, res) => {
res.send('获取用户列表')
})
router.all('/user/add', (req, res) => {
res.send('添加用户')
})
使用module.exports向外共享路由对象
module.exports = router
使用app.use()函数注册路由模块
// 导入路由模块
const userRouter = require('./router')
// 注册路由模块
app.use('/api', userRouter)
完整代码
/*router.js*/
const express = require('express')
const router = express.Router()
router.get('/user/list', (req, res) => {
res.send('获取用户列表')
})
router.all('/user/add', (req, res) => {
res.send('添加用户')
})
module.exports = router
/*index.js*/
const express = require('express')
const app = express()
// 导入路由模块
const userRouter = require('./router')
// 注册路由模块
app.use('/api', userRouter)
app.listen(8080, () => {
console.log('监听8080端口,学习express路由模块');
})
中间件(Middleware),特指业务流程的中间处理环节。
作用
调用流程
格式

next()函数的作用
实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
简单中间件函数
/**
* 简单中间件
*/
const mw1 = function(req, res, next) {
console.log('简单中间件');
next()
}
全局中间件
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
/**
* 全局中间件
*/
const mw2 = function(req, res, next) {
console.log('全局中间件')
next()
}
app.use(mw2)
/**
* 简化形式
*/
app.use(function(req, res, next) {
console.log('简化形式')
next()
})
可以使用 app.use() 连续定义多个全局中间件。客户端请求到达 服务器之后,会按照中间件定义的先后顺序依次进行调用。
局部中间件
/**
* 局部中间件
*/
const mw3 = function(req, res, next) {
console.log('局部中间件')
next()
}
app.get('/', mw3, function(req, res) {
// 只在当前路由中生效
res.send('111')
})
应用级别
通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件。
/**
* 应用级别
*/
app.use((req, res, next) => {
next()
})
app.get('/', mw, (req, res) => {
res.send('局部')
})
路由级别
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。
/**
* 路由级别
*/
const router = express.Router()
router.use((req, res, next) => {
next()
})
app.use('/', router)
错误级别
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
/**
* 错误级别
*/
app.get('/', (req, res) => {
throw new Error('服务器内部发生了错误!')
res.send('111')
})
app.use((err, req, res, next) => {
console.log(err.message)
})
错误级别的中间件,必须注册在所有路由之后!
Express内置的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
url-encoded格式也叫
form格式或x-www-form-urlencoded格式;
第三方中间件
自定义中间件
需求:实现类似express.urlencoded中间件,用于解析POST提交的表单数据。
思路:
req的data事件,将请求的数据保存到自定义变量str中;req的end事件,在请求体发送完毕时触发,将变量str中的数据使用express内置模块querystring(现在已经被弃用了,但是还是可以使用)进行解析(可以将查询字符串=>对象格式),并将解析结果挂载回req.body上;实现:
/*自定义中间件.js*/
/**
* 内置模块:
* 用来处理查询字符串,
* 通过parse()函数,可以将查询字符串解析成对象格式
*/
const qs = require('querystring')
function myUrlencoded(req, res, next) {
let str = '' //用来保存请求体中的数据
req.on('data', chunk => {
str += chunk
})
req.on('end', function() {
// console.log(argument);
req.body = qs.parse(str)
next()
})
}
module.exports = myUrlencoded
/*router.js 中间件的路由模块*/
const express = require('express')
const router = express.Router()
router.all('/mw', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers', '*')
res.send('OK')
})
module.exports = router
/*test.js 测试模块*/
const express = require('express')
const app = express()
const mwRouter = require('./router')
const myUrlencoded = require('./自定义中间件')
app.use(myUrlencoded) // 中间件需要在路由之前注册
app.use(mwRouter)
app.listen(80, () => {
console.log('running at 80...');
})