以前就觉得Nodejs的MooTools库很奇怪,因为用他的时候,不需要把require的返回值保存起来,今天实在憋不住,就研究了下,对NodeJs的require机制又有了几分深刻的理解。
MooTools库的“奇怪”用法:
require('mootools');
var QueryCommand = new Class({
initialize: function (product, week, action) {
this.product = product;
this.weeknum = week;
this.action = action;
},
toJsonString: function () {
return JSON.stringify(this);
}
});
看到没,把require当作C++的include用了,之后的代码可以直接使用Class类型了。其实,MooTools的做法是把Class这个类型“附加”到了全局作用域。我觉得MooTools可能就是这么设计的,因为他是一个OO库嘛。
那么,MooTools是怎么把他自己附加到全局作用域的呢?其实很巧妙,是通过函数字面量做的:
(function(){
var Class = this.Class = new Type('Class', function(params){
if (instanceOf(params, Function)) params = {initialize: params};
var newClass = function(){
reset(this);
if (newClass.$prototyping) return this;
this.$caller = null;
var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
this.$caller = this.caller = null;
return value;
}.extend(this).implement(params);
newClass.$constructor = Class;
newClass.prototype.$constructor = newClass;
newClass.prototype.parent = parent;
return newClass;
});
......
})();
他通过函数字面量定义了一个匿名函数,然后立即执行之。这个是JS的惯用法。有一点要注意的是,在执行这个函数的时候,其实是没有this指针的!那么在函数内部使用this,访问的就是全局对象,就是那个始祖对象!
再看一个更加简单的例子:
app.js
require('./a.js')
console.log(a);
a.js
a = 3; //输出3
注意,上面不是var a=3;所以该语句实在全局作用域上附加了a这个属性(注意,也不是this!是始祖对象),然后呢,在app.js模块就能使用了。其实,在其他的模块也能访问了。
接下来说说,为什么会污染到全局作用域,nodejs不是用require来加载每个模块的嘛,模块应该互相独立的呀,其实nodeJs的模块加载顺序是这样的:
Module._load("xxx.js") --> var module = new Module(); --> module.load("xxx.js") --> module._compile() --> 最终调用的是被wrapper的模块代码,上面那个例子的话,最终执行的是:
(function (exports, require, module, __filename, __dirname) {
a = 3;
});
看一下module.compile()的代码就清楚了:
// Returns exception if any
Module.prototype._compile = function(content, filename) {
var self = this;
// remove shebang
content = content.replace(/^\#\!.*/, '');
function require(path) {
return self.require(path);
}
require.resolve = function(request) {
return Module._resolveFilename(request, self);
};
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
require.cache = Module._cache;
var dirname = path.dirname(filename);
if (Module._contextLoad) {
if (self.id !== '.') {
debug('load submodule');
// not root module
var sandbox = {};
for (var k in global) {
sandbox[k] = global[k];
}
sandbox.require = require;
sandbox.exports = self.exports;
sandbox.__filename = filename;
sandbox.__dirname = dirname;
sandbox.module = self;
sandbox.global = sandbox;
sandbox.root = root;
return runInNewContext(content, sandbox, filename, true);
}