前言
NodeJS这东西是不是学过了,之后感觉又像没学到什么东西???
我最近翻到了之前学习node的笔记,又结合了一些大佬的经验,这里把node系列相关的东西串联一下,分享给小伙伴们,顺便我自己也加深一下印象。
总共分为六篇
node打怪升级系列 - Koa篇
node打怪升级系列 - 手写中间件篇
node打怪升级系列 - 手写发布订阅和观察者篇
node打怪升级系列 - 手写compose(洋葱模型)
node打怪升级系列 - 手写简易脚手架篇
本文重点记录下Koa篇
里的装备
正文开始
1,Node.js的框架选择
目前市面上主流的Node.js框架就是Express
和Koa
二分天下啦
这里分为三大块
简单聊下二者之间的区别
Express
的社区更大,Koa
支持ES6+
的特性,小伙伴们可以自行选择~~~
- 社区
Express
的社区更大,有更多的学习资源和插件,可以快速上手一个项目
-
写法
-
Koa
比Express
更简洁,Koa
引入了ES6
特性通过async/await,消除了回调嵌套,使得异步/同步代码更易于阅读和理解。 -
Express
使用回调函数处理异步逻辑,特殊业务场景可能会导致回调嵌套,使得代码的可读性变差。
-
-
中间件
-
Koa
的中间件更强大一些,Koa
中间件封装成Promise对象
返回,更适合自己开发中间件 -
Express
未引入ES6
特性,Express
中间件封装成callback
返回
-
本文是Koa
框架的打怪篇
3,为啥有了Koa等框架的诞生
想必,小伙伴们在刚看到这句话的时候,心理就有了答案啦~~~
因为人太懒了,哈哈
但是,就是因为懒,也催生出了很多技术和科技的发展。
比如在日常的项目开发中,其实TCP
和Http
已经可以应付大多数业务场景了,但是Koa
还是应运而生了,为啥呢,因为框架的诞生是为了让开发人员少些代码,多些划水时间~~~ 呃~~~
4,HTTP连接
4.1,搭建一个服务端,实现一个getBooks接口
Node Api
不熟悉的可以看下⚡node打怪升级系列 - 基础篇
js
const http = require('http'); // 引入处理网络的http模块Api
const hostName = '127.0.0.1'; // 本地ip
const port = '9118'; // 端口号
http.createServer(function (req, res) {
if (req.url === '/getBooks' && req.method === 'GET') {
const data = [{ 'label': '语文' }, { 'label': '英文' }];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
} else {
res.writeHead(404);
res.end('Server is lost~~~');
}
}).listen(port, hostName)
console.log(`Http Server is listening on ${port}`)
http.createServer
:创建一个服务器并监听端口
res.writeHead
: 设置了HTTP响应的状态码和HTTP响应头。
res.end("Hello Http")
: 这一行用于结束HTTP响应,并给客户端发送响应体。
4.2,客户端调用getBooks接口
js
const http = require('http');
const options = {
hostname: '127.0.0.1',
port: 9118,
path: '/getBooks',
method: 'GET'
}
const req = http.request(options, (res) => {
res.setEncoding('utf-8');
res.on('data', (data) => {
console.log(`clint receive data: ${data}`);
});
});
req.on('error', (error) => {
console.error(`Request error: ${error}`);
});
req.end();
5,TCP连接
5.1,搭建一个服务端,建立长连接,监听客户端传过来的消息
并且发消息给客户端
js
const net = require('net'); // 引入处理网络的tcp模块Api
const hostName = '127.0.0.1';
const port = '9002';
// 当 TCP 建立成功时,会走这个callback函数,params 是 socket 对象
net.createServer((socket) => {
const url = [hostName, port].join(':');
console.log(`${url} 已经连接成功!`);
socket.on('data', (data) => {
console.log(`${url} 收到数据: ${data}`); // 监听客户端发来的消息
socket.write(`服务端已经收到了数据:${data}`);// 向客户端发送消息
})
socket.on('close', (data) => {
console.log(`${url} 正在关闭`);
})
}).listen(port, hostName);
console.log(`Server is listening on ${port}`)
5.2,客户端和服务端建立长连接,向服务端发送消息
并且监听服务端发回来的消息
js
const net = require('net');
const hostName = '127.0.0.1';
const port = '9002';
const client = new net.Socket();
const url = [hostName, port].join(':');
let count = 1;
client.connect(port, hostName, () => {
console.log(`客户端已经成功链接到 ${url}`);
const timer = setInterval(() => {
if (count > 10) {
client.write('我发完了'); // 向服务端发消息
clearInterval(timer);
return;
}
client.write(`这是我第${count++}次发数据`) // 向服务端发消息
}, 1000)
})
client.on('data', (data) => {
console.log(`收到数据:${data}`) // 监听服务端发来的消息
});
client.on('close', () => {
console.log('客户端关闭链接');
})
// client.destory()
6,Koa连接
安装Koa
框架
js
npm i -S koa koa-router koa-static koa2-cors koa-bodyparser
或者
js
yarn add koa koa-router koa-static koa2-cors koa-bodyparser
搭建一个服务端
js
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 定义getBooks接口
router.get('/getBooks', (ctx, next) => {
const books = [
{ label: '语文' },
{ label: '英文' },
];
ctx.type = "application/json"
ctx.body = { data: books, msg: 'success' };
});
// 将路由中间件添加到Koa应用中
app.use(router.routes());
app.use(router.allowedMethods());
// 启动服务器并监听端口
app.listen(3000, () => {
console.log('Server is listening on port 3000');
});
router.allowedMethods()
是一个中间件,路由定义的HTTP方法(如GET、POST、PUT等)可以处理,未定义的返回错误。
7,koa装饰器
装饰器是一个函数,写法是@ + 函数名
,一般用来对类进行处理,处理共享不同类之间存在相同业务逻辑的优雅写法
本文主要聊下装饰器在Koa实战
中的常用写法,想了解更多的可以去看看阮一峰大佬的ECMAScript 6 入门
js
function decorator(target, name, descriptor){}
@decorator
class A {}
-
target: 要装饰的
类本身
|| 要装饰的类的原型对象
-
name: 要装饰的属性名
-
descriptor: 该属性的描述对象
-
属性存取器(accessor): 我没用过
target在装饰类的时候是类本身,装饰类的方法的时候是类的原型对象
7.1,装饰类
js
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
上文有说target在装饰类的时候是类本身,所以:
js
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
7.2,装饰类的方法
js
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
function readonly(target, name, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
// 上文有说target在装饰类的方法时是类的原型对象
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
7.3,"装饰函数"
装饰器只能装饰类
和类的方法
,是不能装饰函数
的,因为函数存在函数提升
,但是可以通过高阶函数
直接执行
js
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
loggingDecorator
里的arguments
属于简写,不是很清楚的,可以看下下面这个
js
function loggingDecorator(wrapped) {
return function(arg) {
console.log('Starting');
const result = wrapped.apply(this,[arg]);
console.log('Finished');
return result;
}
}
运行下wrapped(123)
, 可得到下面的结果
可以看到,装饰器是对类的处理,高阶函数可以做到对函数的处理。
7.4,装饰器传参的写法
上面聊的都是@ + 函数名
,这里聊下@ + 函数名(参数)
是怎么写的
js
@Controller('/book')
class BookController {
}
function Controller(prefix = "") {
return function (target) {
// 给 controller 类添加路由的前缀
target.prefix = prefix;
}
}
7.5,装饰器来优化路由逻辑(写接口)
index.js
js
import Koa from 'koa';
import Router from 'koa-router';
import { controllers } from './utils/decorator';
import Book from './bookController';
const app = new Koa();
const router = new Router();
console.log("[[[controllers: ]]]", controllers);
controllers.forEach(item => {
// 每个路由的前缀
let { url, constructor, method, handler } = item;
const { prefix } = constructor;
if(prefix) url = `${prefix}${url}`;
console.log(url, method, handler)
router[method](url, handler);
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3008, () => {
console.log("server is running in 3008")
})
import Book from './bookController';
这句引用但是没用到的代码, 是因为避免无引用时候代码被Tree Shaking
删掉
controllers
的值收集时机和装饰器有点关系,在编译时,也就是在运行之前就开始收集好的路由信息
bookController.js
js
import { RequestMapping, Controller, RequestMethod } from "./decorator"
@Controller('/book')
export default class BookController {
@RequestMapping(RequestMethod.GET, '/all')
async getAllBooks(ctx) {
ctx.body = {
data: ['JS 一天精通', "css 从入门到放弃"]
}
}
@RequestMapping(RequestMethod.GET)
async getAll(ctx) {
ctx.body = {
data: ['JS XXXX', "css XXXX"]
}
}
}
上面的装饰器写法配合index.js中的controllers.forEach
运行之后和下面的写法是一个意思
小伙伴们如果觉得装饰器写法繁琐了,在实际开发过程中,可以选择自己喜欢的写法
~~~
js
router.get('/book/all', async (ctx) => {
ctx.body = {
data: ['JS 一天精通', "css 从入门到放弃"]
}
})
router.get('/book/getAll', async (ctx) => {
ctx.body = {
data: ['JS XXXX', "css XXXX"]
}
})
decorator.js
js
export const RequestMethod = {
"GET": "get",
"POST": "post",
"PUT": "put",
"DELETE": 'delete',
"OPTION": 'option',
"PATCH": 'patch'
}
export const controllers = [];
export function Controller(prefix = "") {
return function (target) {
// 给 controller 类添加路由的前缀
target.prefix = prefix;
}
}
/**
* 给类的方法添加装饰
*
*/
export function RequestMapping(method = "", url = "") {
return function (target, name, descriptor) {
let path = "";
// 判断有没有定义 url
if (!url) {
path = `/${name}`;
} else {
path = url;
}
const item = {
url: path,
method: method,
handler: target[name],
constructor: target.constructor,
};
controllers.push(item);
}
}
完结
这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。
欢迎转载,但请注明来源。
最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。