为什么模块级别的return语句在Node.js中有效?

the*_*eye 47 javascript return syntax-error node.js node-modules

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

console.log("Trying to reach");
return;
console.log("dead code");
Run Code Online (Sandbox Code Playgroud)

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

Trying to reach
Run Code Online (Sandbox Code Playgroud)

在标准输出但不是" dead code" - return实际停止执行.

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

语义

如果ECMAScript程序包含不在a内的return语句,FunctionBody则认为它在语法上是不正确的.

在上面显示的程序中return不包含任何功能.

那为什么不扔?

the*_*eye 98

TL; DR

模块由函数中的Node.js包装,如下所示:

(function (exports, require, module, __filename, __dirname) {
    // our actual module code
});
Run Code Online (Sandbox Code Playgroud)

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

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

这就是程序仅打印Trying to reach并跳过console.log以下return语句的原因.

内幕

这是我们需要了解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();
};
Run Code Online (Sandbox Code Playgroud)

在该Module._load函数中,创建一个新的Module对象加载它.

var module = new Module(filename, parent);
...
...
try {
  module.load(filename);
  hadException = false;
Run Code Online (Sandbox Code Playgroud)

这个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;
};
Run Code Online (Sandbox Code Playgroud)

由于我们的文件的扩展名是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);
};
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

这是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;
Run Code Online (Sandbox Code Playgroud)

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

// create wrapper function
var wrapper = Module.wrap(content);
Run Code Online (Sandbox Code Playgroud)

我们开始寻找什么Module.wrap,哪个只是

Module.wrap = NativeModule.wrap;
Run Code Online (Sandbox Code Playgroud)

src/node.js文件中定义,这是我们发现的地方,

NativeModule.wrap = function(script) {
  return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};

NativeModule.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];
Run Code Online (Sandbox Code Playgroud)

这是我们的程序是如何有机会获得神奇的变量,exports,require,module,__filename__dirname

然后包装的功能是编译和执行在这里runInThisContext,

var compiledWrapper = runInThisContext(wrapper, { filename: filename });
Run Code Online (Sandbox Code Playgroud)

然后终于,模块的编译包装的函数对象调用像这样,以填充值exports,require,module,__filename__dirname

var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
Run Code Online (Sandbox Code Playgroud)

这就是我们的模块由Node.js处理和执行的方式,这就是为什么return语句可以正常工作的原因.