前言
往期全栈课程:
Vue从入门到精通
微信小程序从入门到精通
Node.js基础
简介
Node.js是什么?
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O的模型,使其轻量又高效。Node.js 的包管理工具 npm 是全球最大的开源库生态系统。
Node.js 不是一门语言,也不是 JavaScript 的框架,也不是像Nginx一样的Web服务器 ,Node.js 是 JavaScript 在服务器端的运行环境(平台)。
Node.js的组成
在 Node.js 里运行 JavaScript,跟在 Chrome 里运行 JavaScript 有什么不同?
二者采用的是同样的 JS 引擎。在 Node.js 里写 JS,和在前端写 JS,几乎没有不同。在写法上的区别在于:Node.js 没有浏览器、页面标签相关的 API(不能操作BOM对象和DOM对象),但是新增了一些 Node.js 相关的 API。通俗来说,对于开发者而言,在前端写 JS 是用于控制浏览器;而 Node.js 环境写 JS 可以控制整个计算机。
我们知道,JavaScript 的组成分为三个部分:
- ECMAScript:描述了JS的语法和基本对象
- DOM(document object model):DOM 是文档对象模型,处理网页内容的方法和接口。是W3C 的标准;
- BOM(browser object model);BOM 是浏览器对象模型,提供与浏览器交互的方法和接口。各个浏览器厂商根据 DOM在各自浏览器上的实现;
ECMAScript 是 JS 的语法;DOM 和 BOM 浏览器端为 JS 提供的 API。
而 Node.js 的组成分为:
- ECMAScript。ECMAScript 的所有语法在 Node 环境中都可以使用。
- Node 环境提供的一些附加 API(包括文件、网络等相关的 API)。
如下图所示:
知名度较高的Node.js开源项目
-
express:Node.js 中著名的 web 服务框架。
-
Koa:下一代的 Node.js 的 Web 服务框架。所谓的“下一代”是相对于 Express 而言的。
-
Egg:2016 年,阿里巴巴研发了知名的 Egg.js 开源项目,号称企业级 Web 服务框架。Egg.js 是基于 Koa 开发的。
-
mocha:是现在最流行的 JavaScript 测试框架,在浏览器和 Node 环境都可以使用。
-
PM2:node 多进程管理。
-
jade:非常优秀的模板引擎,不仅限于 js 语言。
-
CoffeeScript:用简洁的方式展示 JavaScript 优秀的部分。
-
Atom:编辑器。
-
VS Code:最酷炫的编辑器。
-
socket.io:实时通信框架。
Node.js的特点
- 异步、非阻塞 IO 模型
- 事件循环
- 单线程
- 总结:轻量和高效
Node.js 的性能和效率非常高。
传统的 Java 语言是一个请求开启一个线程,当请求处理完毕后就关闭这个线程。而 Node.js 则完全没有采用这种模型,它本质上就是一个单线程。
你可能会疑问:一个线程如何服务于大量的请求、如何处理高并发的呢?这是因为,Node.js 采用的是异步的、非阻塞的模型。
这里所谓的“单线程”,指的是 Node 的主线程只有一个。为了确保主线程不被阻塞,主线程是用于接收客户端请求。但不会处理具体的任务。而 Node 的背后还有一个线程池,线程池会处理长时间运行的任务(比如 IO 操作、网络操作)。线程池里的任务是通过队列和事件循环的机制来执行。
环境安装
通过 Node.js 安装包产生的问题:
- 安装新版本时,需要覆盖旧版本;而且以前版本安装的很多全局工具包,需要重新安装。
- 无法回滚到之前的旧版本。
- 无法在多个版本之间切换(很多时候,不同的项目需要使用特定版本。或者,我想临时尝鲜一下新版本的特性)
因此不推荐使用安装包的形式安装,Win推荐使用NVM,Mac使用n
安装教程如下:
mac:https://juejin.cn/post/7065701236925792293
win:https://juejin.cn/post/6844903861308637192
真好用!!!
包管理工具
由于 Node 是一套轻内核的平台,虽然提供了一系列的内置模块,但是不足以满足开发者的需求,于是乎出现了包(package)的概念: 与核心模块类似,就是将一些预先设计好的功能或者说 API 封装到一个文件夹,提供给开发者使用。
Node 本身并没有太多的功能性 API,所以市面上涌现出大量的第三方人员开发出来的 Package。
npm
NPM:Node Package Manager。官方链接: https://www.npmjs.com/
Node.js 发展到现在,已经形成了一个非常庞大的生态圈。包的生态圈一旦繁荣起来,就必须有工具去来管理这些包。
NPM 的常用命令
npm init
npm install 包名 –g (uninstall,update)
npm install 包名 --save-dev (uninstall,update)
npm list -g (不加-g,列举当前目录下的安装包)
npm info 包名(详细信息) npm info 包名 version(获取最新版本)
npm install md5@1(安装指定版本)
npm outdated( 检查包是否已经过时)
"dependencies": { "md5": "^2.1.0" } ^ 表示 如果 直接npm install 将会 安md5
2.*.* 最新版本
"dependencies": { "md5": "~2.1.0" } ~ 表示 如果 直接npm install 将会 安装md5 2.1.* 最新版本
"dependencies": { "md5": "*" } * 表示 如果 直接npm install 将会 安装 md5 最新版本
nrm
和npm一样都是包管理工具,喜欢那个用哪个
yarn
和npm一样都是包管理工具,喜欢那个用哪个
https://yarn.bootcss.com/,推荐这个
对比npm:
速度超快: Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。
超级安全: 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。
开始新项目
yarn init
添加依赖包
yarn add [package]
yarn add [package]@[version]
yarn add [package] --dev
升级依赖包
yarn upgrade [package]@[version]
移除依赖包
yarn remove [package]
安装项目的全部依赖
yarn install
cnpm
和npm一样都是包管理工具,喜欢那个用哪个
pnpm
使用文档如下:https://www.pnpm.cn/motivation
CommonJS
前言
网站越来越复杂,js代码、js文件也越来越多,会遇到一些问题:
- 文件依赖
- 全局污染、命名冲突
程序模块化包括:
- 日期模块
- 数学计算模块
- 日志模块
- 登陆认证模块
- 报表展示模块等。
所有这些模块共同组成了程序软件系统。
一次编写,多次使用,才是提高效率的核心。
模块化规范
模块化规范的引入
假设我们引入模块化,首先可能会想到的思路是:在一个文件中引入多个js文件。如下:
<body>
<script src="zepto.js"></script>
<script src="fastClick.js"></script>
<script src="util/login.js"></script>
<script src="util/base.js"></script>
<script src="util/city.js"></script>
</body>
但是这样做会带来很多问题:
- 请求过多:引入十个js文件,就有十次http请求。
- 依赖模糊:不同的js文件可能会相互依赖,如果改其中的一个文件,另外一个文件可能会报错。
以上两点,最终导致:难以维护。
于是,这就引入了模块化规范。
CommonJS 的基本语法
在 CommonJS 中,每个文件都可以当作一个模块:
- 在服务器端:模块的加载是运行时同步加载的。
- 在浏览器端: 模块需要提前编译打包处理。首先,既然同步的,很容易引起阻塞;其次,浏览器不认识
require
语法,因此,需要提前编译打包。
模块的暴露和引入
Node.js 中只有模块级作用域,两个模块之间的变量、方法,默认是互不冲突,互不影响,这样就导致一个问题:模块 A 要怎样使用模块B中的变量&方法呢?这就需要通过 exports
关键字来实现。
Node.js中,每个模块都有一个 exports 接口对象,我们可以把公共的变量、方法挂载到这个接口对象中,其他的模块才可以使用。
接下来详细讲一讲模块的暴露、模块的引入。
exports
exports
对象用来导出当前模块的公共方法或属性。别的模块通过 require 函数调用当前模块时,得到的就是当前模块的 exports 对象。
// 相当于是:给 exports 对象添加属性
exports.xxx = value
这个 value 可以是任意的数据类型。
注意:暴露的关键词是exports
,不是export
。其实,这里的 exports 类似于 ES6 中的 export 的用法,都是用来导出一个指定名字的对象。
const name = 'zs'
const foo = function(value){
return value * 2
}
exports.day01_name = name
exports.day01_foo = foo
module.exports
module.exports
用来导出一个默认对象,没有指定对象名。
语法格式:
// 方式一:导出整个 exports 对象
module.exports = value;
// 方式二:给 exports 对象添加属性
module.exports.xxx = value;
这个 value 可以是任意的数据类型。
代码举例:
// 方式1
module.exports = {
name: '我是 module1',
foo(){
console.log(this.name);
}
}
// 我们不能再继续写 module.exports = value2。因为重新赋值,会把 exports 对象 之前的赋值覆盖掉。
// 方式2
const age = 28;
module.exports.age = age;
module.exports
还可以修改模块的原始导出对象。比如当前模块原本导出的是一个对象,我们可以通过 module.exports 修改为导出一个函数。如下:
module.exports = function () {
console.log('hello world')
}
区别
最重要的区别:
- 使用exports时,只能单个设置属性
exports.a = a;
- 使用module.exports时,既单个设置属性
module.exports.a
,也可以整个赋值module.exports = obj
。
其他要点:
- Node中每个模块的最后,都会执行
return: module.exports
。 - Node中每个模块都会把
module.exports
指向的对象赋值给一个变量exports
,也就是说exports = module.exports
。 module.exports = XXX
,表示当前模块导出一个单一成员,结果就是XXX。- 如果需要导出多个成员,则必须使用
exports.add = XXX; exports.foo = XXX
。或者使用module.exports.add = XXX; module.export.foo = XXX
。
require
require函数用来在一个模块中引入另外一个模块。传入模块名,返回模块导出对象。
语法格式:
const module1 = require('模块名');
解释:
- 内置模块:require的是包名。
- 下载的第三方模块:require的是包名。
- 自定义模块:require的是文件路径。文件路径既可以用绝对路径,也可以用相对路径。后缀名
.js
可以省略。
代码举例:
const m1 = require('./main.js');
const module2 = require('./main');
const module3 = require('Demo/src/main.js');
m1.method()
require()函数的两个作用:
- 执行导入的模块中的代码。
- 返回导入模块中的接口对象。
内置模块
前置知识点
箭头函数
// 传统函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
回调函数
回调函数常用于异步操作和事件处理
- 当异步操作完成后,可以使用回调函数进行操作。
- 当事件发生时可以使用回调函数进行操作处理一些逻辑
回调函数的应用
readableStream.on('data', (chunk) => {
console.log('Data:', chunk);
});
你可能知道.on是一个方法然后括号里面传入了两个参数,一个参数是data,一个参数是被()=>{}修饰的
那么这个被()=>{}修饰的部分就是回调函数,回调函数一般是将回调函数做为参数传递给另一个函数,那么为了方便就可以用上面提到的箭头函数来传递。这样是不是很方便呢,也不用想给回调函数起个什么名字了哈哈。
在前端开发中回调函数和箭头函数非常常见,一定要理解不然都看不懂这代码啥意思。
http模块
要使用 HTTP 服务器和客户端,则必须 require('http')
。
const http = require('http')
// 创建本地服务器来从其他接收数据
const server = http.createServer((req,res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!'
}));
})
server.listen(8000);
结果展示
jsonp
JSON 是一种数据格式,而 JSONP 是一种用于跨域数据请求的技术。JSONP 利用动态创建 <script>
标签的方式来获取跨域数据,但它也存在一些安全风险。在现代的 Web 开发中,由于浏览器支持跨域资源共享(CORS)等技术,JSONP 的使用已经相对较少,更多地使用 JSON 和 Ajax 进行数据交互。
模拟get
var http = require('http')
var https = require('https')
// 1、接口 2、跨域
const server = http.createServer((request, response) => {
var url = request.url.substr(1)
var data = ''
response.writeHeader(200, {
'content-type': 'application/json;charset=utf-8',
'Access-Control-Allow-Origin': '*'
})
https.get(`https://m.lagou.com/listmore.json${url}`, (res) => {
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
response.end(JSON.stringify({
ret: true,
data
}))
})
})
})
server.listen(8080, () => {
console.log('localhost:8080')
})
效果如下:
在上述示例中,我们使用 https.request()
方法创建了一个请求对象,指定了请求的参数和路径,包括查询参数 page=2
。我们向 reqres.in
的 /api/users
路径发送了一个 GET 请求。
当收到响应时,我们监听 response
对象的 data
事件来获取响应数据。在 end
事件中,我们处理完整的响应数据。
请注意,上述示例中使用的是 HTTPS 请求,端口号默认为 443。如果你希望使用 HTTP 请求,你可以将 options
中的 port
设置为 80,并使用 http
模块替代 https
模块。
模拟post
const https = require('https');
// POST 请求的选项
const options = {
hostname: 'reqres.in', // API 的主机名
port: 443, // API 的端口号
path: '/api/users', // API 的路径
method: 'POST', // 请求方法
headers: {
'Content-Type': 'application/json', // 请求的内容类型
},
};
// 创建请求对象
const request = https.request(options, (response) => {
let data = '';
// 接收响应数据
response.on('data', (chunk) => {
data += chunk;
});
// 响应结束时处理数据
response.on('end', () => {
console.log('Response:', data);
});
});
// 请求数据
const postData = JSON.stringify({ name: 'John Doe', job: 'Developer' }); // 要发送的数据
request.write(postData);
// 发送请求
request.end();
请求效果如下:
在上述示例中,我们使用 https.request()
方法创建一个请求对象,指定了请求的参数和头部信息。我们向 reqres.in
的 /api/users
路径发送了一个 POST 请求。我们使用 JSON 格式的数据对象 { name: 'John Doe', job: 'Developer' }
作为请求主体,并通过 request.write()
方法将其写入请求。
当收到响应时,我们监听 response
对象的 data
事件来获取响应数据。在 end
事件中,我们处理完整的响应数据。
请注意,上述示例使用的是 HTTPS 请求,端口号为 443。如果你希望使用 HTTP 请求,你可以将 options
中的 port
设置为 80,并使用 http
模块替代 https
模块。
url模块
parse
const url = require('url')
const urlString = 'https://www.google.com/'
const parsedStr = url.parse(urlString)
console.log(parsedStr)
效果如下:
看结果知道是用来解析url的
注意:url.parse返回的是一个Url对象
format
const url = require('url')
const urlObject = {
protocol: 'https:',
slashes: true,
auth: null,
host: 'www.baidu.com:443',
port: '443',
hostname: 'www.baidu.com',
hash: '#tag=110',
search: '?id=8&name=mouse',
query: { id: '8', name: 'mouse' },
pathname: '/ad/index.html',
path: '/ad/index.html?id=8&name=mouse'
}
const parsedObj = url.format(urlObject)
console.log(parsedObj)
效果如下:
看来是用来格式化URL的
resolve
const url = require('url')
var a = url.resolve('/one/two/three', 'four')
var b = url.resolve('http://example.com/', '/one')
var c = url.resolve('http://example.com/one', '/two')
console.log(a + "," + b + "," + c)
拼接url的,效果如下:
querystring模块
parse和stringify
escape/unescape
可以看出一个是将特殊字符进行转义,一个是对转义后的特殊字符进行解析
转义的好处是可以防止SQL注入
escape
和 unescape
函数是 JavaScript 中的字符串转义函数,它们可以用于防止 SQL 注入攻击。然而,需要注意的是,这两个函数在最新的 ECMAScript 标准中已被废弃,因为它们并不是完全安全可靠的转义方式。相反,现代的开发实践推荐使用预编译语句等技术来防止 SQL 注入。
预编译是一种执行之前先定义好SQL语句模版,其中包含占位符,而不是直接插入用户提供的数据。然后将用户输入的数据作为参数传递给预编译语句,数据库连接库会负责将数据安全地插入到查询中,避免了SQL注入的风险。
event模块
const EventEmitter = require('events');
// 创建自定义事件发射器
class MyEmitter extends EventEmitter {}
// 创建事件发射器实例
const myEmitter = new MyEmitter();
// 注册事件监听器
myEmitter.on('myEvent', (arg) => {
console.log('Event received:', arg);
});
// 触发事件
myEmitter.emit('myEvent', 'Hello, world!');
在上述示例中,我们创建了一个自定义的事件发射器 MyEmitter
,通过继承 EventEmitter
类。然后,我们创建了 MyEmitter
的实例 myEmitter
。接下来,我们使用 on()
方法注册了一个监听器,监听名为 myEvent
的事件,并定义了事件发生时的回调函数。最后,我们使用 emit()
方法触发了 myEvent
事件,并传递了一个参数 'Hello, world!'
。
当触发 myEvent
事件时,注册的监听器会执行相应的回调函数,并输出事件的参数。
events
模块的使用让事件驱动的编程更加简洁和灵活。它被广泛应用于 Node.js 中处理异步操作、网络请求、事件处理等场景,以及许多第三方模块和库中。
fs文件操作模块
Node.js 的 fs
(File System)模块是一个内置模块,用于处理文件系统操作。它提供了一组方法,用于读取、写入、更新和删除文件,以及处理文件和目录的相关操作。
const fs = require('fs');
//写入
fs.writeFile('file.txt', 'Hello, world!', (err) => {
if (err) {
console.error(err);
return;
}
console.log('File written successfully!');
});
// 读取文件
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
// 判断文件是否存在
if (fs.existsSync('file.txt')) {
console.log('File exists');
} else {
console.log('File does not exist');
}
异步方法采用回调函数作为参数,在操作完成时被调用。这样可以在等待操作完成时,继续执行其他任务,提高程序的并发性和响应性。
例如,异步读取文件的方法是 fs.readFile()
,它接受一个回调函数作为参数,当文件读取完成时会调用该回调函数,并传递读取到的数据。
stream流模块
const fs = require('fs');
// 创建可读流
const readableStream = fs.createReadStream('file.txt', 'utf8');
// 创建可写流
const writableStream = fs.createWriteStream('output.txt', 'utf8');
// 将可读流的数据写入可写流
readableStream.pipe(writableStream);
// 监听可读流的 'data' 事件
readableStream.on('data', (chunk) => {
console.log('Data:', chunk);
});
// 监听可写流的 'finish' 事件
writableStream.on('finish', () => {
console.log('Write complete.');
});
在示例中,readableStream.on('data', callback)
和 writableStream.on('finish', callback)
是两个事件监听的示例。on()
方法用于注册事件监听器,并指定要监听的事件名称和相应的回调函数。
'data'
是监听事件的名称,它是可读流 readableStream
发出的事件。当可读流有新的数据块可用时,会触发 'data'
事件。通过注册监听器来捕获 'data'
事件,可以在每次有新数据时执行相应的回调函数。
类似地,'finish'
事件是可写流 writableStream
发出的事件。当所有数据都被写入可写流后,会触发 'finish'
事件。通过注册监听器来捕获 'finish'
事件,可以在写入完成时执行相应的回调函数。
zlib模块
const zlib = require('zlib');
const data = 'Hello, world!';
zlib.gzip(data, (err, compressedData) => {
if (err) {
console.error(err);
return;
}
console.log('Compressed data:', compressedData);
});
data
是要压缩的原始数据,而 compressedData
是压缩后的数据。
crypto模块
crypto
模块是 Node.js 的内置模块之一,提供了加密和解密的功能。它通过提供一组加密算法和相关的操作函数,用于处理数据的加密、解密、签名、哈希等安全操作。
const crypto = require('crypto');
const data = 'Hello, world!';
//创建了一个哈希对象 hash,使用的哈希算法是 SHA-256。
const hash = crypto.createHash('sha256');
hash.update(data);
//执行哈希计算并获取哈希值。digest() 方法用于计算哈希对象的摘要。它接受一个参数来指定输出的格式,例如 'hex' 表示以十六进制字符串的形式输出。调用 digest('hex') 方法后,它返回计算得到的哈希值,并将其赋值给变量 hashedData。
const hashedData = hash.digest('hex');
console.log('Hashed data:', hashedData);
路由
基础
var fs = require("fs")
var path = require("path")
function render(res, path) {
res.writeHead(200, { "Content-Type": "text/html;charset=utf8" })
res.write(fs.readFileSync(path, "utf8"))
res.end()
}
const route = {
"/login": (req, res) => {
render(res, "./static/login.html")
},
"/home": (req, res) => {
render(res, "./static/home.html")
},
"/404": (req, res) => {
res.writeHead(404, { "Content-Type": "text/html;charset=utf8" })
res.write(fs.readFileSync("./static/404.html", "utf8"))
}
}
获取参数
get请求
"/api/login":(req,res)=>{
const myURL = new URL(req.url, 'http://127.0.0.1:3000');
console.log(myURL.searchParams.get("username"))
render(res,`{ok:1}`)
}
post请求
"/api/login": (req, res) => {
var post = '';
// 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
req.on('data', function (chunk) {
post += chunk;
});
// 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
req.on('end', function () {
post = JSON.parse(post);
render(res, `{ok:1}`)
});
}
静态资源处理
function readStaticFile(req, res) {
const myURL = new URL(req.url, 'http://127.0.0.1:3000')
var filePathname = path.join(__dirname, "/static", myURL.pathname);
if (fs.existsSync(filePathname)) {
// console.log(1111)
res.writeHead(200, { "Content-Type": `${mime.getType(myURL.pathname.split(".")[1])};charset=utf8` })
res.write(fs.readFileSync(filePathname, "utf8"))
res.end()
return true
} else {
return false
}
}
Express
安装
yarn init
yarn add express
路由
Express 提供了 app.get()
、app.post()
、app.put()
、app.delete()
等方法用于定义路由。你可以根据 HTTP 请求方法和 URL 路径,将请求映射到相应的处理函数上。
匹配路径的时候可以上正则表达式
下面是一个基本的路由示例:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.post('/users', (req, res) => {
// 处理创建用户的逻辑
});
app.put('/users/:id', (req, res) => {
// 处理更新用户的逻辑
});
app.delete('/users/:id', (req, res) => {
// 处理删除用户的逻辑
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
中间件
Express 中间件允许你在请求和响应之间执行一些额外的逻辑。中间件函数可以用来处理请求、修改请求或响应对象、进行身份验证、记录日志等。你可以使用 app.use()
或特定的 HTTP 方法来注册中间件。
const express = require('express');
const app = express();
// 自定义中间件
const middleware1 = (req, res, next) => {
console.log('Middleware 1');
next(); // 调用 next() 将请求传递给下一个中间件或路由处理函数
};
const middleware2 = (req, res, next) => {
console.log('Middleware 2');
next(); // 调用 next() 将请求传递给下一个中间件或路由处理函数
};
// 注册中间件
app.use(middleware1);
app.use(middleware2);
// 路由处理函数
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
在上述示例中,我们定义了两个中间件函数 middleware1
和 middleware2
,它们分别在控制台打印一些信息,并调用 next()
将请求传递给下一个中间件或路由处理函数。这样,当我们访问根路径 /
时,请求将依次通过两个中间件,并最终到达路由处理函数,返回响应。
中间件的应用场景包括但不限于:身份验证、日志记录、错误处理、请求预处理等。它们可以对请求进行处理、修改请求或响应对象,并决定请求的控制流程。中间件提供了一种可扩展的机制,用于在请求和响应之间执行额外的逻辑。
获取请求参数
Express 提供了多种方式来获取请求参数,包括 URL 参数、查询参数、请求体参数等。你可以通过 req.params
、req.query
、req.body
等属性来访问这些参数。
const express = require('express');
const app = express();
app.get('/users/:id', (req, res) => {
const userId = req.params.id; // 获取 URL 参数
console.log(userId)
const queryParams = req.query; // 获取查询参数
// 处理逻辑
});
app.post('/users', (req, res) => {
const userData = req.body; // 获取请求体参数
// 处理逻辑
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
托管静态文件
Express 提供了 express.static()
方法来托管静态文件,例如 CSS、JavaScript、图像等。你只需将静态文件所在的目录作为参数传递给 express.static()
,然后通过访问对应的 URL 路径即可获取静态文件。
const express = require('express');
const app = express();
app.use(express.static('public')); // 托管 public 目录下的静态文件
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
服务端渲染(模板引擎)
Express 可以与各种模板引擎配合使用,实现服务端渲染的功能。你可以选择适合你的项目的模板引擎,如 EJS、Pug、Handlebars 等,并将其与 Express 集成,通过模板引擎渲染动态内容并生成 HTML。
const express = require('express');
const app = express();
// 设置 EJS 作为模板引擎
app.set('view engine', 'ejs');
// 路由处理函数
app.get('/', (req, res) => {
const data = {
title: 'My Server-side Rendered Page',
message: 'Hello, world!',
};
res.render('index', data); // 渲染名为 "index" 的模板,并传递数据
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
接下来,在同级目录下创建一个名为 views
的文件夹,并在其中创建一个名为 index.ejs
的文件。在 index.ejs
文件中,我们可以使用 EJS 模板引擎的语法来嵌入动态数据。
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
在上述示例中,我们创建了一个根路径的路由处理函数,使用 res.render()
方法渲染了一个名为 index
的模板,并传递了一个包含动态数据的对象。这里的动态数据包括 title
和 message
。在模板中,我们使用 <%= %>
语法来输出动态数据。
当我们启动服务器并访问 http://localhost:3000
时,Express 会根据我们的路由处理函数生成动态的 HTML 页面,并将其发送给客户端进行展示。此时,服务器已经在响应中将动态数据注入到 HTML 页面中,完成了服务端渲染的过程。
MongoDB
这里我使用docker的方式来安装运行。
docker pull mongo:4
docker run -p 27017:27017 --name mongo \
-v /mydata/mongo/db:/data/db \
-d mongo:4
使用Navicat来做可视化操作
这里做一个简单的查询,我们可以知道MongoDB更适合和Node结合使用,因为他存储的数据格式是BSON格式的和JSON比较像
CRUD示例
运行的mongodb版本要和nodejs中安装的mongodb包的依赖版本一致
安装依赖:
yarn add mongodb@4.2.2
CRUD测试:
const { MongoClient } = require('mongodb');
// MongoDB 连接 URI
const uri = 'mongodb://localhost:27017';
// 数据库名称和集合名称
const dbName = 'leadnews-history';
const collectionName = 'ap_user_search';
// 连接 MongoDB
MongoClient.connect(uri, (err, client) => {
if (err) {
console.error('Failed to connect to MongoDB:', err);
return;
}
console.log('Connected to MongoDB');
const db = client.db(dbName);
const collection = db.collection(collectionName);
// 创建文档(Create)
const document = { name: 'John', age: 25 };
collection.insertOne(document, (err, result) => {
if (err) {
console.error('Failed to insert document:', err);
} else {
console.log('Document inserted:', result.insertedId);
}
// 读取文档(Read)
collection.find().toArray((err, documents) => {
if (err) {
console.error('Failed to find documents:', err);
} else {
console.log('Documents found:', documents);
}
// 更新文档(Update)
const filter = { name: 'John' };
const update = { $set: { age: 30 } };
collection.updateOne(filter, update, (err, result) => {
if (err) {
console.error('Failed to update document:', err);
} else {
console.log('Document updated:', result.modifiedCount);
}
// 删除文档(Delete)
const deleteFilter = { name: 'John' };
collection.deleteOne(deleteFilter, (err, result) => {
if (err) {
console.error('Failed to delete document:', err);
} else {
console.log('Document deleted:', result.deletedCount);
}
// 关闭连接
client.close();
});
});
});
});
});
接口规范与业务分层
RESTful
RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序的通信方式。它是一种基于 HTTP 协议的设计模式,旨在创建可扩展、灵活和易于维护的 Web 服务。
使用 RESTful 架构,可以设计出易于理解、可扩展和可维护的 Web 服务。以下是 RESTful 架构的一些关键特点和示例:
- 使用 HTTP 方法来表示对资源的操作:
- GET:获取资源的表示。
- POST:创建新资源。
- PUT:更新资源。
- DELETE:删除资源。
- 使用 URI(Uniform Resource Identifier)来标识资源:
/users
:表示用户资源。/products
:表示产品资源。
- 使用状态码(Status Code)来表示请求结果:
- 200:成功的请求。
- 201:已成功创建资源。
- 404:未找到资源。
- 500:服务器内部错误。
- 使用响应的内容类型(Content-Type)来指示返回的数据格式:
application/json
:返回 JSON 格式的数据。application/xml
:返回 XML 格式的数据。
- 使用超链接(Hyperlinks)来表示资源之间的关系:
- 使用链接来实现导航、发现和操作资源。
通过遵循 RESTful 架构的原则,可以设计出符合标准、易于理解和易于扩展的 Web 服务。RESTful 架构通常用于构建基于 Web 的 API(Application Programming Interface),支持客户端和服务器之间的数据交互和操作。
需要注意的是,RESTful 架构是一种设计模式和原则,并不是具体的技术或实现。在实际开发中,可以使用不同的技术和框架来实现 RESTful Web 服务,例如使用 Node.js、Express、Django、Spring 等。
URI 是用于唯一标识实体的名称,而 URL 则是用于定位实体的具体位置或资源的地址。
比如你要找一个人名为张三的人,那么URI就是张三,URL可以理解为张三的具体位置比如身份证上的地址
MVC
MVC(Model-View-Controller)是一种常用的软件架构模式,用于组织和设计应用程序的结构。它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller),每个组件负责不同的任务。
下面是 MVC 架构中各个组件的角色和职责:
- 模型(Model):
- 模型表示应用程序的数据和业务逻辑。
- 它负责处理数据的存储、读取、验证和更新。
- 模型通常是应用程序的核心,它不依赖于视图或控制器,独立地管理数据和业务逻辑。
- 视图(View):
- 视图是用户界面的表示。
- 它负责将模型中的数据以可视化的方式呈现给用户。
- 视图通常是被动的,不处理业务逻辑,只负责展示数据。
- 控制器(Controller):
- 控制器充当模型和视图之间的中间人,负责处理用户的输入和操作。
- 它接收用户的请求,并根据请求调用适当的模型来处理数据和逻辑。
- 控制器还负责将模型的数据更新反映到视图上。
MVC 架构的优点包括:
- 分离关注点:MVC 将应用程序的不同方面(数据、显示、控制逻辑)分离,使代码更易于维护和测试。
- 可扩展性:由于模块化和分层的设计,MVC 架构允许开发人员独立地修改和扩展各个组件。
- 代码复用:模型和视图可以在不同的应用程序场景中重用,提高了开发效率。
- 可测试性:MVC 架构使单元测试和集成测试更容易进行,因为各个组件的职责清晰明确。
需要注意的是,MVC 是一种架构模式,而不是具体的技术或框架。许多编程语言和框架都支持 MVC 架构,例如,ASP.NET MVC、Ruby on Rails、Spring MVC 等。
Node.js 也可以使用 MVC 架构模式来组织和设计应用程序的结构。尽管 Node.js 本身并没有内置的 MVC 框架,但你可以使用许多第三方模块和框架来实现 MVC 架构。
- Express.js:Express.js 是一个流行的轻量级 Node.js Web 框架,它提供了灵活的路由、中间件和视图模板等功能,可以很好地支持 MVC 架构的开发。
- Koa:Koa 是由 Express.js 团队创建的下一代 Node.js Web 框架,它采用了更现代的异步函数(async/await)风格,同样可以用于构建 MVC 应用程序。
登录鉴权
cookie和session
Cookie 作为客户端存储会话标识符的机制,而 Session 则是服务器端存储会话信息的机制。它们配合使用,确保用户身份的验证和会话状态的维持。
token
客户端发起请求,后端校验没问题的话使用jwt工具类生成token返回给客户端
用户下次登录会携带token,后端使用拦截器或过滤器对token进行校验
文件上传管理
Multer 是一个 node.js 中间件,用于处理 multipart/form-data
类型的表单数据,它主要用于上传文件。
安装:
yarn add multer
测试:
const express = require('express');
const multer = require('multer');
// 创建 Express 应用
const app = express();
// 配置 multer 中间件
const storage = multer.diskStorage({
destination: (req, file, cb) => {
// 指定上传文件的保存目录
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
// 指定上传文件的文件名
cb(null, file.originalname);
}
});
const upload = multer({ storage });
// 处理文件上传的 POST 请求
app.post('/upload', upload.single('file'), (req, res) => {
// 上传文件的处理逻辑
// req.file 包含了上传的文件信息
res.send('文件上传成功');
});
// 启动服务器
app.listen(3000, () => {
console.log('服务器已启动,监听端口 3000');
});
在上述代码中,我们定义了 storage
对象,其中包含了 destination
和 filename
属性,它们都是函数。这些函数在执行时,接收三个参数:req
(请求对象)、file
(上传的文件对象)和 cb
(回调函数)。
cb
函数需要在适当的时机被调用,以通知 multer
文件上传的状态和结果。通过调用 cb
函数,我们可以将错误对象或者指定的值传递给 multer
,以便它能够继续处理文件上传过程。
而 const upload = multer({ storage })
的意思是创建一个 multer
实例,并将配置对象 storage
传递给它。这样可以创建一个处理文件上传的中间件,我们可以在路由处理程序中使用 upload
中间件来处理文件上传的请求。
Koa2
Koa2 是一个基于 Node.js 的 Web 框架,它提供了一套优雅的中间件(middleware)机制,用于处理 Web 请求和响应。
1.安装 Koa2 :
yarn add koa
2.创建 Koa2 应用:
const Koa = require('koa');
const app = new Koa();
3.编写中间件:
Koa2 使用中间件机制来处理请求和响应。
中间件是一个函数,它可以访问请求对象和响应对象,并执行一些操作。
Koa2 中间件通过 app.use() 方法注册。
以下是一个简单的示例,展示了一个中间件的使用:
app.use(async (ctx, next) => {
// 执行一些操作
// ...
// 调用下一个中间件
await next();
});
4.处理路由:
Koa2 不内置路由功能,可以使用第三方模块(如 koa-router)来处理路由。
安装 koa-router 模块:
yarn add koa-router
使用 koa-router 创建和处理路由:
const Router = require('koa-router');
const router = new Router();
// 定义路由
router.get('/', async (ctx) => {
ctx.body = 'Hello, Koa!';
});
// 注册路由中间件
app.use(router.routes());
5.启动服务器:
使用 app.listen() 方法来启动服务器,并监听指定的端口:
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
疑问1:在 Koa2 中,中间件函数通常会接收两个参数:
ctx
和next
。
ctx
(Context):ctx
是一个上下文对象,它封装了请求和响应的相关信息。通过ctx
,你可以访问请求的头部、参数、URL 等信息,并设置响应的状态码、头部、内容等。ctx
提供了许多属性和方法来操作请求和响应。next
:next
是一个函数,用于调用下一个中间件函数。当一个中间件完成它的任务后,可以通过调用next()
来将控制权移交给下一个中间件函数。如果不调用next()
,后续的中间件将不会执行。- 在中间件函数中,
async
和await
是用于处理异步操作的关键字。通过将中间件函数声明为async
,可以在其中使用await
来等待异步操作的完成。
疑问2:await意思是让异步变成同步了吗?
实际上,await
并不会将异步操作变成同步操作。它只是用于在异步操作执行完成之前,暂停函数的执行,并等待异步操作的结果。
需要注意的是,只有在 async
函数内部才能使用 await
关键字,而且 async
函数本身也是异步的,它返回一个 Promise 对象,因此调用 async
函数时需要使用 await
或者处理返回的 Promise 对象。
疑问3:promise是什么东西?
Promise 是 JavaScript 中用于处理异步操作的一种机制,它提供了一种更优雅和可靠的方式来处理异步任务的结果。
在传统的回调函数模式中,异步操作的结果通常通过回调函数传递。这种方式可能导致回调地狱(callback hell)的问题,代码可读性较差,逻辑复杂。而 Promise 提供了一种链式调用的方式,使得异步操作的处理更加清晰和可维护。
疑问4:回调地狱什么意思
回调地狱(Callback Hell)指的是在异步编程中,多层嵌套的回调函数形成的一种代码结构,导致代码可读性差、难以理解和维护的问题。
当有多个异步操作需要依次执行时,往往会出现回调函数嵌套的情况。每个异步操作的回调函数又会包含另一个异步操作,这样就形成了嵌套的回调函数结构,导致代码缩进层级过深,难以跟踪和理解。
例如,以下是一个简化的回调地狱示例:
asyncOperation1(function(result1) {
asyncOperation2(result1, function(result2) {
asyncOperation3(result2, function(result3) {
// ... 更多的回调嵌套
});
});
});
这种回调地狱的代码结构很难阅读和维护,因为代码逻辑被分散在多个回调函数中,而且代码缩进深,增加了理解的难度。此外,错误处理也变得复杂,很难在合适的位置捕获和处理错误。
疑问5:通过将中间件函数声明为 async,可以在其中使用 await 来等待异步操作的完成。但是很多地方没有使用await啊,它怎么处理异步操作结束后的结果呢?
当中间件函数中没有使用 await
关键字时,它仍然是一个异步函数,它会返回一个 Promise 对象。在 Koa2 中,中间件函数可以返回一个 Promise 对象,用于处理异步操作的结果。如果没有使用 await
,则返回的 Promise 对象会立即被解析,不会等待异步操作的完成。
你可以通过在中间件函数中使用 .then()
和 .catch()
方法来处理 Promise 对象的结果。
例如,以下是一个示例代码:
app.use((ctx, next) => {
// 异步操作
const promise = someAsyncFunction();
promise.then((result) => {
// 处理异步操作成功的结果
ctx.body = result;
// 调用下一个中间件
return next();
}).catch((error) => {
// 处理异步操作失败的结果
console.error(error);
// 响应错误状态码
ctx.status = 500;
});
});
在上述示例中,someAsyncFunction()
返回一个 Promise 对象。我们通过调用 .then()
方法来处理异步操作成功的结果,通过调用 .catch()
方法来处理异步操作失败的结果。
在 .then()
方法中,我们可以设置相应的响应体(ctx.body
),并通过调用 next()
来调用下一个中间件。在 .catch()
方法中,我们可以处理错误情况,并设置相应的响应状态码。
即使没有使用 await
,仍然可以通过 Promise 的方法链式调用来处理异步操作的结果。这样可以保持代码的可读性,并且依然能够处理异步操作的成功和失败情况。
Mysql
身为后端开发人员,mysql我可太熟悉了,这里简单介绍下node操作Mysql的过程。
const mysql = require('mysql2');
// 创建数据库连接
const connection = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'guli',
});
// 连接数据库
connection.connect((err) => {
if (err) {
console.error('Failed to connect to MySQL:', err);
return;
}
console.log('Connected to MySQL');
});
// 创建数据
const createData = (data) => {
const sql = 'INSERT INTO nodejs_test (name, age) VALUES (?, ?)';
const values = [data.name, data.age];
connection.query(sql, values, (err, result) => {
if (err) {
console.error('Failed to create data:', err);
return;
}
console.log('Data created:', result);
});
};
// 读取数据
const readData = () => {
const sql = 'SELECT * FROM nodejs_test';
connection.query(sql, (err, results) => {
if (err) {
console.error('Failed to read data:', err);
return;
}
console.log('Data read:', results);
});
};
// 更新数据
const updateData = (id, data) => {
const sql = 'UPDATE nodejs_test SET name = ?, age = ? WHERE id = ?';
const values = [data.name, data.age, id];
connection.query(sql, values, (err, result) => {
if (err) {
console.error('Failed to update data:', err);
return;
}
console.log('Data updated:', result);
});
};
// 删除数据
const deleteData = (id) => {
const sql = 'DELETE FROM nodejs_test WHERE id = ?';
const values = [id];
connection.query(sql, values, (err, result) => {
if (err) {
console.error('Failed to delete data:', err);
return;
}
console.log('Data deleted:', result);
});
};
// 调用相应的函数执行 CRUD 操作
createData({ name: 'zs', age: '22' });
readData();
updateData(1, { name: 'zs', age: '23' });
deleteData(1);
// 断开数据库连接
connection.end((err) => {
if (err) {
console.error('Failed to disconnect from MySQL:', err);
return;
}
console.log('Disconnected from MySQL');
});
Socket编程
WebSocket 服务器端代码:
javascriptCopy code
const WebSocket = require('ws');
// 创建 WebSocket 服务器
const wss = new WebSocket.Server({ port: 8080 });
// 监听连接事件
wss.on('connection', (ws) => {
console.log('Client connected');
// 监听消息事件
ws.on('message', (message) => {
console.log('Received message from client:', message);
});
// 监听关闭事件
ws.on('close', () => {
console.log('Client disconnected');
});
});
WebSocket 客户端代码:
javascriptCopy code
const WebSocket = require('ws');
// 创建 WebSocket 客户端连接
const socket = new WebSocket('ws://localhost:8080');
// 监听连接事件
socket.onopen = () => {
console.log('Connected to server');
// 发送消息到服务器
socket.send('Hello from client');
};
// 监听消息事件
socket.onmessage = (event) => {
console.log('Received message from server:', event.data);
};
// 监听关闭事件
socket.onclose = () => {
console.log('Disconnected from server');
};
在上述代码中,我们使用 ws
模块创建了一个 WebSocket 服务器,并监听在端口 8080
。当客户端连接到服务器时,会触发 connection
事件,在事件处理程序中监听客户端的消息和关闭事件。
客户端通过创建一个 WebSocket 客户端连接,连接到服务器的 WebSocket 端口。在连接建立后,客户端发送一条消息到服务器,并监听服务器发送的消息和关闭事件。