koa源码(小白级教程)

115 阅读3分钟

前言

身为一个前端,如果想往更高的级别晋升的话,那么既要有深度,也要有广度,所以我们也应该多了解一些服务端的知识。

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,这个方法让很多人头疼,根本记不住

图片.png

源码长这个样子

要想记住上面这个东西,我们首先知道request和req的区别? 所以这两个变量的区别就是requestKoa包装过的reqreq是原生的请求对象。responseres也是类似的

既然requestresponse都只是包装过的语法糖,那其实Koa没有这两个变量也能跑起来。所以我们拎骨架的时候完全可以将这两个变量踢出去,这下骨架就清晰了。

createContext(req, res) {
    const context = Object.create(this.context);
    context.app = this;
    context.req = req;
    context.res = res;
    return context;
}

那么我们的简版koa就写好啦,大家赶紧测试测试吧

参考

手写Koa.js源码