专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
OSC开源社区  ·  Gitee开源MCP ... ·  2 天前  
OSC开源社区  ·  【直播预约】开源、可定义数据中台AllDat ... ·  2 天前  
程序员的那些事  ·  董事长刺死CTO(2):董事长早已写好复仇名 ... ·  2 天前  
待字闺中  ·  给MCP祛魅 ·  5 天前  
OSC开源社区  ·  MCP这么火,来一波简单实操记录 ·  4 天前  
51好读  ›  专栏  ›  OSC开源社区

你需要了解的 Node.js 模块

OSC开源社区  · 公众号  · 程序员  · 2017-04-02 08:27

正文

#点击图片报名参加武汉、长沙源创会#


OSC 协作翻译

原文:

Requiring modules in Node.js: Everything you need to know

链接:

https://medium.freecodecamp.com/requiring-modules-in-node-js-everything-you-need-to-know-e7fbd119be8#.vb4g1p532

翻译: Viyi, 边城, Tocy, BigEcho, snake_007, Tony, JKol

请输入标题     bcdef

Node 使用两个核心模块来管理模块依赖:

require 模块 ,在全局范围可用——无需 require('require')。


module 模块 ,在全局范围可用——无需 require('module')。

你可以将 require 模块视为命令,将 module 模块视为所有必需模块的组织者。

请输入标题     abcdefg


在 Node 中获取一个模块并不复杂。

const config = require('/path/to/file');


由 require 模块导出的主要对象是一个函数(如上例所用)。 当 Node 使用本地文件路径作为函数的唯一参数调用该 require() 函数时,Node 将执行以下步骤:

解析: 找到文件的绝对路径。

加载: 确定文件内容的类型.

封装: 给文件其私有作用域。 这使得 require 和 module 对象两者都可以下载我们需要的每个文件。

评估: 这是 VM 对加载的代码最后需要做的。

缓存: 当我们再次需要这个文件时,不再重复所有的步骤。


在本文中,我将尝试用示例解释这些不同的阶段,以及它们是如何影响我们在 Node 中编写模块的方式的。



先在终端创建一个目录来保存所有示例:

mkdir ~/learn-node && cd ~/learn-node

本文之后所有命令都在 ~/learn-node 下运行。


解析本地路径

我现在向你介绍 module 对象。你可以在一个的 REPL(译者注:Read-Eval-Print-Loop,就是一般控制台干的事情)会话中很容易地看到它:

~/learn-node $ node

> module

Module {

id: ' ',

exports: {},

parent: undefined,

filename: null,

loaded: false,

children: [],

paths: [ ... ] }


每个模块对象都有一个 id 属性作为标识。这个 id 通常是文件的完整路径,不过在 REPL 会话中,它只是


Node 模块与文件系统有着一对一的关系。请求模块就是把文件内容加载到内存中。


不过,因为 Node 中有很多方法用于请求文件(比如,使用相对路径,或预定义的路径),在我们把文件内容加载到内存之前,我们需要找到文件的绝对位置。


现在请求 'find-me' 模块,但不指定路径:

require('find-me');


Node 会按顺序在 module.paths 指定的路径中去寻找 find-me.js。

~/learn-node $ node

> module.paths

[ '/Users/samer/learn-node/repl/node_modules',

'/Users/samer/learn-node/node_modules',

'/Users/samer/node_modules',

'/Users/node_modules',

'/node_modules',

'/Users/samer/.node_modules',

'/Users/samer/.node_libraries',

'/usr/local/Cellar/node/7.7.1/lib/node' ]


路径列表基本上会是从当前目录到根目录下的每一个 node_modules 目录。它也会包含一些不推荐使用的遗留目录。


如果 Node 在这些目录下仍然找不到 find-me.js,它会抛出 “cannot find module error.(不能找到模块)” 这个错误消息。

~/learn-node $ node
> require('find-me')
Error: Cannot find module 'find-me'
at Function.Module._resolveFilename (module.js:470:15)
at Function.Module._load (module.js:418:25)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at repl:1:1    at ContextifyScript.Script.runInThisContext (vm.js:23:33)
at REPLServer.defaultEval (repl.js:336:29)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.onLine (repl.js:533:10)


现在创建一个局部的 node_modules 目录,放入一个 find-me.js,require('find-me') 就能找到它。

~/learn-node $ mkdir node_modules

~/learn-node $ echo "console.log('I am not lost');" > node_modules/find-me.js

~/learn-node $ node

> require('find-me');

I am not lost

{}

>


如果别的路径下存在另一个 find-me.js 文件,例如在 home 目录下存在 node_modules 目录,其中有一个不同的 find-me.js:

$ mkdir ~/node_modules

$ echo "console.log('I am the root of all problems');" > ~/node_modules/find-me.js


现在 learn-node 目录也包含 node_modules/find-me.js —— 在这个目录下 require('find-me'),那么 home 目录下的 find-me.js 根本不会被加载:

~/learn-node $ node

> require('find-me')

I am not lost

{}

>


如果删除了~/learn-node 目录下的的 node_modules 目录,再次尝试请求 find-me.js,就会使用 home 目录下 node_modules 目录中的 find-me.js 了:

~/learn-node $ rm -r node_modules/

~/learn-node $ node

> require('find-me')

I am the root of all problems

{}

>


请求一个目录

模块不一定是文件。我们也可以在 node_modules 目录下创建一个 find-me 目录,并在其中放一个 index.js 文件。同样的 require('find-me') 会使用这个目录下的 index.js 文件:

~/learn-node $ mkdir -p node_modules/find-me

~/learn-node $ echo "console.log('Found again.');" > node_modules/find-me/index.js

~/learn-node $ node

> require('find-me');

Found again.

{}

>


注意如果存在局部模块,home 下 node_modules 路径中的相应模块仍然会被忽略。


在请求一个目录的时候,默认会使用 index.js,不过我们可以通过 package.json 中的 main 选项来改变起始文件。比如,希望 require('find-me') 在 find-me 目录下去使用另一个文件,只需要在那个目录下添加  package.json 文件来完成这个事情:

~/learn-node $ echo "console.log('I rule');" > node_modules/find-me/start.js

~/learn-node $ echo '{ "name": "find-me-folder", "main": "start.js" }' > node_modules/find-me/package.json

~/learn-node $ node

> require('find-me');

I rule

{}

>


require.resolve


如果你只是想找到模块,并不想执行它,你可以使用 require.resolve 函数。除了不加载文件,它的行为与主函数 require 完全相同。如果文件不存在它会抛出错误,如果找到了指定的文件,它会返回完整路径。

> require.resolve('find-me');

'/Users/samer/learn-node/node_modules/find-me/start.js'

> require.resolve('not-there');

Error: Cannot find module 'not-there'

at Function.Module._resolveFilename (module.js:470:15)

at Function.resolve (internal/module.js:27:19)

at repl:1:9

at ContextifyScript.Script.runInThisContext (vm.js:23:33)

at REPLServer.defaultEval (repl.js:336:29)

at bound (domain.js:280:14)

at REPLServer.runBound [as eval] (domain.js:293:12)

at REPLServer.onLine (repl.js:533:10)

at emitOne (events.js:101:20)

at REPLServer.emit (events.js:191:7)

>


这很有用,比如,检查一个可选的包是否安装并在它已安装的情况下使用它。


相对路径和绝对路径

除了在 node_modules 目录中查找模块之外,我们也可以把模块放置于任何位置,然后通过相对路径(./ 和 ../)请求,也可以通过以 / 开始的绝对路径请求。


比如,如果 find-me.js 是放在 lib 目录而不是 node_modules 目录下,可以这样请求:

require('./lib/find-me');


文件中的父子关系

创建 lib/util.js 文件并添加一行 console.log 代码来识别它。console.log 会输出模块自身的 module 对象:

~/learn-node $ mkdir lib

~/learn-node $ echo "console.log('In util', module);" > lib/util.js


在 index.js 文件中干同样的事情,稍后我们会通过 node 命令执行这个文件。让 index.js 文件请求 lib/util.js:

~/learn-node $ echo "console.log('In index', module); require('./lib/util');" > index.js


现在用 node 执行 index.js:

~/learn-node $ node index.js

In index Module {

id: '.',

exports: {},

parent: null,

filename: '/Users/samer/learn-node/index.js',

loaded: false,

children: [],

paths: [ ... ] }

In util Module {

id: '/Users/samer/learn-node/lib/util.js',

exports: {},

parent:

Module {

id: '.',

exports: {},

parent: null,

filename: '/Users/samer/learn-node/index.js',

loaded: false,

children: [ [Circular] ],

paths: [...] },

filename: '/Users/samer/learn-node/lib/util.js',

loaded: false,

children: [],

paths: [...] }


注意到现在的列表中主模块 index (id: '.') 是 lib/util 模块的父模块。不过 lib/util 模块并未作为 index 的子模块列出来。不过那里有个 [Circular] 值因为那里存在循环引用。如果 Node 打印 lib/util 模块对象,它就会陷入一个无限循环。因此这里用 [Circular] 代替了 lib/util 引用。


现在更重要的问题是,如果 lib/util 模块又请求了 index 模块,会发生什么事情?这就是我们需要了解的循环依赖,Node 允许这种情况存在。


在理解它之前,我们先来搞明白 module 对象中的另外一些概念。


exports、module.exports 以及同步加载模块

exports 是每个模块都有的一个特殊对象。如果你观察仔细,会发现上面示例中每次打印的模块对象中都存在一个 exports 属性,到目前为止它只是个空对象。我们可以给这个特殊的 exports 对象任意添加属性。例如,我们为 index.js 和 lib/util.js 导出 id 属性:

// Add the following line at the top of lib/util.js

exports.id = 'lib/util';

// Add the following line at the top of index.js

exports.id = 'index';


现在执行 index.js,我们会看到这些属性受到 module 对象管理:

~/learn-node $ node index.js

In index Module {

id: '.',

exports: { id: 'index' },

loaded: false,

... }

In util Module {

id: '/Users/samer/learn-node/lib/util.js',

exports: { id: 'lib/util' },

parent:

Module {

id: '.',

exports: { id: 'index' },

loaded: false,

... },

loaded: false,

... }


上面的输出中我去掉了一些属性,这样看起来比较简洁,不过请注意 exports 对象已经包含了我们在每个模块中定义的属性。你可以在 exports 对象中任意添加属性,也可以直接把 exports 整个替换成另一个对象。比如,可以把 exports 对象变成一个函数,我们会这样做:

// Add the following line in index.js before the console.log

module.exports = function() {};


现在运行 index.js,你会看到 exports 对象是一个函数:

~/learn-node $ node index.js
In index Module {
id: '.',
exports: [Function],
loaded: false,
... }


注意,我没有通过 exports = function() {} 来将 exports 对象改变为函数。这样做是不行的,因为模块中的 exports 变量只是 module.exports 的引用,它用于管理导出属性。如果我们重新给 exports 变量赋值,就会丢失对 module.exports 的引用,实际会产生一个新的变量,而不是改变了 module.exports。


每个模块中的 module.exports 对象就是通过 require 函数请求那个模块返回的。比如,把 index.js 中的 require('./lib/util') 改为:

const UTIL = require('./lib/util');

console.log('UTIL:', UTIL);


这段代码会输出 lib/util 导出到 UTIL 常量中的属性。现在运行 index.js,输出如下:

UTIL: { id: 'lib/util' }


再来谈谈每个模块的 loaded 属性。到目前为止,每次我们打印一个模块对象的时候,都会看到这个对象的 loaded 属性值为 false。


module 模块使用 loaded 属性来跟踪哪些模块是加载过的(true值),以及哪些模块还在加载中(false 值)。比如我们可以通过调用 setImmediate 来打印 modules 对象,在下一事件循环中看看完成加载的 index.js 模块:

// In index.js

setImmediate(() => {

console.log('The index.js module object is now loaded!', module)

});


输出是这样的:

The index.js module object is now loaded! Module {

id: '.',

exports: [Function],

parent: null,

filename: '/Users/samer/learn-node/index.js',







请到「今天看啥」查看全文