导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[深入25] Typescript
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
(一) 前置知识
(1) 一些单词
proper 非常 正确的 adj // properly 正确地 adv
dealing with 处理
expose 暴露 陈述
silent 沉默的 寂静
poll 轮询
stderr 标准错误
noop 空函数
(2) koa用到的nodeJs中的一些api
1
http.createServer()
koa案列:koa.listen(3000) -> http.createServer -> server.listen(3000)
nodeApi:
- http.createServer([options][, requestListener])
- 参数
- options:是一个配置对象,可选,一般不用配置
- requestListener:请求监听函数,可选
- 返回值
- http.Server的新实例,返回的新实例上具有 listen() 方法
官方文档:http://nodejs.cn/api/http.html#http_http_createserver_options_requestlistener
---
案例:
const http = require('http');
const server = http.createServer((req, res) => {
// 创建本地服务器来从其接收数据
// req请求
// res响应
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ // --- res.end()
data: 'Hello World!'
}));
});
server.listen(8000); // --------- server.listen()
2
process.nextTick
函数签名:process.nextTick(callback[, ...args])
作用:微任务 - 在同步方法执行完毕后,下一轮事件循环中的开始执行
特点:
- 执行时机在同步任务之后,在异步任务宏任务setTimeout之前
- 其实process.nextTick()会在node事件循环的各个周期优先执行
参数:
- 1. callback回调函数
- 2. args当调用callback时要传入的额外参数
3
node.js事件轮训机制 - 一共分为6个阶段
(1) timers 定时器阶段
- ( 计时 ) 和 ( 执行到点的定时器 )
(2) pending callbacks 阶段
- 执行某些系统操作的回调函数,比如 ( tcp错误类型 )
(3) idle, prepare 阶段
- 一些准备工作
(4) poll轮询阶段,是一个轮询队列
- 1. 如果 ( 轮询队列不为空 ),依次取出执行,直到 ( 轮询队列为空 ) 或者 ( 达到系统最大限制 )
- 2. 如果 ( 轮询队列为空 )
- 1. 如果之前设置过 ( setImmediate ) 函数,则直接进入下一个阶段 ( check阶段 )
- 2. 如果之前没有设置过setImmediate函数,则会在当前poll阶段 ( 等待 )
- 直到 ( 轮询队列 ) 添加进了新的回调函数,那么就会进入(4)阶段1的判断,继续执行
- 或者 ( 定时器 ) 到点了,也会进入下一个阶段 ( check阶段 )
(5) check 阶段
- 执行 ( setImmediate ) 回调函数
(6) close callbacks 阶段
- 执行 ( close ) 事件回调函数
-------> 注意点:process.nextTick() 会在nodejs事件轮询的 ( 任意阶段,优先执行 )
---
案例
console.log(1); // 同步任务
setTimeout(() => console.log(2)); // timer阶段执行 - nodejs事件轮询的第 1 个阶段
setTimeout(() => console.log(8), 0); // timer阶段执行 - nodejs事件轮询的第 1 个阶段
process.nextTick((n) => console.log(n), 3); // --- 在 node.js 事件轮询的 ( 任意阶段,优先执行 ),即在同步任务执行完毕后,优先执行
setImmediate(() => console.log(4)); // check阶段执行 - nodejs事件轮询的第 5 个阶段
new Promise((resolve) => {
console.log(5); // 同步任务
resolve();
console.log(7); // 同步任务
}).then((res) => console.log(6)); // --- 微任务
// 执行顺序 1 5 7 3 6 2 8 4
// 同步任务 1 5 7
// 异步任务(微任务) 3 6
// 异步任务(宏任务) 2 8 4
(3) koa的一些api
1
const app = new Koa()
- 1
- 问题:app上有哪些属性?
- 回答:
app.env -----------> 环境变量 -> 默认是 NODE_ENV 或 'development'
app.keys ----------> 签名的cookie密钥数组
app.proxy ---------> 当真正的代理头字段将被信任时忽略 `.subdomains` 的 `app.subdomainOffset` 偏移量,默认为 2
app.proxyIpHeader -> 代理ip消息头
app.maxIpCount ----> 从代理ip消息头读取的最大ips,默认是0表示无限
- 2
- 问题:如何设置这些属性
- 回答:有两种方法
- 1. 通过参数来设置: const app = new Koa({proxy: true})
- 2. 动态来设置:app.proxy = true
2
context
- Koa Context 将 node 的 `request` 和 `response` 对象封装到单个对象中
- 每个请求都将创建一个 `Context`,并在中间件中作为接收器引用,或者 `ctx` 标识符
app.use(async ctx => {
ctx; // 这是 Context
ctx.request; // 这是 koa Request
ctx.response; // 这是 koa Response
ctx.req; // Node 的 `request` 对象
ctx.res; // Node 的 `response` 对象
ctx.state; // 推荐的命名空间,用于通过中间件传递信息和你的前端视图
ctx.app; // 引用程序实例引用
});
(4) koa中间件的使用
- koa应用程序
- koa应用程序是一个包含 (
一组中间件函数) 的对象,按照类似 ( 堆栈 ) 的方式组织和执行
- koa应用程序是一个包含 (
- 中间价
- 中间件可以完成比如:
内容协商,缓存清理,代理支持,重定向等
- 中间件可以完成比如:
- next()
- 当一个中间件调用
next(),则该函数暂停,并将控制传递给定义的 (下一个中间件) - 当下游没有更多中间件执行后,( 堆栈将展开 ) 并且 ( 每个中间键恢复执行 ) 其上游行为
- 当一个中间件调用
1
koa中间件的执行顺序
---
const Koa = require("./lib/application.js");
const app = new Koa();
app.use(async (ctx, next) => { console.log(1); await next(); console.log(2); });
app.use(async (ctx, next) => { console.log(3); await next(); console.log(4); });
app.use(async (ctx, next) => { console.log(5); ctx.body = "测试中间执行顺序"; });
app.listen(1000, () => 'app run 1000')
---
// 执行顺序为 1 3 5 4 2
// 原理:
// - 1. 当中间件函数中,执行了next()方法时,其实就是执行下一个中间件,递归的执行下去
// - 2. 当执行完next(),接着执行当前中间件函数中剩下的代码,顺序就是函数调用栈的顺序
// - 3. 其实就是洋葱模型
2
context对象中包含了哪些属性 - ( ctx对象 )
---
context.app -> app属性指的是const app = new Koa() 生成的koa实例
context.req
context.res
context.request
context.response
context.originalUrl
context.state
---
app.use(async (ctx, next) => { console.log(1); await next(); console.log(2) })
(5) Object.create()
Obejct.create
---
const instanceObj = Object.create(prototypeObj)
1. 作用:以 ( 参数对象 ) 为 ( 原型 ),生成 ( 实例对象 ),实例对象完全继承参数对象的属性和方法
2. 注意点:生成的实例对象,( 实例对象本身没有任何属性和方法 ),只是继承了参数对象的所有属性和方法
3. 例子
const a = {name: []}
const b = Object.create(a)
b // {}
b.name === a.name // true
Object.getPrototypeof(b) === a // true
4. Object.create() 方法的模拟实现
- 实现原理:通过 ( 构造函数式继承 ) 来实现
function create(obj) {
function F(){} // 声明一个构造函数
F.prototype = obj // 将构造函数F的prototype赋值为obj,这样通过 new F() 生成的实例就能继承 obj 对象的属性和方法
return new F() // 返回实例
}
(6) 如何调试koa源码
- 本项目已经做好了调试配置,只需要执行
cnpm run dev断点调试index.js文件即可 - koa源码分析-源码调试仓库地址
1. 克隆koa源码:git clone git@github.com:koajs/koa.git
2. 安装依赖:cnpm install
3. 新建 index.js,并写入示例代码
4. 在 `vscode` 中选择 `运行和调试`, 新建 `launch.json` ,选择 `node`,并做如下配置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/index.js"
}
]
}
5. 启动项目:node index.js
6. 在index.js中打断点,通过 `运行和调试` 菜单中的 `开始调试` 按钮进行断点吊饰即可
(二) KOA源码目录结构说明
1. 入口文件
- 在package.json中通过 `main` 属性得知入口文件是 `lib/application.js`
2. 核心文件
- 核心文件都在 `lib` 文件夹中
- lib/application.js --> 主要就是Koa类相关代码,即 new Koa()
- lib/context.js ------> 是contxt对象相关
- lib/request.js ------> request相关
- bli/response.js -----> response相关
3. 依赖
- koa-compose ---------> 处理中间件
- on-finished ---------> 主要作用:当 HTTP 请求关闭、完成或出错时执行回调
(三) 源码主流程 - 中间件
1.
首先有这样一段代码
const Koa = require("./lib/application.js");
const app = new Koa();
app.use(async (ctx, next) => { console.log(1); await next(); console.log(2); });
app.use(async (ctx, next) => { console.log(3); await next(); console.log(4); });
app.use(async (ctx, next) => { console.log(5); ctx.body = "测试中间执行顺序"; });
app.listen(1000, () => 'app run 1000')
---
// 执行顺序为 1 3 5 4 2
// 原理:
// - 1. 当中间件函数中,执行了next()方法时,其实就是执行下一个中间件,递归的执行下去
// - 2. 当执行完next(),接着执行当前中间件函数中剩下的代码,顺序就是函数调用栈的顺序
// - 3. 其实就是洋葱模型
2.
const app = new Koa()
app.use()
app.listen()
对应源码如下
---
class Application extends Emitter {
constructor(options) {}
// ------------------------------- use
use(fn) {
this.middleware.push(fn);
return this;
}
// ------------------------------- listen
listen(...args) {
const server = http.createServer(this.callback()); // http.createServer是node原生api
return server.listen(...args);
}
// ------------------------------- callback
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res); // 创建context对象,包含request,resposne,req,res等属性
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
// ------------------------------- handleRequest
handleRequest(ctx, fnMiddleware) {
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
// 中间件的执行过程
// 所以核心就是这个 fnMiddleware(ctx)
}
}
3
fnMiddleware(ctx)
- fnMiddleware(ctx) 就是执行 const fn = compose(this.middleware) 返回的fn
- 而compose是依赖库 `koa-compose` ,具体代码如下
---
// ------------------------------------------------------------------------------------------------------------------- compose
// compose
// - 参数
// - 中间价组成的数组,每个中间件必须是函数
// - 返回值
// - 返回一个函数
function compose(middleware) {
if (!Array.isArray(middleware))
throw new TypeError("Middleware stack must be an array!"); // 参数必须是数组
for (const fn of middleware) {
if (typeof fn !== "function")
throw new TypeError("Middleware must be composed of functions!"); // 每个中间件必须由函数组成
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1;
return dispatch(0);
function dispatch(i) {
// 调用时:dispatch(0)
if (i <= index) {
return Promise.reject(new Error("next() called multiple times"));
// i <= index 证明next()方法被多次调用
// dispatch(0)时得出: ( 0<=-1不成立 )
}
index = i;
// 1
// i = 0 --> index = i = 0
let fn = middleware[i];
// 1
// middleware[0] 第一个中间件函数
if (i === middleware.length) fn = next;
// 当为最后一个中间件时,继续执行dispatch() --> fn=next=undefined --> return Promise.resolve(),
// - next不存在,return Promise.resolve()
// - next存在,则执行next(context, dispatch.bind(null, i+1))
// fn不存在
if (!fn) return Promise.resolve();
// fn存在
// 则继续往下执行
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
// 返回Promise成功的结果 -> 即执行中间件,中间件返回值作为promise成功状态的结果值
// 1
// 中间件函数的结构
// app.use(async (ctx, next) => { ... })
// async (ctx, next) => {...}
// 2
// fn(context, dispatch.bind(null, i + 1)) ---> next() 其实就是这里的 dispatch() 函数 ---> i+1就是下一个中间件
// 2022.04.11 添加一个问题
// 问题:到底是什么是中间件中的next
// 回答:
// - 1. 中间件 app.use(async (ctx, next) => { ... })
// - 2. next 就是上面的 dispatch 函数
// - 3. 而 dispatch 又会去执行下一个中间件,不断重复,直到最后一个中间件
// - 4. 还要注意 next() 调用的时机,如果next() 后面还有代码,在next()执行完后,还会回来继续执行
} catch (err) {
return Promise.reject(err);
}
}
};
}
4
梳理中间件
- 调用顺序:app.listen() ---> callback() ---> handleRequest() ---> 中间件fn(ctx).then(handleResponse).catch(onerror)
- fnMiddleware = compose(this.middleware) = function (context, next) => dispatch(0)
---
中间件 - 具体的执行过程
1. fnMiddleware(ctx).then(handleResponse).catch(onerror)
2. (function (context, next) => dispatch(0)).then(handleResponse).catch(onerror)
3. dispatch(0).then(handleResponse).catch(onerror)
4. Promise.resolve(middlewareFn0(context, dispatch.bind(null,1)))
5. Promise.resolve(async(ctx, dispatch) => {... dispatch(1) ...})
6. 最终形态如下
const [fn1, fn2, fn3] = this.middleware()
const fnMiddleware = function(context, next) {
return Promise.resolve(fn1(context, function next1() {
return Promise.resolve(fn2(context, function next2() { // 每个中间件,如果存在next就return Promise.resolve(fn(context, dispatch.bind(null, i + 1))),不存在就 return Promise.resolve()
return Promise.resolve(fn3(context, function next3() { // 最后一个中间件没有next函数了,因为已经是最后一个
return Promise.resolve()
}))
}))
}))
}
7. fnMiddleware() 执行的最终状态如下
- 7.1
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
ctx.body = "测试中间执行顺序";
});
- 7.2
fnMiddleware() => Promise.resolve(
// fn1()
console.log(1)
await Promise.resolve( // next()
// fn2()
console.log(3)
await Promise.resolve( // next()
// fn3()
console.log(5)
return Promise.resolve()
)
console.log(4)
)
console.log(2)
)
.then(handleResponse)
.catch(onerror)
// 13542
源码分析仓库地址
资料
koa官网 koa.bootcss.com/
koa源码分析 juejin.cn/post/699867…
川神 juejin.cn/post/684490…