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