手写Express.js源码 (2)

Express这里之所以使用mixin,而不是普通的面向对象来继承,是因为它除了要mixin proto外,还需要mixin其他库,也就是需要多继承,我这里省略了,但是官方源码是有的。

express.js对应的源码看这里:https://github.com/expressjs/express/blob/master/lib/express.js

app.listen

上面说了,express.js只是一个空壳,真正的app在application.js里面,所以app.listen也是在这里。

// application.js var app = exports = module.exports = {}; app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };

上面代码就是调用原生http模块创建了一个服务器,但是传的参数是this,这里的this是什么呢?回想一下我们使用express的时候是这样用的:

const app = express(); app.listen(3000);

所以listen方法的实际调用者是express()的返回值,也就是上面express.js里面createApplication的返回值,也就是这个函数:

var app = function (req, res) { };

所以这里的this也是这个函数,所以我在express.js里面就加了注释,这个函数是http.createServer的回调函数。现在这个函数是空的,实际上他应该是整个web服务器的处理入口,所以我们给他加上处理的逻辑,在里面再加一行代码:

var app = function(req, res) { app.handle(req, res); // 这是真正的服务器处理入口 }; app.handle

app.handle也是挂载在app下面的,所以他实际也在application.js这个文件里面,下面我们来看看他干了什么:

app.handle = function handle(req, res) { var router = this._router; // 最终的处理方法 var done = finalhandler(req, res); // 如果没有定义router // 直接结束返回 if (!router) { done(); return; } // 有router,就用router来处理 router.handle(req, res, done); }

上面代码可以看出,实际处理路由的是router,这是Router的一个实例,并且挂载在this上的,我们这里还没有给他赋值,如果没有赋值的话,会直接运行finalhandler并且结束处理。finalhandler也是一个第三方库,GitHub链接在这里:https://github.com/pillarjs/finalhandler。这个库的功能也不复杂,就是帮你处理一些收尾的工作,比如所有路由都没匹配上,你可能需要返回404并记录下error log,这个库就可以帮你做。

app.get

上面说了,在具体处理网络请求时,实际上是用app._router来处理的,那么app._router是在哪里赋值的呢?事实上app._router的赋值有多个地方,一个地方就是HTTP动词处理方法上,比如我们用到的app.get或者app.post。无论是app.get还是app.post都是调用的router方法来处理,所以可以统一用一个循环来写这一类的方法。

// HTTP动词的方法 var methods = ['get', 'post']; methods.forEach(function (method) { app[method] = function (path) { this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, Array.prototype.slice.call(arguments, 1)); return this; } });

上面代码HTTP动词都放到了一个数组里面,官方源码中这个数组也是一个第三方库维护的,名字就叫methods,GitHub地址在这里:https://github.com/jshttp/methods。我这个例子因为只需要两个动词,就简化了,直接用数组了。这段代码其实给app创建了跟每个动词同名的函数,所有动词的处理函数都是一样的,都是去调router里面的对应方法来处理。。

我们注意到上面代码除了调用router来处理路由外,还有一行代码:

this.lazyrouter();

lazyrouter方法其实就是我们给this._router赋值的地方,代码也比较简单,就是检测下有没有_router,如果没有就给他赋个值,赋的值就是Router的一个实例:

app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router(); } }

app.listen,app.handle和methods处理方法都在application.js里面,application.js源码在这里:https://github.com/expressjs/express/blob/master/lib/application.js

Router

写到这里我们发现我们已经使用了Router的多个API,比如:

router.handle

router.route

route[method]

所以我们来看下Router这个类,下面的代码是从源码中简化出来的:

// router/index.js var setPrototypeOf = require('setprototypeof'); var proto = module.exports = function () { function router(req, res, next) { router.handle(req, res, next); } setPrototypeOf(router, proto); return router; }

这段代码对我来说是比较奇怪的,我们在执行new Router()的时候其实执行的是new proto(),new proto()并不是我奇怪的地方,奇怪的是他设置原型的方式。我之前在讲JS的面向对象的文章提到过如果你要给一个类加上类方法可以这样写:

function Class() {} Class.prototype.method1 = function() {} var instance = new Class();

这样instance.__proto__就会指向Class.prototype,你就可使用instance.method1了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxdwd.html