在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实现了两>点:
- 使模块内的本地变量私有化,作用在模块范围内,而不是>暴露为全局对象。
- 提供了一些模块特定的变量:
- 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
exports
和 module.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;
}
综上,可以知道使用exports
和module.exports
的场景:
- 当希望模块导出一个空的默认对象,以及暴露一些外部可以通过这个对象访问的api或者变量时,使用
exports
//模块中导出
exports.func = ...
exports.a = ...
//外界访问
const moduleName = require('module.js');//获取默认对象
moduleName.func();//访问对象中挂载暴露的函数
console.log(moduleName.a);
- 当希望模块导出一个变量或函数时,外部直接访问这个变量和函数,使用
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)
可以得到如下结果:
组成解析:
- 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