【问题标题】:Why does a module level return statement work in Node.js?为什么模块级返回语句在 Node.js 中起作用?
【发布时间】:2015-05-11 09:05:48
【问题描述】:

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

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

这工作 without any errors 并打印:

Trying to reach

在标准输出中但不是“dead code” - return 实际上已停止执行。

但是根据specification of return statements in ECMAScript 5.1

语义

如果一个 ECMAScript 程序包含一个不在 FunctionBody 中的 return 语句,则认为它在语法上不正确。

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

那为什么不扔呢?

【问题讨论】:

    标签: javascript node.js return syntax-error node-modules


    【解决方案1】:

    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 function开始,

    // 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 函数中,new Module object is createdit is loaded

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

    Module function's load does this

    // 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 的含义。可以看到here

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

    module 对象的 _compile 在该函数中被调用,this is where the magic happens

    // 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 的作用,which is nothing but

    Module.wrap = NativeModule.wrap;
    

    which is defined in src/node.js file 这就是我们找到这个的地方,

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

    这就是我们的程序访问魔术变量exportsrequiremodule__filename__dirname的方式

    然后包装函数编译执行hererunInThisContext

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-21
      • 2020-06-30
      • 2020-11-29
      • 1970-01-01
      • 1970-01-01
      • 2021-01-28
      • 1970-01-01
      相关资源
      最近更新 更多