Node.js中的require()如何工作?

79

我尝试了这个:

// mod.js
var a = 1;
this.b = 2;
exports.c = 3;

// test.js
var mod = require('./mod.js');
console.log(mod.a);    // undefined
console.log(mod.b);    // 2
console.log(mod.c);    // 3, so this === exports?

因此我想象require()可能是这样实现的:

var require = function (file) {
    var exports = {};
    var run = function (file) {
        // include "file" here and run
    };
    run.apply(exports, [file]);
    return exports;
}

这样是正确的吗?请帮我理解require(),或者告诉我在哪里可以找到源代码。谢谢!

7个回答

57

源代码在这里exports/require不是关键字,而是全局变量。你的主要脚本在一个函数中被包装,该函数在开始之前,并具有所有全局变量,如requireprocess等。

请注意,虽然module.js本身使用了require(),但它是另一个require函数,而且它是在名为“node.js”的文件中定义的。

上述的副作用是:在模块中间(不属于任何函数)放置“return”语句是完全可以的,实际上相当于“注释掉”了其余代码。


这并不会让它变得更简单。该模块使用了 require,同时也定义了 require。在只看源代码的情况下,我发现这个操作有点难以理解。 - polkovnikov.ph
在模块本身中需要的是不同的require。简化版本的Module被创建来引导模块系统-在这里查看代码-https://github.com/nodejs/node/blob/v4.0.0/src/node.js#L861-L949 - Andrey Sidorov
这些全局变量及其返回值的文档在哪里? - Srikan
@Srikan 在官方文档中提到 - https://nodejs.org/dist/latest-v8.x/docs/api/modules.html#modules_the_module_wrapper (我关于调用exports/require全局变量的说法并不完全正确 - 通常它们是传递给包装函数的参数,在加载模块时被调用) - Andrey Sidorov
require不是全局变量tmk,它绑定到当前文件的__dirpath等。 - Alexander Mills
1
@AlexanderMills 这不完全是全局变量,而是因为每个模块都被包装在一个函数内部,并且require作为该函数的参数之一传递。 - Andrey Sidorov

11
var mod = require('./mod.js');

require 是一个函数,它接受一个名为 path 的参数,在这种情况下,路径为 ./mod.js

当调用 require 时,会发生一系列的任务:

  1. 调用在lib/module.js中声明的 Module.prototype.require 函数,该函数断言路径存在且为字符串

  2. 调用 lib/module.js 中的函数 Module._load,该函数通过调用 Module._resolveFilename(request, parent, isMain) 解析文件

  3. 调用 Module._resolveFilename 函数,并检查模块是否为本地模块(本地模块由在 lib/internal/bootstrap_node.js 中定义的 NativeModule 函数返回), 如果是,则返回该模块,否则检查路径的字符数(必须至少2个字符)和一些字符(路径必须以 ./ 开头) 通过在 lib/internal/bootstrap_node.js 中定义的 Module._resolveLookupPaths 函数
  4. 检查包含文件的目录
  5. 如果路径包含扩展名(在我们的例子中是:mod.js),则在 lib/path.js 中定义的 basename 函数将检查扩展名是否为 "js"
  6. 然后它将为给定的文件创建一个新模块 var module = new Module(filename, parent);
  7. 内容将通过在lib/internal/bootstrap_node.js中定义的函数 NativeModule.prototype.compile编译
  8. NativeModule.wraplib/internal/bootstrap_node.js 中定义,它接受编译的mod.js的JavaScript内容并对其进行包装:它将其包装在一些其他代码中,使所有这些工作正常运行。 因此,在 mod.js 中编写的代码被包装在函数表达式中。 这意味着您在 Node 中编写的所有内容都在 V8 中运行
  9. module.exports 就是返回的内容

9
Andrey 展示了源代码,但如果你也想知道如何使用它,容易和简单的解释在这里(http://nodejs.org/api/modules.html)。
这对我来说是两个很好的例子。
//foo.js, multiple methods
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

//circle.js
var PI = Math.PI;
exports.area = function (r) {
  return PI * r * r;
};
exports.circumference = function (r) {
  return 2 * PI * r;
};

//bar.js
var square = require('./square.js');
var mySquare = square(2);
console.log('The area of my square is ' + mySquare.area());

//square.js, single method
module.exports = function(width) {
  return {
    area: function() {
      return width * width;
    }
  };
}

我最喜欢的模式是:
(function (controller) {

  controller.init = function (app) {

    app.get("/", function (req, res) {
        res.render("index", {});
    });

  };
})(module.exports);

如果定义了一个 var express = require('express'),为什么在它之后,他们还要重新定义另一个变量作为 var app = express() - TomSawyer
不明白你最喜欢的模式与require有什么关系。 - ishandutta2007
@TomSawyer,因为require('express')返回一个函数,该函数返回一个应用程序。这只是他们构建它的方式。希望自从4年前你问这个问题以来,你已经回答了它。 - Eric Jeker

2

0

试试这个。
这是我用来创建与Node.js相同功能的代码片段。

/*
FILE: require.js
*/
/*
This is the file used
*/
window.require = function(src, ret) {
  if (src === 'jsmediatags') {
    src = 'https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.5/jsmediatags.js';
  };
  var d = document.createElement('script');
  d.src = src;
  document.head.appendChild(d);
  var fullURL = src.split('://');
  var neededURL = fullURL[1];
  var nameParts = neededURL.split('/');
  var nameNUM = nameParts.length - 1;
  var fileName = nameParts[nameNUM];
  var g = fileName.split('.');
  var global = g[0];
  if (ret === true) {
    return window[global]
  };
};

See if this works, and to add more files to its library, just type more in. (if (src===yourfilenamehere) { src = "path/to/your/file" }


1
这并没有回答 OP 所问的问题。OP 问的是 require 函数是如何工作以及如何实现的。这个解决方案是使用纯 JavaScript 重新创建 node.jsrequire 函数的方法。 - Tyler2P

0
在Node.js中,模块加载机制会在第一次调用require时缓存模块。这意味着每次使用require('xyz-module')都会得到相同的xyz-module实例,确保模块类似于单例,并且在应用程序中具有相同的状态。
您可以从文件系统或已安装的模块中加载本地模块和路径引用。如果传递给require函数的标识符不是本地模块或文件引用(以/、../、./或类似方式开头),则Node.js将查找已安装的模块。它将遍历您的文件系统,在node_modules文件夹中查找引用的模块。它从当前模块的父目录开始,然后移动到父目录,直到找到正确的模块或达到文件系统的根目录。

-10

源代码可以在下载旁边找到:http://nodejs.org/。exports/require 是关键字,我认为它们不是直接用 JavaScript 编写的。Node 是用 C++ 编写的,JavaScript 只是围绕 C++ 核心的脚本外壳。


当你只是“想”或猜测时,最好不要回答问题。当一个模块从文件系统中加载并解析后,它会被包装在一个函数中,并由v8引擎编译,最后该模块会被缓存。requiremodule__filename等都是在编译后注入到模块中的函数和变量,而模块运行在v8引擎上下文中,但模块本身是一个闭包,因此变量和函数永远不会冲突(除非您使用全局变量并弄乱了它们)。 - Jone Polvora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接