上一节讲了express的入口文件,当执行主函数,会调用app.init方法,这个方法就来源于application模块。

  这个模块有很多方法,目前仅仅过一下初始化方法:

app.init = function init() {
// 在render时缓存对应的view
this.cache = {};
// 保存对应ext的解析引擎
this.engines = {};
// 本地配置
this.settings = {}; this.defaultConfiguration();
};

  这里初始化了三个对象,作用在注释有简单介绍,实际用法在后面再做介绍。

  随后执行了默认配置方法,先不看那个,在这之前过一个工具方法。

app.set

app.set = function set(setting, val) {
// 如果只传一个参数相当于get
if (arguments.length === 1) return this.settings[setting];
// 打印日志
debug('set "%s" to %o', setting, val);
// set value
this.settings[setting] = val;
// 特殊键的设置会触发一些方法
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;
};

  这里的set方法对应官网API的app.set(name, value),虽然官网实例用的是app.set设置值,用app.get取值,但是从源码来看set传一个参数就相当于app.get。

  打印日志就暂时不管了,引入如下:

var debug = require('debug')('express:application');

  这个生成了一个日志打印机,后面的参数为对应的命名空间,会映射到process的一个环境变量上。

  而字符串中的%s、%o对应后面的参数,并会被format为指定格式的字符串,这里参考下C的prinf格式化输出吧,讲起来没完了,先搁着。

  当set的值为etag、query parser、trust proxy fn之一时,会触发一些编译函数并设置对应的fn值,对应的编译函数单独讲。

defaultConfiguration

  接下来看默认属性的设置(稍微调整了下代码结构):

app.defaultConfiguration = function defaultConfiguration() {
// 获取环境变量
var env = process.env.NODE_ENV || 'development'; // 相当于this.set(setting, true);
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);
this.set('view', View);
this.set('views', resolve('views'));
this.set('jsonp callback name', 'callback'); this.locals = Object.create(null);
this.mountpath = '/';
this.locals.settings = this.settings; // 兼容处理
// var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
configurable: true,
value: true
}); debug('booting in %s mode', env);
// 监听mount方法
this.on('mount', function onmount(parent) {
// ...
}); // 生产模式打开view cache属性
if (env === 'production') {
this.enable('view cache');
} // 4.x移除app.router方法
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.');
}
});
};

  可以看到仅仅是一些属性默认值的设置,并没有做其余的操作,非常的简单。

app.use

  其中有一个特别的点,就是每一个app在初始化都会默认监听一个mount事件,而这个事件会在何时触发呢?答案是use中。

  先把官网的案例贴出来看一眼:

var admin = express();
admin.on('mount', function (parent) {
console.log('Admin Mounted');
console.log(parent); // refers to the parent app
});
admin.get('/', function (req, res) {
res.send('Admin Homepage');
});
app.use('/admin', admin);

  当调用app.use时,会触发admin上的mount事件并打印一些字符串,接下来看看该方法。

  在看源码时,需要对方法有一个清晰的认识,官网介绍如下:

app.use([path,] function [, function...])

  其中path为指定的路径,可以不传,默认为/,后面的中间件函数至少要传一个,所有对指定路径的请求都会触发对应的中间件,看起来就像tapable的apply。

  整理后的use方法源码如下:

app.use = function use(fn) {
// 参数的偏移
// 如果传了path就是1
var offset = 0;
// 默认path
var path = '/'; // 参数修正
if (typeof fn !== 'function') {
var arg = fn;
// 当path为数组时
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// 直接判断数组中第一个元素是否不为函数
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
// 扁平化参数数组 获取所有的中间件
// 处理形如app.use(path,[middlewarefn1,middlewarefn2],subApp)的参数组合
var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) {
throw new TypeError('app.use() requires a middleware function')
} // 懒加载路由
// 该方法只有第一次调用是有效的 单例模式
this.lazyrouter();
var router = this._router; fns.forEach(function(fn) {
// quack quack 哈哈
// 当该中间件不是express应用时直接调用router.use
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn);
}
// 设置子应用的mountpath、parent
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this; // 这个涉及router模块 暂时不深入
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function(err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err);
});
}); // 触发mount事件
fn.emit('mount', this);
}, this); return this;
};

  由于path参数是可选的,而后面所有的function会被扁平化为一个一维数组,所以大体上可以根据中间件是否包含express应用而分为两种情况处理。

  当不包含express应用时,处理就十分简单,在初始化一个router对象后,依次对所有的中间件调用router.use(path,fn)。

  当包含express应用时,处理就不太一样了,自定义一个中间件函数并调用了app.handle方法,而这个handle最终会指向router.handle,所以就不深入讲解了。

  下面讲一些咸鱼方法。

app.engine

app.engine = function engine(ext, fn) {
// 这个应该叫模板解析函数?
if (typeof fn !== 'function') {
throw new Error('callback function required');
}
// 获取指定文件的扩展名
var extension = ext[0] !== '.' ?
'.' + ext :
ext;
// 将后缀与对应解析方式保存到engines对象上
this.engines[extension] = fn; return this;
};

app.METHOD

  这里特地请求方法,诸如get、post等等。

methods.forEach(function(method) {
app[method] = function(path) {
// app.get传一个参数时指向获取本地参数的app.set方法
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
this.lazyrouter();
var route = this._router.route(path);
// 调用route.METHOD(path,callback)
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});

  这里最后仍然把请求的具体实现指向了route模块的方法。

  可以稍微看一下这个methods的种类:

module.exports = getCurrentNodeMethods() || getBasicNodeMethods();

function getCurrentNodeMethods() {
// http.METHODS => 返回解析器支持的 HTTP 方法的列表
return http.METHODS && http.METHODS.map(function lowerCaseMethod(method) {
return method.toLowerCase();
});
} // 默认的请求方式集合
function getBasicNodeMethods() {
return [
'get', 'post', 'put', 'head', 'delete', 'options', 'trace', 'copy', 'lock', 'mkcol', 'move',
'purge', 'propfind', 'proppatch', 'unlock', 'report', 'mkactivity', 'checkout', 'merge',
'm-search', 'notify', 'subscribe', 'unsubscribe', 'patch', 'search', 'connect'
];
}

  这里会优先获取node的内置模块http的METHODS属性,如果获取不到就用本地定义的数据。

  尝试在本地打印了一下http.METHODS属性,果然不一样:

[
'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', 'GET', 'HEAD', 'LINK', 'LOCK',
'M-SEARCH', 'MERGE', 'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS',
'PATCH', 'POST', 'PROPFIND', 'PROPPATCH', 'PURGE', 'PUT', 'REBIND', 'REPORT', 'SEARCH',
'SUBSCRIBE', 'TRACE', 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE'
]

  全部大写,高大上,内容也多了不少。

app.all

app.all = function all(path) {
this.lazyrouter(); var route = this._router.route(path);
var args = slice.call(arguments, 1);
// 遍历所有的方法 依次调用route进行处理
for (var i = 0; i < methods.length; i++) {
route[methods[i]].apply(route, args);
} return this;
};

  这就很暴力了,直接遍历数组所有方法。

app.listen

app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

  从源码可知,这个方法实际上只是对node里监听端口方法的一种简化。

小结

  其实app的很多方法最终还是指向了其他模块,本身最主要的作用是保存了几个配置对象,负责在需要的时候取出对象的值。

  另外还有一个app.render其实也比较复杂,然而涉及到了view这个特殊模块,所以这里暂时不讲这个了。

.2-浅析express源码之applicaiton模块(1)-咸鱼方法的更多相关文章

  1. .3-浅析express源码之applicaiton模块(2)-app.render

    这个模块还漏了一个稍微复杂点的API,就是app.render,首先看官网的定义: app.render(view, [locals], callback) view为对应的文件名,locals为一个 ...

  2. .4-浅析express源码之applicaiton模块(3)-compile函数

    基本上application模块的api都看的差不多了,但是在app.set中还有一个遗漏点,如下: app.set = function set(setting, val) { // ...设值 / ...

  3. .5-浅析express源码之Router模块(1)-默认中间件

    模块application已经完结,开始讲Router路由部分. 切入口仍然在application模块中,方法就是那个随处可见的lazyrouter. 基本上除了初始化init方法,其余的app.u ...

  4. .7-浅析express源码之Router模块(3)-app[METHODS]

    之前的讨论都局限于use方法,所有方式的请求会被通过,这一节讨论express内部如何处理特殊请求方法. 给个流程图咯~ 分别给出app.METHODS与router.METHODS: // app. ...

  5. .6-浅析express源码之Router模块(2)-router.use

    这一节继续深入Router模块,首先从最常用的use开始. router.use 方法源码如下: proto.use = function use(fn) { var offset = 0; var ...

  6. express源码剖析--Router模块

    1.加载模块执行代码: methods.forEach(function(method){ //method是http协议的各种请求方法,如:get,put,headee,post Route.pro ...

  7. express源码分析之Router

    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的 ...

  8. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  9. 读Zepto源码之Event模块

    Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...

随机推荐

  1. 使用JAVA API 解析ORC File

    使用JAVA API 解析ORC File orc File 的解析过程中,使用FileInputFormat的getSplits(conf, 1)函数, 然后使用 RecordReaderreade ...

  2. .NET MVC 学习笔记(三)— MVC 数据显示

    . NET MVC 学习笔记(三)—— MVC 数据显示 在目前做的项目中,用的最多的数据展示控件就是table展示(说不是的请走开,不是一路人),以下详细阐述下table的使用方法. 先看效果: 上 ...

  3. 微信小程序如何跳转到另一个小程序

    微信小程序如何跳转到另一个小程序,要注意:在app.json文件里也要配置 navigateToMiniProgramAppIdList,如下图: "navigateToMiniProgra ...

  4. lost+found目录有啥用?

    Linux系统中根目录下或者新挂载的磁盘目录下有一个叫lost+found,它的作用是什么? 如果你运行fsck命令(文件系统检查和修复命令),它也许会找到一些数据碎片,这些文件碎片在硬盘中并没有引用 ...

  5. Spark踩坑——java.lang.AbstractMethodError

    今天新开发的Structured streaming部署到集群时,总是报这个错: SLF4J: Class path contains multiple SLF4J bindings. SLF4J: ...

  6. 利用Knockoutjs对电话号码进行验证

    问题来源 最近在项目中前端使用Knockoutjs,验证模块自然也是使用Knockoutjs来进行表单验证了,比较头痛,因为没有使用过Knockoutjs,更加别说要去用它做表单验证了,于是乎恶补了一 ...

  7. JS实现网页背景旋转缩放轮播效果

    实现效果:效果预览 css代码: .switch_images { display: inline-block; margin:; padding:; width: 100%; height: 100 ...

  8. Java入门-类HelloWorld是公共的,应在名为HelloWorld.java的文件中声明

    开始学习java了,搭好环境,notepad++中新建一个java文件,新建一个HelloWorld类, public class HelloWorld { public static void ma ...

  9. 人工智能-机器学习之Selenium(chrome驱动,火狐驱动)

    selenium是一个用于web应用程序测试的工具,Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE.Mozilla Firefox.Mozilla Suite等 ...

  10. 06-03 Java 面向对象思想概述、开发设计特征,类和对象的定义使用,对象内存图

    面向对象思想概述.开发设计特征 1:面向对象思想 面向对象是基于面向过程的编程思想. 面向过程:强调的是每一个功能的步骤 面向对象:强调的是对象,然后由对象去调用功能 2:面向对象的思想特点 A:是一 ...