为什么模块级返回语句在 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 内的返回语句,则认为它在语法上不正确

在上面显示的程序中,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 reach并跳过return语句后面的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 函数中,将创建一个新的 Module 对象并加载该对象。

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

Module函数的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函数的地方。

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.wrap做什么,这只不过是

Module.wrap = NativeModule.wrap;

这是src/node.js文件中定义的,这就是我们找到它的地方,

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

这就是我们的程序如何访问魔术变量,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 语句可以正常工作的原因。

最新更新