前言
身为一个前端,如果想往更高的级别晋升的话,那么既要有深度,也要有广度,所以我们也应该多了解一些服务端的知识。
koa已经诞生很多年了,以轻便,简洁著称,今天我们就手撕koa源码
本节源码已经上传到github:github.com/Y-wson/Dail…
简单例子
const Koa = require("koa");
const koa = new Koa();
koa.use((ctx, next) => {
ctx.res.end("hello world");
});
koa.listen(3000, () => {
console.log("服务已开启");
});
此例子想必大家都能看懂,就是在浏览器上输出hello world, 从这个例子中,我们可以看出,引入了koa库,koa是个类,所以要实例一下,koa上有use和listen两个方法
手写源码
首先我们新建一个文件夹,取名MyKoa,代表我们自己的koa库
然后yarn init -y,初始化package.json
在package.json的main中指向lib文件夹下面的application文件。我们在讲require的时候,曾经说过,如果require找不到内置模块的话,会把路径当做文件来找,找到文件以后,如果有package.json,就找main所指向的文件,如果没有package.json,或者package.json没有main属性的话,那么就找index.js文件
我们的package.json文件如下
{
"name": "koa",
"version": "1.0.0",
"main": "lib/application",
"license": "MIT"
}
虽然koa使用的时候叫koa,但是在他的文件名确实application,application会继承Emitter类,这个类其实就是一个发布订阅模式,可以订阅和发布消息。如果有些方法在application.js中找不到,那可能就是继承自EventEmitter application.js初始化如下
const Emitter = require("events");
class Application extends Emitter {
constructor() {
// 先执行父类的构造函数
// 在进行一些初始化工作
super();
// 用来存储中间件函数
this.middleware = [];
this.context = {};
}
use() {}
listen() {}
}
module.exports = Application;
接下来我们就开始实现use方法
use方法的作用很简单,就是存储中间件函数,然后返回this实现链式调用
use(func) {
// 中间件必须是一个函数,不然就报错
if (typeof func !== "function") {
throw new TypeError("middleware must be a function");
}
this.middleware.push(func);
// 实现链式调用
return this;
}
然后实现listen方法,这个方法更简单,其实就是调用了原生的http.createServer方法,去开启服务
listen(...args) {
const server = http.createServer((req, res)=>{});
server.listen(...args);
}
这个时候我们就要思考了,我们的中间件什么时候执行?答案是创建服务的时候
所以改写
const server = http.createServer(this.callback);
接下来就是实现一下callback的逻辑
callback() {
// 把所有的中间件传给compose,来返回一个函数
const middlewareCompose = compose(this.middleware);
// callback返回值必须符合http.createServer参数形式
const handleRequest = (req, res) => {
// 创建格式化上下文
const context = this.createContext(req, res);
middlewareCompose(context);
};
return handleRequest;
}
现在我们先把主要的目标集中在compose这个函数,koa为了实现这个,还专门写了一个koa-compose库
// 异步递归遍历函数
// 传入中间件数组,返回一个函数
// 默认只执行第一个中间件,如果第一个中间件不执行next()方法的话,那么不会执行第二个中间件,依次类推
// 注意要用promise.resolve包裹,这样每一个中间件的返回值都是一个promise了,所以可以使用await next()
compose(middleware) {
return function (context) {
const dispatch = (i) => {
let fn = middleware[i];
if (i === middleware.length) {
return Promise.resolve();
}
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
};
// 执行第一个中间件
return dispatch(0);
};
}
接下来就剩最后一个方法了createContext,这个方法让很多人头疼,根本记不住
源码长这个样子
要想记住上面这个东西,我们首先知道request和req的区别?
所以这两个变量的区别就是request是Koa包装过的req,req是原生的请求对象。response和res也是类似的
既然request和response都只是包装过的语法糖,那其实Koa没有这两个变量也能跑起来。所以我们拎骨架的时候完全可以将这两个变量踢出去,这下骨架就清晰了。
createContext(req, res) {
const context = Object.create(this.context);
context.app = this;
context.req = req;
context.res = res;
return context;
}
那么我们的简版koa就写好啦,大家赶紧测试测试吧