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

时间:2021-05-12 22:42:33

When I was answering another question I came across a Node.js module with a top-level return statement. For example:

当我回答另一个问题时,我遇到了一个节点。具有*返回语句的js模块。例如:

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

This works without any errors and prints:

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

Trying to reach

in the standard output but not "dead code" - the return actually ceased execution.

在标准输出中,但不是“死代码”——返回实际上停止执行。

But according to the specification of return statements in ECMAScript 5.1,

但是根据ECMAScript 5.1的返回语句规范,

Semantics

语义

An ECMAScript program is considered syntactically incorrect if it contains a return statement that is not within a FunctionBody.

如果一个ECMAScript程序包含一个不属于FunctionBody的返回语句,那么它就被认为是语法错误的。

In the program shown above return is not within any function.

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

Then why doesn't this throw?

那为什么不投呢?

1 个解决方案

#1


86  

TL;DR

博士TL;

The modules are wrapped by Node.js within a function, like this:

模块由节点封装。函数内的js,如下:

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

So the above shown code is actually executed by Node.js, like this

上面显示的代码实际上是由Node执行的。js,像这样

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

That is why the program prints only Trying to reach and skips the console.log following the return statement.

这就是为什么程序只打印试图达到和跳过控制台的原因。根据返回语句进行日志记录。

Internals

This is where we need to understand how Node.js processes Modules. When you run your .js file with Node.js, it treats that as a module and compiles it with the v8 JavaScript engine.

这就是我们需要理解Node的地方。js流程模块。使用Node运行.js文件时。它将其作为一个模块,并使用v8 JavaScript引擎对其进行编译。

It all starts with runMain function,

一切从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();
};

In the Module._load function, a new Module object is created and it is loaded.

在模块。_load函数,创建一个新的模块对象并加载它。

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

The Module function's load does this,

模块函数的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;
};

Since our file's extension is js, we see what the Module._extensions has for .js. It can be seen here

因为我们的文件扩展名是js,所以我们可以看到模块是什么。_extensions为. js。在这里可以看到。

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

The module object's _compile is invoked in that function and this is where the magic happens,

模块对象的_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.

This is where the require function, used by our node modules's is created first.

这就是我们的节点模块使用的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;

And then there is something about wrapping the code,

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

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

We set out to find what Module.wrap does, which is nothing but

我们开始寻找什么模块。wrap会做,这只是。

Module.wrap = NativeModule.wrap;

which is defined in src/node.js file and that is where we find this,

在src/node中定义。这就是我们找到的,

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

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

This is how our programs have access to the magic variables, exports, require, module, __filename and __dirname

这就是我们的程序如何访问魔法变量、导出、需求、模块、__filename和__dirname的方式

Then the wrapped function is compiled and executed here with runInThisContext,

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

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

And then finally, the module's compiled wrapped function object is invoked like this, with values populated for exports, require, module, __filename and __dirname

最后,将像这样调用模块的已编译包装函数对象,并为export、require、module、__filename和__dirname填充值

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

This is how our modules are processed and executed by Node.js and that is why the return statement works without failing.

这就是我们的模块如何被节点处理和执行的方式。这就是为什么return语句可以正常工作。

#1


86  

TL;DR

博士TL;

The modules are wrapped by Node.js within a function, like this:

模块由节点封装。函数内的js,如下:

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

So the above shown code is actually executed by Node.js, like this

上面显示的代码实际上是由Node执行的。js,像这样

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

That is why the program prints only Trying to reach and skips the console.log following the return statement.

这就是为什么程序只打印试图达到和跳过控制台的原因。根据返回语句进行日志记录。

Internals

This is where we need to understand how Node.js processes Modules. When you run your .js file with Node.js, it treats that as a module and compiles it with the v8 JavaScript engine.

这就是我们需要理解Node的地方。js流程模块。使用Node运行.js文件时。它将其作为一个模块,并使用v8 JavaScript引擎对其进行编译。

It all starts with runMain function,

一切从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();
};

In the Module._load function, a new Module object is created and it is loaded.

在模块。_load函数,创建一个新的模块对象并加载它。

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

The Module function's load does this,

模块函数的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;
};

Since our file's extension is js, we see what the Module._extensions has for .js. It can be seen here

因为我们的文件扩展名是js,所以我们可以看到模块是什么。_extensions为. js。在这里可以看到。

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

The module object's _compile is invoked in that function and this is where the magic happens,

模块对象的_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.

This is where the require function, used by our node modules's is created first.

这就是我们的节点模块使用的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;

And then there is something about wrapping the code,

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

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

We set out to find what Module.wrap does, which is nothing but

我们开始寻找什么模块。wrap会做,这只是。

Module.wrap = NativeModule.wrap;

which is defined in src/node.js file and that is where we find this,

在src/node中定义。这就是我们找到的,

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

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

This is how our programs have access to the magic variables, exports, require, module, __filename and __dirname

这就是我们的程序如何访问魔法变量、导出、需求、模块、__filename和__dirname的方式

Then the wrapped function is compiled and executed here with runInThisContext,

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

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

And then finally, the module's compiled wrapped function object is invoked like this, with values populated for exports, require, module, __filename and __dirname

最后,将像这样调用模块的已编译包装函数对象,并为export、require、module、__filename和__dirname填充值

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

This is how our modules are processed and executed by Node.js and that is why the return statement works without failing.

这就是我们的模块如何被节点处理和执行的方式。这就是为什么return语句可以正常工作。