为什么模块级别 return 语句在 Node.js 中有效?
Why does a module level return statement work in Node.js?
当我回答 时,我遇到了一个带有顶级 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,
Semantics
An ECMAScript program is considered syntactically incorrect if it contains a return statement that is not within a 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
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
function, a new Module object is created and it 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});'
];
这就是我们的程序如何访问魔术变量,exports
、require
、module
、__filename
和 __dirname
然后编译执行包装函数here with runInThisContext
,
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
最后,模块的已编译包装函数对象被调用,如 this,并为 exports
填充值, require
、module
、__filename
和 __dirname
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
这就是 Node.js 处理和执行我们的模块的方式,这就是为什么 return
语句可以正常工作的原因。
当我回答 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,
Semantics
An ECMAScript program is considered syntactically incorrect if it contains a return statement that is not within a
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
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
function, a new Module object is created and it 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});'
];
这就是我们的程序如何访问魔术变量,exports
、require
、module
、__filename
和 __dirname
然后编译执行包装函数here with runInThisContext
,
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
最后,模块的已编译包装函数对象被调用,如 this,并为 exports
填充值, require
、module
、__filename
和 __dirname
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
这就是 Node.js 处理和执行我们的模块的方式,这就是为什么 return
语句可以正常工作的原因。