Подтвердить что ты не робот

Почему оператор возврата уровня модуля работает в Node.js?

Когда я отвечал на еще один вопрос, я наткнулся на Node.js-модуль с инструкцией верхнего уровня return. Например:

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

Это работает без каких-либо ошибок и печатает:

Trying to reach

в стандартном выводе, но не "dead code" - действие return фактически прекращено.

Но согласно спецификации инструкций return в ECMAScript 5.1,

Семантика

Программа ECMAScript считается синтаксически неправильной, если она содержит оператор return, который не находится в пределах FunctionBody.

В программе, показанной выше, return не входит в какую-либо функцию.

Тогда почему это не бросается?

4b9b3361

Ответ 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 и пропускает console.log в соответствии с инструкцией return.

Внутренности

Здесь нам нужно понять, как Node.js обрабатывает модули. Когда вы запускаете свой .js файл с помощью Node.js, он рассматривает это как модуль и компилирует его с помощью механизма JavaScript v8.

Все начинается с 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 функция нового объекта модуля создана и он загружен.

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

Функция 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;
};

Так как наше расширение файла 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);
};

В этой функции вызывается 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.

Здесь создается require функция, используемая нашими модулями node.

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 does, которое не что иное, как

Module.wrap = NativeModule.wrap;

который определен в файле src/node.js, и именно там мы находим это,

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

Затем завернутая функция скомпилирована и выполнена здесь с 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 работает без сбоев.