Por que uma instrução de retorno de nível de módulo funciona no Node.js?

Quando eu estava respondendo a outra pergunta , encontrei um módulo Node.js com uma declaração de return nível superior. Por exemplo:

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

Isso funciona sem erros e impressões:

 Trying to reach 

na saída padrão, mas não em ” dead code ” – o return realmente parou a execução.

Mas de acordo com a especificação de declarações de return no ECMAScript 5.1 ,

Semântica

Um programa ECMAScript é considerado incorreto sintaticamente se contiver uma declaração de retorno que não esteja dentro de um FunctionBody .

No programa mostrado acima, o return não está dentro de nenhuma function.

Então, por que isso não acontece?

TL; DR

Os módulos são envolvidos pelo Node.js dentro de uma function, assim:

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

Então, o código mostrado acima é realmente executado pelo Node.js, como este

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

É por isso que o programa imprime apenas Trying to reach e pula o console.log seguindo a declaração de return .

Internals

É aqui que precisamos entender como o Node.js processa os módulos. Quando você executa seu arquivo .js com o Node.js, ele trata isso como um módulo e o compila com o mecanismo JavaScript da v8.

Tudo começa com a 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(); }; 

Na function Module._load , um novo object Module é criado e é carregado .

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

A load da function do Module faz isso ,

 // 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; }; 

Como a extensão do nosso arquivo é js , vemos o que o Module._extensions tem para .js . Pode ser visto aqui

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

O _compile do object do _compile é invocado nessa function e é aí que a mágica acontece ,

 // 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. 

É aqui que a function require , usada pelos módulos do nó, é criada primeiro.

 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; 

E então há algo sobre envolver o código,

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

Nós nos propusemos a descobrir o que o Module.wrap faz, que não é nada além de

 Module.wrap = NativeModule.wrap; 

que é definido no arquivo src/node.js e é aí que encontramos isso,

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

É assim que nossos programas têm access às variables ​​magic, exports , require , module , __filename e __dirname

Então a function empacotada é compilada e executada aqui com runInThisContext ,

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

E, finalmente, o object de function empacotado e compilado do módulo é chamado assim, com valores preenchidos para exports , require , module , __filename e __dirname

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

É assim que nossos módulos são processados ​​e executados pelo Node.js e é por isso que a instrução de return funciona sem falhar.

Intereting Posts