为什么Node.js中的模块级返回语句工作?

当我回答另一个问题时,我遇到了一个带有顶级return语句的Node.js模块。 例如:

 console.log("Trying to reach"); return; console.log("dead code"); 

这工作没有任何错误和打印:

 Trying to reach 

在标准输出而不是“ dead code ” – return实际上停止执行。

但是根据ECMAScript 5.1中的return语句的规范 ,

语义

如果ECMAScript程序包含不在FunctionBody内的返回语句,则 ECMAScript程序在语法上被认为是不正确的

在上面显示的程序中return不在任何函数内。

那为什么不扔呢?

TL; DR

模块在一个函数中被Node.js包装,如下所示:

 (function (exports, require, module, __filename, __dirname) { // our actual module code }); 

所以上面显示的代码实际上是由Node.js执行的,就像这样

 (function (exports, require, module, __filename, __dirname) { console.log("Trying to reach"); return; console.log("dead code"); }); 

这就是为什么程序只打印Trying to reachreturn语句之后Trying to reach并跳过console.log

内幕

这是我们需要了解Node.js如何处理模块的地方。 当你用Node.js运行你的.js文件时,它会把它当作一个模块,并用v8 JavaScript引擎编译。

这一切都从runMain函数开始,

 // bootstrap main module. Module.runMain = function() { // Load the main module--the command line argument. Module._load(process.argv[1], null, true); // Handle any nextTicks added in the first tick of the program process._tickCallback(); }; 

Module._load函数中, 创build一个新的Module对象并加载它 。

 var module = new Module(filename, parent); ... ... try { module.load(filename); hadException = false; 

Modulefunction的load做到这一点 ,

 // Given a file name, pass it to the proper extension handler. Module.prototype.load = function(filename) { debug('load ' + JSON.stringify(filename) + ' for module ' + JSON.stringify(this.id)); assert(!this.loaded); this.filename = filename; this.paths = Module._nodeModulePaths(path.dirname(filename)); var extension = path.extname(filename) || '.js'; if (!Module._extensions[extension]) extension = '.js'; Module._extensions[extension](this, filename); this.loaded = true; }; 

由于我们的文件的扩展名是js ,所以我们看到了Module._extensions.js 。 在这里可以看到

 // Native extension for .js Module._extensions['.js'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); module._compile(stripBOM(content), filename); }; 

module对象的_compile在该函数中被调用, 这就是魔术发生的地方 ,

 // Run the file contents in the correct scope or sandbox. Expose // the correct helper variables (require, module, exports) to // the file. // Returns exception, if any. 

这是我们的节点模块使用的require函数首先被创build的地方。

 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; 

然后有一些关于包装的代码,

 // create wrapper function var wrapper = Module.wrap(content); 

我们着手寻找Module.wrapfunction, 这是什么

 Module.wrap = NativeModule.wrap; 

这是在src/node.js文件中定义的,那是我们find这个的地方,

 NativeModule.wrap = function(script) { return NativeModule.wrapper[0] + script + NativeModule.wrapper[1]; }; NativeModule.wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ]; 

这是我们的程序如何访问魔术variables, exportsrequiremodule__filename__dirname

然后用runInThisContext编译并执行包装函数,

 var compiledWrapper = runInThisContext(wrapper, { filename: filename }); 

最后,模块的已编译的包装函数对象被调用,为exportsrequiremodule__filename__dirname填充值

 var args = [self.exports, require, self, filename, dirname]; return compiledWrapper.apply(self.exports, args); 

这就是我们的模块如何被Node.js处理和执行,这就是为什么return语句没有失败的原因。