Node.js(三)模块

在Node.js模块系统中,每个文件都被视为一个独立的模块

导出和引用模块


比如,新建一个people.js作为一个模块:
编辑代码:

exports.sayMyName = (name) => {return '我的名字是'+name};

这个js文件就是一个模块,这个模块用exports.方法名导出了sayMyname这个函数。
在执行代码之前,Node.js会先执行一个步骤:
Node.js把模块(每一个文件)封装在一个函数中(模块封装器)

模块封装器

在执行模块(文件)代码之前,Node.js会使用一个函数封装器来封装代码

(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});

通过在每个模块中添加了这样一层的包裹,Node.js实现了两>点:

  1. 使模块内的本地变量私有化,作用在模块范围内,而不是>暴露为全局对象。
  2. 提供了一些模块特定的变量:
    • exports:对于module.exports的更简短的引用形式
    • require:是一个函数,用于引入模块、 JSON、或本地文件。
    • module:对当前模块的引用, module.exports 用于指定一个模块所导出的内容,即可以通过 require() 访问的内容
    • __filename:当前模块已解析的绝对路径
    • __dirname:当前模块的目录名

exports是一个module.exports的更简短的引用形式,是一个对象,通过在此对象上指定额外的属性,可以将函数和对象添加到模块的根部。
在另一个模块(文件)中引用people模块,使用require语句来引用模块:

const people = require('./people.js')
console.log(`这位先生开始介绍自己,他说道:${people.sayMyName("Jack")}`)

注意:使用console时,使用${变量}来包裹变量,有变量时,使用``来作为输出语句的引号。
运行如下:


运行结果

exports 和 module.exports


exportsmodule.exports有什么不一样呢?通过上述介绍,可以知道exports 其实是module.exports 的引用,那么什么时候使用exports,什么时候使用module.exports 呢?
exports变量在模块内的作用域中是可用的,且在模块执行之前赋值给module.exports
故此,module.exports.f = ... 可以快捷地写成exports.f = ...,但是当给它赋值之后,它将不再绑定到module.exports,即,当你要使用赋值语句时,就不可再使用exports = ...,而要使用module.exports = ...

module.exports.hello = true;//从模块的引用中导出
exports.hello = true;//从模块的引用中导出
module.exports = exports = function Contructor(){};//导出模块
//module.exports被赋值时,也会重新赋值exports
exports = { hello: false };  // 不导出,仅在模块中可用。
//此时,exports 不与 module.exports绑定了。

通过require()的实现来说明他们之间的关系:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // 模块代码在这。在这个例子中,定义了一个函数。
    function someFunc() {}
    exports = someFunc;
    // exports被赋值了,此时,exports 不再是一个 module.exports 的快捷方式,
    // 且这个模块依然导出一个空的默认对象。
    module.exports = someFunc;
    // module.exports被赋值了,此时,该模块导出 someFunc,而不是默认对象。
  })(module, module.exports);
  return module.exports;
}

综上,可以知道使用exportsmodule.exports的场景:

  1. 当希望模块导出一个空的默认对象,以及暴露一些外部可以通过这个对象访问的api或者变量时,使用exports
//模块中导出
exports.func = ...
exports.a = ...
//外界访问
const moduleName = require('module.js');//获取默认对象
moduleName.func();//访问对象中挂载暴露的函数
console.log(moduleName.a);
  1. 当希望模块导出一个变量或函数时,外部直接访问这个变量和函数,使用module.exports
//模块中导出
module.exports = function(){}
//外界访问
const moduleName = require('module.js');//获取默认对象
moduleName()

想要传参数到模块中动态配置的方法:
使用module.exports导出一个工厂函数,这个函数可以传入参数,在这个函数中,模块内就可以使用参数进行逻辑操作,从而达到动态配置的效果,如
设置一个模块,可以计算汇率,设置汇率会动态配置的参数:

let rate;
function rmbToDollar(rmb){
    return rmb/rate;
}

module.exports = function(r){
    rate = r
    return {rmbToDollar} 
}

在外部动态配置汇率,结构出我们要使用的函数:

const {rmbToDollar} = require('./currency')(6)
console.log(rmbToDollar(10))

访问主模块

主模块,即当前模块是不是直接运行的模块(文件)。
当Node.js直接运行一个文件时,require.main会被设置为其module,如果要判断一个文件是不是被直接运行,通过以下语句判断:

require.main === module

如果是通过node xxx.js运行,为true;
如果是通过require运行,为false。
要获取当前程序的入口点,可以通过以下代码获取:

require.main._filename

核心模块

Node.js有一些模块是核心模块,定义在lib/目录下,require()会优先加载核心模块,即使有模块与核心模块重名,也会先加载内置的模块。

require()查找目录

require中传入的参数有以下几种情况

  • 以 '/' 为前缀的模块是文件的绝对路径。 例如, require('/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。
  • 以 './' 为前缀的模块是相对于调用 require() 的文件的。 也就是说, circle.js 必须和 foo.js 在同一目录下以便于 require('./circle') 找到它。
  • 当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
    *如果给定的路径不存在,则 require() 会抛出一个 code 属性为 'MODULE_NOT_FOUND'Error
    在一个项目中设定入口文件时,通常是在根目录下创建一个package.json文件,并定一个main模块,如:
{ "name" : "some-library",
  "main" : "./lib/some-library.js" }

如果这是在 ./some-library 目录中,则 require('./some-library') 会试图加载 ./some-library/lib/some-library.js。
如果目录中没有package.json文件,则Node.js会试图加载目录中的index.js或者index.node。

详解module对象

每个模块中都有一个module对象,表示当前模块的对象的引用。里面有一些关于本模块(文件)的信息。module对象是每个模块本地的。
在文件中输出module:

console.log(module)

可以得到如下结果:


module构成

组成解析:

  • id:<string>模块的标识符,通常是完全解析后的文件名
  • exports:<Object>对象,可用于导出变量或函数等
  • parent:* <Object>模块对象,最先应用该模块的模块
  • filename:<string>完全解析后的文件名
  • loaded:<boolean>模块是否加载完成,或者正在加载中
  • children:<Array>被该模块引用的模块对象数组
  • paths:<string[]>模块的搜索路径,是一个数组
  • require(id):返回已解析的模块的module.exports

npm


npm是Node.js的包管理器,通常在安装Node.js的时候一起安装了。
查看npm的版本

npm -v
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容