Node.js介紹3-模塊化

node的模塊化管理是JavaScript語言向服務器語言邁進的重要一步役拴。

使用過Node.js的程序員對require肯定很熟悉:

var http = require('http');
var fs = require('fs');

下面我們看看node的模塊化是如何做的。

創(chuàng)建模塊

定義模塊有兩種方式:

  1. module.exports方式
    創(chuàng)建custom_hello.js
var hello = function() {
    console.log("hello!");
}
module.exports = hello;
  1. exports方式
    創(chuàng)建custom_goodbye.js
exports.goodbye = function() {
    console.log("bye!");
}

這兩種方式有什么區(qū)別呢括享,我們看看如何使用這兩個模塊:
創(chuàng)建測試程序app.js

var hello = require('./custom_hello');
var gb = require('./custom_goodbye');
hello();
gb.goodbye();

我們看到了使用起來有些不同之處,需要注意一下珍促。

導出多個函數(shù)和隱藏內(nèi)部實現(xiàn)

很明顯铃辖,exports只是module上的普通變量,所以我們可以在上面掛載我們想要的函數(shù)猪叙,需要隱藏的函數(shù)娇斩,我們不掛載就好了。這樣就方便的實現(xiàn)了功能隱藏穴翩。

// Private
var TWO = 2;
function sum(x, y) {
    return x + y;
}

// Public
module.exports = {
    x: 5,
    add2: function(num) {
        return sum(TWO, num);
    },
    addX: function(num) {
        return sum(module.exports.x, num);
    }
}

require的實現(xiàn)

require的定義在Module.js中

// Loads a module at the given file path. Returns that module's
// `exports` property.
Module.prototype.require = function(path) {
  assert(path, 'missing path');
  assert(typeof path === 'string', 'path must be a string');
  return Module._load(path, this, /* isMain */ false);
};

require只是Module._load的簡單封裝犬第,下面我們看看Module的作用。

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  if (parent && parent.children) {
    parent.children.push(this);
  }

  this.filename = null;
  this.loaded = false;
  this.children = [];
}
module.exports = Module;

Module有兩個作用:

  1. 保存加載的文件芒帕。
  2. 加載文件歉嗓。

Module._load

算法為:
檢查是否有緩存。

  1. 如果有背蟆,則返回 cachedModule.exports;
  2. 如果是native模塊鉴分,使用NativeModule加載。
  3. 都不是带膀,則創(chuàng)建一個新的模塊志珍,存在緩存,返回exports
Module._load = function(request, parent, isMain) {
  if (parent) {
    debug('Module._load REQUEST %s parent: %s', request, parent.id);
  }

  var filename = Module._resolveFilename(request, parent);

  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;
  }

  if (NativeModule.nonInternalExists(filename)) {
    debug('load native module %s', request);
    return NativeModule.require(filename);
  }

  var module = new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }

  Module._cache[filename] = module;

  tryModuleLoad(module, filename);

  return module.exports;
};

function tryModuleLoad(module, filename) {
  var threw = true;
  try {
    module.load(filename);
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
    }
  }
}

Module.prototype._compile

module.load(filename);函數(shù)最終會調(diào)用_compile函數(shù)垛叨。模塊加載是同步運行的碴裙,這個和瀏覽器的模塊加載器requirejs是不同的,在node中我們可以方便的使用同步代碼加載模塊点额。最后我們的目標是產(chǎn)生如下的包裝舔株。這個我們自己寫的模塊機制是不是很像。

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

模塊加載算法

這里說的算法實際上是查找文件的算法还棱,主要代碼在_findPath中载慈。

Module._findPath = function(request, paths) {
  var exts = Object.keys(Module._extensions);

  if (request.charAt(0) === '/') {
    paths = [''];
  }

  var trailingSlash = (request.slice(-1) === '/');

  var cacheKey = JSON.stringify({request: request, paths: paths});
  if (Module._pathCache[cacheKey]) {
    return Module._pathCache[cacheKey];
  }

  // For each path
  for (var i = 0, PL = paths.length; i < PL; i++) {
    var basePath = path.resolve(paths[i], request);
    var filename;

    if (!trailingSlash) {
      // try to join the request to the path
      filename = tryFile(basePath);

      if (!filename && !trailingSlash) {
        // try it with each of the extensions
        filename = tryExtensions(basePath, exts);
      }
    }

    if (!filename) {
      filename = tryPackage(basePath, exts);
    }

    if (!filename) {
      // try it with each of the extensions at "index"
      filename = tryExtensions(path.resolve(basePath, 'index'), exts);
    }

    if (filename) {
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
  }
  return false;
};

我們看到最后會緩存filenameModule._pathCache = {};,由于代碼調(diào)用比較多珍手,我們看看偽代碼會更清晰办铡。

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. let M = X + (json main field)
   c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   c. DIR = path join(PARTS[0 .. I] + "node_modules")
   b. DIRS = DIRS + DIR
   c. let I = I - 1
5. return DIRS

從上面的算法可以看到辞做,模塊加載機制會嘗試在不同的路徑搜索我們需要的模塊,比較智能寡具,但是也比較耗時間啊秤茅。下面我們來看一個對node模塊加載機制的改進。

提升node的模塊加載性能

提升的思路來源自這篇文章:faster-node-app-require童叠。上面的算法每次都需要在不同的目錄去找模塊框喳,如果我們固定下來路徑,就可以大大減少時間啦厦坛∥蹇澹可以在這里下載測試代碼。我們看看怎么實現(xiàn)的吧杜秸。

var Module = require('module');
var fs = require('fs');
var exists = fs.existsSync;

var _require = Module.prototype.require;
var SAVE_FILENAME =
  process.env.CACHE_REQUIRE_PATHS_FILE ?
  process.env.CACHE_REQUIRE_PATHS_FILE :
  './.cache-require-paths.json';
var nameCache = exists(SAVE_FILENAME) ? JSON.parse(fs.readFileSync(SAVE_FILENAME, 'utf-8')) : {};

var currentModuleCache;
var pathToLoad;

Module.prototype.require = function cachePathsRequire(name) {

  currentModuleCache = nameCache[this.filename];
  if (!currentModuleCache) {
    currentModuleCache = {};
    nameCache[this.filename] = currentModuleCache;
  }
  if (currentModuleCache[name] &&
    // Some people hack Object.prototype to insert their own properties on
    // every dictionary (for example, the 'should' testing framework). Check
    // that the key represents a path.
    typeof currentModuleCache[name] === 'string') {
    pathToLoad = currentModuleCache[name];
  } else {
    pathToLoad = Module._resolveFilename(name, this);
    currentModuleCache[name] = pathToprintCacheLoad;
  }

  return _require.call(this, pathToLoad);
};

從上面的代碼中可以看到放仗,把路徑緩存在./.cache-require-paths.json文件中了,所以下次就不用一次次嘗試了撬碟。這樣第一次啟動還是會慢诞挨,所以還可以有優(yōu)化空間,我們提前生成這個文件就好了呢蛤。在我的機器上亭姥,優(yōu)化過的代碼除了第一次以外,會比非優(yōu)化的快兩倍顾稀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坝撑,隨后出現(xiàn)的幾起案子静秆,更是在濱河造成了極大的恐慌,老刑警劉巖巡李,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抚笔,死亡現(xiàn)場離奇詭異,居然都是意外死亡侨拦,警方通過查閱死者的電腦和手機殊橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狱从,“玉大人膨蛮,你說我怎么就攤上這事〖狙校” “怎么了敞葛?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長与涡。 經(jīng)常有香客問我惹谐,道長持偏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任氨肌,我火速辦了婚禮鸿秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怎囚。我一直安慰自己卿叽,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布桩了。 她就那樣靜靜地躺著附帽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪井誉。 梳的紋絲不亂的頭發(fā)上蕉扮,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音颗圣,去河邊找鬼喳钟。 笑死,一個胖子當著我的面吹牛在岂,可吹牛的內(nèi)容都是我干的奔则。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔽午,長吁一口氣:“原來是場噩夢啊……” “哼易茬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起及老,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抽莱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骄恶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體食铐,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年僧鲁,在試婚紗的時候發(fā)現(xiàn)自己被綠了虐呻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡寞秃,死狀恐怖斟叼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情春寿,我是刑警寧澤犁柜,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站堂淡,受9級特大地震影響馋缅,放射性物質(zhì)發(fā)生泄漏扒腕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一萤悴、第九天 我趴在偏房一處隱蔽的房頂上張望瘾腰。 院中可真熱鬧,春花似錦覆履、人聲如沸蹋盆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栖雾。三九已至,卻和暖如春伟众,著一層夾襖步出監(jiān)牢的瞬間析藕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工凳厢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留账胧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓先紫,卻偏偏與公主長得像治泥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遮精,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,080評論 0 1
  • Node.js是目前非尘蛹校火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特本冲。 眾所周知准脂,在Netscape設計出JavaScri...
    w_zhuan閱讀 3,613評論 2 41
  • Node.js是目前非常火熱的技術(shù)眼俊,但是它的誕生經(jīng)歷卻很奇特。 眾所周知粟关,在Netscape設計出JavaScri...
    Myselfyan閱讀 4,072評論 2 58
  • 模塊 Node 有簡單的模塊加載系統(tǒng)疮胖。在 Node 里,文件和模塊是一一對應的闷板。下面例子里澎灸,foo.js加載同一個...
    保川閱讀 598評論 0 0
  • 一次在出租車上,與年齡相近的司機交談遮晚,他回憶起自己的歷歷往事:少年時脖頸上掛著鑰匙躑躅街頭性昭,因為沒有幼兒園可去,便...
    三皮騎士閱讀 379評論 0 0