犹豫了好久,最终决定把express(4.13.3)写成一个系列,这样或许可以多拿点kpi。本人假设读者有一定nodejs基础并了解express的基本使用。无论如何,这里都先放上一个广告, 本人博客 ,为何在群里宣传了这么久,star却并没有增加-_-。
在使用Express的时候,通常会以如下方式创建一个应用:
var express require('express'); var app = express(); app.listen(3000);
在不使用express的情况下,是这样做的:
var http = require('http'); http.createServer(function(req,res){ res.write('Hello world!'); res.end }).listen(3000);
通过源码,不难发现express其实是一个 工厂函数 ,用来创建app:(express/lib/express.js)
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
express/lib/application.js:
app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };
因此,在使用express的时候,所有的请求都交给app.handle进行处理了,虽然router也有自己的处理逻辑,但也是可以看作app的一个小型示例,会有自己root节点,所以这里插播一条将来会讲到的router部分的内容:
//匹配 localhost/ app.get('/',function(req,req){}); //匹配localhost/list/ app.use('/list', router); router.get('/',function(req,res){});
虽然本人觉得这部分并不重要,但一开始即讲app内的方法,似乎有点突然,所以这里仅贴上源码的内容凑紫薯:
var finalhandler = require('finalhandler'); var Router = require('./router'); var methods = require('methods'); var middleware = require('./middleware/init'); var query = require('./middleware/query'); var debug = require('debug')('express:application'); var View = require('./view'); var http = require('http'); var compileETag = require('./utils').compileETag; var compileQueryParser = require('./utils').compileQueryParser; var compileTrust = require('./utils').compileTrust; var deprecate = require('depd')('express'); var flatten = require('array-flatten'); var merge = require('utils-merge'); var resolve = require('path').resolve; var slice = Array.prototype.slice;
var app = exports = module.exports = {};
导出一个对象字面量,这里没什么好说的,init方法会扩展这个对象:
app.init = function init() { this.cache = {}; this.engines = {}; this.settings = {}; this.defaultConfiguration(); };
通过源码可知,初始化cache,engines(模版引擎),settings之后,调用默认配置函数进行默认配置。
listen方法很简单,使用apply函数调用http模块:
app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };
app.set = function set(setting, val) { if (arguments.length === 1) { // app.get(setting) return this.settings[setting]; } debug('set "%s" to %o', setting, val); // set value this.settings[setting] = val; // trigger matched settings switch (setting) { case 'etag': this.set('etag fn', compileETag(val)); break; case 'query parser': this.set('query parser fn', compileQueryParser(val)); break; case 'trust proxy': this.set('trust proxy fn', compileTrust(val)); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: false }); break; } return this; };
很好理解,根据 arguments
的数量来读取或者进行设置。当选项是 etag
, query parser
, trust proxy
时,会进行额外的设置。
刚才说过,在执行init的时候会进行默认配置 defaultConfiguration
:
app.defaultConfiguration = function defaultConfiguration() { var env = process.env.NODE_ENV || 'development'; // default settings this.enable('x-powered-by'); this.set('etag', 'weak'); this.set('env', env); this.set('query parser', 'extended'); this.set('subdomain offset', 2); this.set('trust proxy', false); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: true }); debug('booting in %s mode', env); this.on('mount', function onmount(parent) { // inherit trust proxy if (this.settings[trustProxyDefaultSymbol] === true && typeof parent.settings['trust proxy fn'] === 'function') { delete this.settings['trust proxy']; delete this.settings['trust proxy fn']; } // inherit protos this.request.__proto__ = parent.request; this.response.__proto__ = parent.response; this.engines.__proto__ = parent.engines; this.settings.__proto__ = parent.settings; }); // setup locals this.locals = Object.create(null); // top-most app is mounted at / this.mountpath = '/'; // default locals this.locals.settings = this.settings; // default configuration this.set('view', View); this.set('views', resolve('views')); this.set('jsonp callback name', 'callback'); if (env === 'production') { this.enable('view cache'); } Object.defineProperty(this, 'router', { get: function() { throw new Error('/'app.router/' is deprecated!/nPlease see the 3.x to 4.x migration guide for details on how to update your app.'); } }); };
这里做了几件事: 1.设置 x-powered-by
, etag
, env
, query parser
, subdomain offset
。
2.注册 mount
事件回调。
3.增加 this.locals(用于保存全局环境变量)
, this.mountpath(保存当前挂载路径)
。
至此,express框架app的主要方法已经介绍完毕,其实express框架并不复杂,希望各位了解其原理之后可以在开发少跳坑,少挖坑,并且能在需要时快速开发出牛逼的插件。