摘要:在后續(xù)的總結(jié)中,我會(huì)繼續(xù)分析,并準(zhǔn)備將一些值得分析的逐一解讀,也會(huì)涉及一些。從一個(gè)官方示例開(kāi)始這是官方給出的一個(gè)簡(jiǎn)單程序,運(yùn)行后訪問(wèn)顯示。第一行載入了框架,我們來(lái)看源代碼中的。代碼的開(kāi)始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。
這兩天仔細(xì)看了看express的源碼,對(duì)其的整個(gè)實(shí)現(xiàn)有了較清晰的認(rèn)識(shí),所以想總結(jié)一下寫(xiě)出來(lái),如果有什么不對(duì)的地方,望指出。
這是第一篇,首先介紹一個(gè)最簡(jiǎn)單的express應(yīng)用運(yùn)行過(guò)程,初步分析了其在源碼中的具體實(shí)現(xiàn),還沒(méi)有涉及到一些比較重要的內(nèi)容比如路由組件的實(shí)現(xiàn)方式,中間件的觸發(fā)流程等。在后續(xù)的總結(jié)中,我會(huì)繼續(xù)分析,并準(zhǔn)備將一些值得分析的public api逐一解讀,也會(huì)涉及一些private api。
基于的版本截止寫(xiě)這篇文章時(shí)目前最新的tags是4.4.2。我是直接看的master分支。express的commits提交非常頻繁,但總體的實(shí)現(xiàn)思路應(yīng)該不會(huì)有大的變化。其在4.x后做了較大的改動(dòng),相對(duì)于3.x最大的地方在于不再依賴(lài)connect,并移除了幾乎所有的內(nèi)置中間件,具體的變動(dòng)請(qǐng)看官方wiki的 Migrating from 3.x to 4.x 及 New features in 4.x。
從一個(gè)官方示例開(kāi)始var express = require("express"); var app = express(); app.get("/", function(req, res){ res.send("Hello World"); }); app.listen(3000);
這是官方給出的一個(gè)簡(jiǎn)單程序,運(yùn)行后訪問(wèn)localhost:3000顯示Hello World。下面我們就來(lái)仔細(xì)看看這段程序。
首先第一行
var express = require("express");
這是典型的Node.js模塊載入代碼,關(guān)于Node.js的模塊載入機(jī)制,不了解的同學(xué)建議看看樸靈的深入Node.js的模塊機(jī)制,非常有幫助。
第一行載入了express框架,我們來(lái)看源代碼中的index.js。
module.exports = require("./lib/express");
好吧,還要繼續(xù)require,我們看./lib/express.js
exports = module.exports = createApplication;
從這里我們可以看出,程序的第一行express最后實(shí)際是這個(gè)createApplication函數(shù)。第二行則是運(yùn)行了這個(gè)函數(shù),然后返回值賦給了app。該函數(shù)代碼如下
var EventEmitter = require("events").EventEmitter; var mixin = require("utils-merge"); var proto = require("./application"); var req = require("./request"); var res = require("./response"); function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, proto); mixin(app, EventEmitter.prototype); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
可以發(fā)現(xiàn),這個(gè)就相當(dāng)于express的"main"函數(shù),其中完成了所有創(chuàng)建express實(shí)例所需要的動(dòng)作,并在執(zhí)行完畢后返回一個(gè)函數(shù)。
代碼的開(kāi)始定義了一個(gè)函數(shù),函數(shù)有形參req,res,next為回調(diào)函數(shù)。
函數(shù)體只有一條語(yǔ)句,執(zhí)行app.handle,handle方法在application.js文件中定義,此處是通過(guò)mixin導(dǎo)入(見(jiàn)下文),handle的代碼如下
app.handle = function(req, res, done) { var router = this._router; // final handler done = done || finalhandler(req, res, { env: this.get("env"), onerror: logerror.bind(this) }); // no routes if (!router) { debug("no routes defined on app"); // generate error var err = new Error("No routes or middlewares have been defined"); err.status = 500; done(err); return; } router.handle(req, res, done); };
它的作用就是將每對(duì)[req,res]進(jìn)行逐級(jí)分發(fā),作用在每個(gè)定義好的路由及中間件上,直到最后完成,具體的過(guò)程我們會(huì)在后續(xù)進(jìn)行分析。
然后來(lái)看看中間的兩行
mixin(app, proto); mixin(app, EventEmitter.prototype);
mixin是在頭部的require處載入的utils-merge模塊,它的代碼如下
exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; };
很明顯,mixin(app, proto);的作用即是將proto中所有的property全部導(dǎo)入進(jìn)app,proto在頭部的require處載入的是./lib/application.js文件,其中定義了大部分express的public api,如app.set,app.get,app.use...詳見(jiàn)官方的API文檔。
mixin(app, EventEmitter.prototype);則將Node.js的EventEmitter中的原型方法全部導(dǎo)入了app。
再來(lái)看接下來(lái)的兩行
app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app };
這里定義了app的request和response對(duì)象,使用了對(duì)象的字面量表示法,使其分別繼承自req(頂部導(dǎo)入的request.js)和res(頂部導(dǎo)入的response.js),并反向引用了app自身。為什么要這樣做呢?這個(gè)問(wèn)題我一開(kāi)始想不明白,后來(lái)我就干脆把這兩行代碼刪了,運(yùn)行,當(dāng)然就是報(bào)錯(cuò),答案就在錯(cuò)誤中的信息里。
TypeError: Object #
has no method "send"
顯示找不到"send"方法,為什么呢?首先我們從app.get()方法看起,不熟悉的人會(huì)找不到它在源碼中的位置,其實(shí)它在application.js中是這樣的
methods.forEach(function(method){ app[method] = function(path){ if ("get" == method && 1 == arguments.length) return this.set(path); this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, [].slice.call(arguments, 1)); return this; }; });
methods在頂部模塊引入中定義,其實(shí)是一個(gè)包含各個(gè)HTTP請(qǐng)求方法的數(shù)組,具體代碼在這里。
從上面的代碼中我們可以看到,這里實(shí)際上是遍歷了所有methods中定義的方法,當(dāng)然其中包括get,而且get方法是被"重載"的,即當(dāng)app.get();的參數(shù)只有一個(gè)時(shí)候,執(zhí)行的是獲取變量的功能,否則,執(zhí)行route組件中的route.get方法,將該路由和回調(diào)函數(shù)(即第二個(gè)參數(shù))存儲(chǔ)進(jìn)一個(gè)棧中(后續(xù)會(huì)進(jìn)一步分析)。
回到原來(lái)的問(wèn)題,在這里,關(guān)鍵是看中間的
this.lazyrouter();
我們看它的具體代碼
app.lazyrouter = function() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing") }); this._router.use(query()); this._router.use(middleware.init(this)); } };
它的作用是在第一次定義路由的時(shí)候初始化路由(添加基本的路由),注意最后一句用到了middleware模塊的init方法,繼續(xù)上代碼
exports.init = function(app){ return function expressInit(req, res, next){ if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express"); req.res = res; res.req = req; req.next = next; req.__proto__ = app.request; res.__proto__ = app.response; res.locals = res.locals || Object.create(null); next(); }; };
它的作用是初始化request和response,可以看到其中用到了我所疑惑app.request和app.respone,它使req和res繼承自了request.js和response.js中的定義,也因此在我去掉了那兩行代碼后會(huì)出現(xiàn)res.send找不到的情況。
另外,定義app.response對(duì)象時(shí)反引用自身,也使得后面在response對(duì)象中能夠通過(guò)this.app獲得所創(chuàng)建的express實(shí)例。
讓我們回到createApplication函數(shù),接下來(lái)是app.init();。顯然,作用是初始化,做哪些工作呢?
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
設(shè)定了cache對(duì)象(render的時(shí)候用到),各種setting的存儲(chǔ)對(duì)象,engines對(duì)象(模板引擎),最后進(jìn)行默認(rèn)的配置,代碼有點(diǎn)長(zhǎng)這里就不上了,就是做一些默認(rèn)的配置。
好了,createApplication函數(shù)就是這些,當(dāng)然,其中略去了很多重要的問(wèn)題,比如路由組件的實(shí)現(xiàn)方式,中間件的觸發(fā)流程等,這我會(huì)在后續(xù)的總結(jié)中進(jìn)行分析。
最開(kāi)頭的官方示例中還有最后一句
app.listen(3000);
代碼如下
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
實(shí)際上是調(diào)用了Node.js原生的http模塊的CreatServer方法,API文檔說(shuō)明是
http.createServer([requestListener])#
Returns a new web server object.The requestListener is a function which is automatically added to the "request" event.
方法返回的是一個(gè)web server對(duì)象,其中的參數(shù)為HTTP request事件觸發(fā)后執(zhí)行的函數(shù)(這里我們給的就是我們?cè)?b>createApplication函數(shù)中獲得的app)。
最后,返回的web server有一個(gè)監(jiān)聽(tīng)端口的listen方法,參數(shù)為需要監(jiān)聽(tīng)的端口號(hào),本示例中即為3000。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87548.html
摘要:就是每一個(gè)教程里面開(kāi)始教學(xué)的事例,啟動(dòng)服務(wù)器的回調(diào)函數(shù)。,從入口開(kāi)始分析源碼首先是把模塊的屬性全部進(jìn)里面去,在把事件的屬性全部進(jìn)里面去,這是為了給增加事件功能。 express4.X源碼解讀第一天 express4.X 跟3.X 有很大區(qū)別,4.X 去除了connect的依賴(lài),3.X基于connect的中間件基本全部不能用,如果還有可以使用的,也是4.X重寫(xiě)的。所以要想繼續(xù)使用這些熟悉...
摘要:載入了框架,我們來(lái)看源代碼中的。函數(shù)函數(shù)代碼如下代碼的開(kāi)始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。相應(yīng)的,等同于繼承,從而讓有了事件處理的能力。 此為裁剪過(guò)的筆記版本。 原文在此:https://segmentfault.com/a/11...原文在此: https://cnodejs.org/topic/574... 感謝@YiQi ,@leijianning 帶來(lái)的好文章。我稍作...
摘要:如果此時(shí)我們不想把文件輸出到內(nèi)存里,可以通過(guò)修改的源代碼來(lái)實(shí)現(xiàn)。服務(wù)啟動(dòng)成功。。。根據(jù)請(qǐng)求的,拼接出 ? webpack-dev-middleware 是express的一個(gè)中間件,它的主要作用是以監(jiān)聽(tīng)模式啟動(dòng)webpack,將webpack編譯后的文件輸出到內(nèi)存里,然后將內(nèi)存的文件輸出到epxress服務(wù)器上;下面通過(guò)一張圖片來(lái)看一下它的工作原理: showImg(https:...
摘要:最近在研究,學(xué)著使用,開(kāi)始不會(huì)用,就百度了一下,沒(méi)有百度到特別完整的解答。查閱了的,綜合了網(wǎng)友的博客,解讀了的源碼,以及使用和驗(yàn)證,終于明白了中的使用。默認(rèn)為網(wǎng)站域名過(guò)期時(shí)間,類(lèi)型為。使用插件,后續(xù)代碼直接使用或者即可 最近在研究express,學(xué)著使用cookie,開(kāi)始不會(huì)用,就百度了一下,沒(méi)有百度到特別完整的解答。查閱了express的API,綜合了網(wǎng)友的博客,解讀了cookie-...
摘要:最近開(kāi)始看源碼,并將源碼解讀放在了我的計(jì)劃中。相對(duì)于其他源碼解讀的文章,基本都會(huì)從整體設(shè)計(jì)開(kāi)始講起,樓主覺(jué)得這個(gè)庫(kù)有點(diǎn)特殊,決定按照自己的思路,從用代替說(shuō)起。源碼沒(méi)有出現(xiàn)注意,其實(shí)有出現(xiàn)一處,是為,而不是,而用代替之。 Why underscore 最近開(kāi)始看 underscore源碼,并將 underscore源碼解讀 放在了我的 2016計(jì)劃 中。 閱讀一些著名框架類(lèi)庫(kù)的源碼,就好...
閱讀 1096·2021-11-16 11:44
閱讀 1377·2019-08-30 13:12
閱讀 2418·2019-08-29 16:05
閱讀 3082·2019-08-28 18:29
閱讀 918·2019-08-26 13:41
閱讀 3238·2019-08-26 13:34
閱讀 2607·2019-08-26 10:35
閱讀 942·2019-08-26 10:28