在Node.js中對模塊載入和執行進行了包裝,使得模塊文件中的變量在一個閉包中,不會污染全局變量,和他人衝突。
前端模塊通常是我們開發人員爲了避免和他人衝突才把模塊代碼放置在一個閉包中。
如何封裝Node.js和前端通用的模塊,我們可以參考Underscore.js 實現,他就是一個Node.js和前端通用的功能函數模塊,查看代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Create a safe reference to the Underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object via a string identifier, // for Closure Compiler "advanced" mode. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } |
通過判斷exports是否存在來決定將局部變量 _ 賦值給exports,向後兼容舊的require()
API,如果在瀏覽器中,通過一個字符串標識符“_”作爲一個全局對象;完整的閉包如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
(function()
{
//
Baseline setup
//
--------------
//
Establish the root object, `window` in the browser, or `exports` on the server.
var
root
=
this;
//
Create a safe reference to the Underscore object for use below.
var
_
=
function(obj)
{
if
(obj
instanceof
_)
return
obj;
if
(!(this
instanceof
_))
return
new
_(obj);
this._wrapped
=
obj;
};
//
Export the Underscore object for **Node.js**, with
//
backwards-compatibility for the old `require()` API. If we're in
//
the browser, add `_` as a global object via a string identifier,
//
for Closure Compiler "advanced" mode.
if
(typeof
exports
!==
'undefined')
{
if
(typeof
module
!==
'undefined'
&&
module.exports)
{
exports
=
module.exports
=
_;
}
exports._
=
_;
}
else
{
root._
=
_;
}
}).call(this);
|
通過function定義構建了一個閉包,call(this)是將function在this對象下調用,以避免內部變量污染到全局作用域。瀏覽器中,this指向的是全局對象(window對象),將“_”變量賦在全局對象上“root._”,以供外部調用。
和Underscore.js 類似的Lo-Dash,也是使用了類似的方案,只是兼容了AMD模塊載入的兼容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | ;(function() { /** Used as a safe reference for `undefined` in pre ES5 environments */ var undefined; /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, 'function': true, 'object': true, 'number': false, 'string': false, 'undefined': false }; /** Used as a reference to the global object */ var root = (objectTypes[typeof window] && window) || this; /** Detect free variable `exports` */ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; /** Detect free variable `module` */ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module; /** Detect the popular CommonJS extension `module.exports` */ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports; /*--------------------------------------------------------------------------*/ // expose Lo-Dash var _ = runInContext(); // some AMD build optimizers, like r.js, check for condition patterns like the following: if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { // Expose Lo-Dash to the global object even when an AMD loader is present in // case Lo-Dash was injected by a third-party script and not intended to be // loaded as a module. The global assignment can be reverted in the Lo-Dash // module by its `noConflict()` method. root._ = _; // define as an anonymous module so, through path mapping, it can be // referenced as the "underscore" module define(function() { return _; }); } // check for `exports` after `define` in case a build optimizer adds an `exports` object else if (freeExports && freeModule) { // in Node.js or RingoJS if (moduleExports) { (freeModule.exports = _)._ = _; } // in Narwhal or Rhino -require else { freeExports._ = _; } } else { // in a browser or Rhino root._ = _; } }.call(this)); |
再來看看Moment.js的封裝閉包主要代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
(function
(undefined)
{
var
moment;
//
check for nodeJS
var
hasModule
=
(typeof
module
!==
'undefined'
&&
module.exports);
/************************************
Exposing Moment
************************************/
function
makeGlobal(deprecate)
{
var
warned
=
false,
local_moment
=
moment;
/*global
ender:false */
if
(typeof
ender
!==
'undefined')
{
return;
}
//
here, `this` means `window` in the browser, or `global` on the server
//
add `moment` as a global object via a string identifier,
//
for Closure Compiler "advanced" mode
if
(deprecate)
{
this.moment
=
function
()
{
if
(!warned
&&
console
&&
console.warn)
{
warned
=
true;
console.warn(
"Accessing
Moment through the global scope is "
+
"deprecated,
and will be removed in an upcoming "
+
"release.");
}
return
local_moment.apply(null,
arguments);
};
}
else
{
this['moment']
=
moment;
}
}
//
CommonJS module is defined
if
(hasModule)
{
module.exports
=
moment;
makeGlobal(true);
}
else
if
(typeof
define
===
"function"
&&
define.amd)
{
define("moment",
function
(require,
exports,
module)
{
if
(module.config().noGlobal
!==
true)
{
//
If user provided noGlobal, he is aware of global
makeGlobal(module.config().noGlobal
===
undefined);
}
return
moment;
});
}
else
{
makeGlobal();
}
}).call(this);
|
從上面的幾個例子可以看出,在封裝Node.js和前端通用的模塊時,可以使用以下邏輯:
1 2 3 4 5 | if (typeof exports !== "undefined") { exports.** = **; } else { this.** = **; } |
即,如果exports對象存在,則將局部變量裝載在exports對象上,如果不存在,則裝載在全局對象上。如果加上ADM規範的兼容性,那麼多加一句判斷:
JavaScript
1
|
if
(typeof
define
===
"function"
&&
define.amd){}
|