成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

解讀express 4.x源碼(1)

summerpxy / 2614人閱讀

摘要:在后續(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ù)有形參reqres,next為回調(diào)函數(shù)。
函數(shù)體只有一條語(yǔ)句,執(zhí)行app.handlehandle方法在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文件,其中定義了大部分expresspublic api,如app.set,app.get,app.use...詳見(jiàn)官方的API文檔。
mixin(app, EventEmitter.prototype);則將Node.jsEventEmitter中的原型方法全部導(dǎo)入了app。

再來(lái)看接下來(lái)的兩行

app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };

這里定義了apprequestresponse對(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();
  };
};

它的作用是初始化requestresponse,可以看到其中用到了我所疑惑app.requestapp.respone,它使reqres繼承自了request.jsresponse.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

相關(guān)文章

  • express4.0源碼解析

    摘要:就是每一個(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ù)使用這些熟悉...

    paraller 評(píng)論0 收藏0
  • 筆記:解讀express 4.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)的好文章。我稍作...

    jzman 評(píng)論0 收藏0
  • webpack-dev-middleware@1.12.2 源碼解讀

    摘要:如果此時(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:...

    yearsj 評(píng)論0 收藏0
  • express中cookie的使用和cookie-parser的解讀

    摘要:最近在研究,學(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-...

    CODING 評(píng)論0 收藏0
  • 從用 void 0 代替 undefined 說(shuō)起

    摘要:最近開(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ù)的源碼,就好...

    Cc_2011 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<