Node.js
Node.js是一个跨平台的JavaScript运行环境,是开发者可以开发服务端的JavaScript应用程序
Node.js为何能执行JS?
- 浏览器能运行js代码,依靠的是内核中的V8引擎
- Node.js是基于Chrome V8引擎进行封装
- 都支持ECMAScript标准语法,Node.js有独立的API
- Node.js没有Dom和Bom对象
node.js的注意事项。
不能够使用bom和dom的API。但是可以使用console和定时器的API。
node.js有一个顶级对象:global。再node2020中引入了globalthis,他指向global对象。
buffer
buffer是一个类似于数组的对象,用于表示固定长度的字节序列
本质是一段内存空间,用来处理二进制。
特点
- Buffer大小固定且无法调整
- Buffer性能较好,直接对计算机内存进行操作
- 每一个元素的大小为一字节
使用
创建Buffer
1 2 3 4 5 6 7 8 9 10 11 12
| var buffer = Buffer.alloc(10) console.log(buffer);
var buffer_unsafe = Buffer.allocUnsafe(1000); console.log(buffer_unsafe)
var buffer_from = Buffer.from("hello"); console.log(buffer_from)
|
Buffer的相关操作
1 2 3 4 5 6 7 8 9 10
| var buffer_from_array = Buffer.from([105,108,111,118,101,121,111,117]);
console.log(buffer_from_array.toString())
var buffer_from = Buffer.from("hello"); console.log(buffer_from[0]);
console.log(buffer_from[0].toString(2))
|
注意事项
1 2 3 4 5 6 7
| var buffer = Buffer.alloc(10); buffer[0] = 166 console.log(buffer[0])
buffer[0] = 256 console.log(buffer[0])
|
fs模块
fs(File System),可以用来操作计算机上磁盘文件,实现文件的读写操作。
1 2 3 4 5 6 7 8 9 10 11
| const fs = require('fs')
fs.writeFile("./test.txt","Hello world",function (res){ if(!res){ console.log("success") } else { console.log("error"); } })
|
fs的工作模式
fs写入文件默认是异步的,写入文件的操作会交给其他线程完成,主线程不会等待IO操作。等到写入完成后,将回调函数压入到队列中,等待主线程执行完毕再执行回调函数。
fs写入文件的同步模式
1 2 3 4 5
|
const fs = require('fs')
fs.writeFileSync('./test.txt','test')
|
fs文件追加写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
const fs = require('fs')
fs.appendFile('./test.txt',"hello world",function (res){ if(!res){ console.log('追加文件成功'); } else { console.log('写入文件失败'); } })
fs.writeFile('./test.txt',"hello world",{flag:'a'},function (res){ if(!res){ console.log('追加文件成功'); } else { console.log('写入文件失败'); } })
|
fs流式写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const fs = require('fs')
const ws = fs.createWriteStream('./test.txt')
ws.write("hello world") ws.write("hello world") ws.write("hello world") ws.write("hello world") ws.write("hello world")
ws.close()
|
fs文件读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
const fs = require('fs')
fs.readFile('./test.txt',function(err,data){ if(err){ console.log('读取失败'); }
console.log(data.toString()); })
let data = fs.readFileSync('./test.txt') console.log(data.toString());
|
fs文件流式读取
1 2 3 4 5 6 7 8 9 10 11 12 13
|
const fs = require('fs')
const rs = fs.createReadStream('./test.txt')
rs.on('data',chunk => { console.log(chunk.length); })
rs.on('end',()=>{ console.log('读取文件结束'); })
|
fs文件重命名和移动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
const fs = require('fs')
fs.rename('./test.txt','./文件.txt',err => { if(err){ console.log('重命名失败'); } console.log('success'); })
fs.rename('./文件.txt','./data/文件.txt',err => { if(err){ console.log('移动文件失败'); } console.log('success'); })
|
fs文件删除
1 2 3 4 5 6 7 8 9 10 11 12
|
const fs = require('fs')
fs.unlink('./data/文件.txt',()=>{
})
fs.rm('./创建vue对象.html',()=>{
})
|
fs文件夹操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
const fs = require('fs')
fs.mkdir('./data',()=>{})
fs.mkdir('./data/a/b',{recursive:true},()=>{})
fs.readdir('./',(err,data)=>{ console.log(data); })
fs.rmdir('./1',(err)=>{ console.log(err); })
fs.rmdir('./data',{recursive:true},(err)=>{ console.log(err); })
|
fs查看资源状态
1 2 3 4 5 6 7 8 9 10 11
|
const fs = require('fs')
fs.stat('./vue.js',(err,data)=>{ console.log(data); console.log(data.isFile()); console.log(data.isDirectory()); })
|
path模块
Node.is 代码中,相对路径是根据终端所在路径来查找的,可能无法找到你想要的文件
建议:在Node.js的代码中,使用绝对路径
如何避免这个问题?
可以使用__dirname来获取模块的路径
__dirname:获取当前模块的绝对路径
不建议使用字符串拼接的方式来拼接路径,可以使用path.resolve和path.join函数来拼接。
Http模块
1.什么是HTTP协议?
http协议,超文本传输协议
是互联网使用最广泛的协议之一,是客户端和服务端通信的一种约定。
2.HTTP报文
请求报文结构
请求行的组成部分
请求方式,如GET,POST
GET 用于获取资源
POST 用于新增资源
DELETE 用于删除资源
PUT/PATCH 用于修改资源
url
协议及版本
请求头
为一个键值对结构,冒号之前的为键,冒号之后为值。记录了客户端请求的一些内容
请求体
请求体的格式非常多,可以为json,或者其他格式。
响应报文结构
响应行的组成部分
协议及版本
相应状态码
200 成功
403 拒绝访问
404 资源未找到
500 服务器端错误
响应状态的描述
响应头的组成部分
记录了服务的相应的一些信息
3.HTTP模块
创建HTTP服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const http = require('http')
const server = http.createServer((req, res)=>{ res.setHeader("content-type",'text/html;charset=utf-8'); res.end('你好'); });
server.listen(9000,()=>{ console.log("服务已经启动"); })
|
提取请求报文内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const http = require('http')
const server = http.createServer((req, res)=>{ console.log(req.method) console.log(req.url) console.log(req.httpVersion) console.log(req.headers) let reqBody = ''; req.on('data',chunk => { reqBody += chunk; }) req.on('end',()=>{ console.log(reqBody) }) res.end() })
server.listen('9000',()=>{ console.log('服务启动') })
|
如果需要解析url,可以使用url模块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const http = require('http')
const url = require('url')
const server = http.createServer((req, res)=>{ let url = new URL('https://www.bilibili.com/video/BV1gM411W7ex?p=52&vd_source=a6904af2f8e9663e9d5addf564fa5351') console.log(url) console.log(req) res.end(); })
server.listen('9000',()=>{ console.log('服务启动') })
|
设置响应体内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const http = require('http')
const server = http.createServer((req, res)=>{ res.statusCode = 201 res.setHeader("myheader","head") res.write("hello")
res.end(); })
server.listen('9000',()=>{ console.log('服务启动') })
|
如何实现网页引入外部资源?
通过前面的学习,我们已经知道,通过在响应体里面设置返回的H5代码,可以让浏览器显示网页。
如果这个h5页面包含了js,css的代码,那么可以直接渲染出来。
但是如果我们使用引入文件的方式,这个时候还能正常的加载资源文件吗?
答案是否定的。
为什么?
因为我们现在浏览器对服务器发出来的请求,执行的是监听的回调函数。这个函数目前的逻辑只会返回h5代码。
我们请求css,js的代码获取的返回内容,都是这个h5,自然无法渲染。
- 高复用性
- 高维护性
Node.js模块化
定义:在Node.js中,每个文件都被视为是一个单独的模块
概念:项目是由很多个模块组成的
好处:提高代码复用性,按需加载,独立作用域
使用:需要标准语法导出和导入进行使用
CommonJs标准
模块化示例
对外暴露函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| function run(){ console.log("跑步") }
function jump(){ console.log("跳跃") }
module.exports = { run:run, jump:jump }
|
外部导入模块
对于内置模块,可以直接使用模块名导入
如果是自定义模块,通过写模块文件路径来导入
1 2 3 4
| const file02 = require('./file02')
const path = require('path')
|
模块化数据的暴露
方式1:
1 2 3 4 5
| module.exports = { run:run, jump:jump }
|
方式2:
1 2 3
| exports.run = run exports.jump = jump
|
模块化暴露数据的注意事项
1.可以使用module.exports暴露任何形式的数据
1 2
| module.exports = 123 module.exports = 'iloveyou'
|
2.不能使用exports = value的形式暴露数据
module.exports,exports的关系如下:
1
| module.exports = exports = {}
|
此处require获取的module.exports的值,修改exports对象不会影响到module.exports的值
1
| const file02 = require('./file02')
|
导入文件模块
- require模块时使用相对路径,./ 和 ../ 不能省略
- 导入js文件和json文件时,可以省略后缀。如果省略后缀,js和json文件同名了,优先导入js文件
- 如果导入其他类型的文件,按照js文件处理
- 如果导入的是一个文件夹,则会优先去检测package.json文件的main属性对应的文件,如果找不到,就会报错。如果main属性不存在,或者package.json不存在,则会尝试导入index.js或者index.json,不存在就报错
- 导入node.js内置模块不需要加../ 或者 ./
require导入模块的基本流程
- 接受相对路径,转换成绝对路径
- 缓存查找是否存在,存在直接返回,不存在往下执行
- 读取目标文件的代码
- 包裹为一个函数并执行
- 添加到缓存
- 返回module.exports
ECMAScript标准
导出:export default {}
导入:import 变量名 from ‘模块名或路径’
注意:Node.js默认支持CommonJS标准语法
如需要使用ECMAScript语法,在运行模块所在文件夹新建package.json文件,并设置
案例
已配置package.json文件
utils.js,被导入的文件
1 2 3 4 5
| const info = '孤岛'
export default { name: info }
|
index.js, 导入utils模块的文件
1 2 3
| import name from './utils.js'
console.log(name)
|
命名导出和导入
导出:export 修饰定义语句
导入:import {同名变量} from ‘模块名或路径’
如何选择默认导出和命名导出?
- 按需加载,使用命名导出和导入
- 全部加载,使用默认导出和导入
案例
utils.js,被导入的文件
1 2 3 4 5 6 7 8 9 10
| export const info = '孤岛'
function log(){ console.log(1) }
export default { name: info, log:log }
|
index.js,导入utils的文件
1 2 3 4 5 6 7
| import {info} from './utils.js' console.log(info)
import obj from './utils.js' console.log(obj)
|
Node.js的包
什么是包?
将模块,代码,其他资料聚合成文件夹
包分为哪两类
- 项目包:编写项目代码的文件夹
- 软件包:封装工具和方法供开发者使用
package.json 文件的作用?
记录软件包的名字,作者,入口文件等信息
导入一个包文件夹的时候,导入的是哪个文件?
默认 index.js 文件,或者 main 属性指定的文件
npm-软件包管理器
npm 是 Node.js 标准的软件包管理器。
在 2017 年1月时,npm 仓库中就已有超过 350000 个软件包,这使其成为世界上最大的单一语言代码仓库,并且可以确定几乎有可用于一切的软件包。
它起初是作为下载和管理 Node.js 包依赖的方式,但其现在也已成为前端JavaScript 中使用的工具。
切换到需要初始化的包路径下,执行如下命令。
注意事项:
- 填写的包名不能使用中文或者大写字母,只能使用友好的URL字符,默认值是文件夹的名称,所以文件夹的名字也不能是大写和中文。
- package.json支持用户手动创建和修改
- 可以是用快速创建的命令创建package.json 全部问题按照默认值回答
1 2
| npm init --yes npm init -y
|
搜索工具包
方式1:使用搜索命令
安装包
注意:规范的操作应该在安装包之前先初始化包
1 2 3 4
| npm install uniq # uniq为包名 # 也可以使用简写 npm i uniq
|
命令运行之后会多出两个资源
- node_modules:用于存放下载的包
- package_lock.json:用于锁定包的版本
require导入npm包的基本流程
- 在当前文件夹下node_modules寻找同名文件夹
- 在上级的node_modules寻找同名文件夹,直至找到磁盘根目录
开发依赖与生产依赖
- 开发依赖是只在开发环境用到的依赖
- 生产依赖是实在开发环境和生产环境都需要用到的依赖
1 2 3 4 5 6 7
| # 安装生产依赖 npm install -S uniq npm install --save uniq
# 安装开发依赖 npm install -D less npm install --save-dev less
|
npm的全局安装
命令:npm i -g nodemon
我们可以使用-g选项来执行全局安装操作。
全局安装的包,安装时不收操作目录的影响。
全局安装的包,安装完之后保存到一个单独的目录。可以使用如下命令查看存放位置:
不是所有的包都是和全局安装,只有全局类的工具才适合
npm安装包的所有依赖
依据package.json和package-lock.json下载项目所需要的依赖
注意:node_modules大多数情况不进入版本控制(git)
npm安装指定版本的包与npm删除包
npm安装指定版本包
1 2
| npm i <package@version> npm i jquery@2.2.1
|
安装指定版本的包之后,将会修改package-lock.json对应依赖的版本号。
npm删除指定的包
1 2
| npm remove <package> npm r <package>
|
执行删除命令之后,也会修改对应的package-lock.json文件
配置命令的别名
通过配置别名可以更简单的执行命令,简化命令的编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "name": "package_01", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "server": "node ./removeSame.js", "start": "node ./removeSame.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "uniq": "^1.0.1" } }
|
通过在package.json的scripts属性中,配置属性即可完成别名的配置。通过npm run ***即可执行命令
npm start 可以省略run,npm start一般用来启动项目
npm run具有向上级目录查找命令的特性。
使用nrm模块管理下载镜像源
安装nrm模块
使用nrm切换镜像源
查看镜像源
检查镜像是否配置成功
yarn-包管理工具
yarn 官方宣称的一些特点
- 速度更快,yarn会将每一个下载过的包缓存起来,下次使用无需下载,同时利用并行下载技术加速下载
- 更安全,在执行代码之前,yarn会根据算法去验证每个安装包的完整性
- 使用详细,简洁的锁文件格式和明确的安装算法,yarn能够保证在不同系统上无差异的工作
yarn安装
可以通过npm命令安装yarn
yarn常用命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 初始化 yarn init yarn init -y # 安装生产依赖 yarn add uniq # 安装开发依赖 yarn add less --dev # 删除依赖 yarn remove less # 安装全局依赖 yarm global add nodemon # 安装项目依赖 yarn # 启动脚本 yarn <server># 不需要添加run
|
yarn配置镜像源
1 2 3 4
| # 查看镜像源 yarn config list # 设置镜像 yarn config set registry <location>
|
express模块
什么是express?
是一个基于node.js的平台的极简的,灵活的web框架。
express是一个工具包,封装了很多的功能,便于我们开发web服务
express使用
1.安装express
2.导入express模块
1
| const express = require('express')
|
3.创建应用
4.创建路由,编写逻辑
1 2 3 4
| app.get('/index',(req,res)=>{ console.log('请求成功') })
|
5.监听端口,启动服务
1 2 3
| app.listen("8000",()=>{ })
|
什么是路由?
路由确定了应用程序如何响应客户端对特定端点的请求。
路由的使用
路由一般有三部分组成
获取请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express')
const app = express()
app.get("/home",(req,res)=>{ console.log(req.get("host")); console.log(req.path) console.log(req.query); console.log(req.ip); res.end("hello express") })
app.listen("8000",()=>{ console.log("监听端口8000,服务启动成功") })
|
获取路由参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const express = require('express')
const app = express()
app.get("/home/:id",(req,res)=>{ console.log(req.params.id) res.end("hello express") })
app.listen("8000",()=>{ console.log("监听端口8000,服务启动成功") })
|
一般响应设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const express = require('express')
const app = express()
app.get("/home",(req,res)=>{ res.status(404) res.set('aaa','bbb') res.send("hhhh啊啊啊") })
app.listen("8000",()=>{ console.log("监听端口8000,服务启动成功") })
|
其他响应设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express')
const path = require('path')
const app = express()
app.get("/home",(req,res)=>{ res.sendFile(path.resolve('test.html')) })
app.listen("8000",()=>{ console.log("监听端口8000,服务启动成功") })
|
中间件
什么是中间件
中间件本质是一个回调函数
中间件函数可以像路由一样,访问到request,response对象,进行操作
中间件的作用
可以封装通用的逻辑,简化代码的编写
中间件的类型
全局中间件
每一个请求到达服务端之后,在执行路由代码之前都会执行的中间件
路由中间件
每一个请求到达服务端之后,会调用对应的路由中间件执行
全局中间件的实践
需求:使用全局中间件实现一个记录访问日志的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| const fs = require('fs')
const express = require('express')
const path = require('path')
function recordAccessAddress(req, res, next) { let {url, ip} = req fs.appendFileSync(path.resolve('access.log'), `${url},${ip}\r\n`) next() }
const app = express()
app.use(recordAccessAddress)
app.get("/index", (req, res) => {
res.json( { msg: "index请求成功" } ) })
app.get("/admin", (req, res) => { res.json( { msg: "admin请求成功" } ) })
app.get("/*", (req, res) => { res.json( { msg: "404" } ) })
app.listen("8000", () => { console.log("监听端口8000,服务启动成功") })
|
路由中间件实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const fs = require('fs')
const express = require('express')
const path = require('path')
const app = express()
let checkCode = function (req,res,next) { if(req.query.code === '521'){ next() } else { res.send('暗号错误') } }
app.get("/index",checkCode, (req, res) => { res.json( { msg: "index请求成功" } ) })
app.get("/admin",checkCode, (req, res) => { res.json( { msg: "admin请求成功" } ) })
app.get("/*", (req, res) => { res.json( { msg: "404" } ) })
app.listen("8000", () => { console.log("监听端口8000,服务启动成功") })
|
静态资源中间件设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| const fs = require('fs')
const express = require('express')
const path = require('path')
const app = express()
app.use(express.static(path.resolve("./public")))
app.get("/index", (req, res) => { res.json( { msg: "index请求成功" } ) })
app.get("/admin", (req, res) => { res.json( { msg: "admin请求成功" } ) })
app.get("/*", (req, res) => { res.json( { msg: "404" } ) })
app.listen("8000", () => { console.log("监听端口8000,服务启动成功") })
|
静态资源中间件注意事项
- 当查询条件为“/”时,静态资源中间件默认寻找index.html返回
- 当存在“/”路由,静态资源又存在index.html文件时,谁先匹配上响应谁
- 路由响应动态资源,静态资源让静态资源中间件处理