JS模块:CJM
跳到导航
跳到搜索
关于[1]
规范的主要内容:模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。 1、所有代码都运行在模块作用域,不会污染全局作用域 —— 一个文件就是一个模块 2、模块初次加载会被缓存,再次使用将从缓存中读取 —— 要让模块再次运行,必须清除缓存
Node.js 将对模块代码封装为如下形式:
(function(exports, require, module, __filename, __dirname) { // 模块代码实际存在于此处 });
- 将变量限制在在模块内。
- 提供了使用模块的属性、方法:
- __dirname:当前模块的绝对目录名;
- __filename:当前模块的绝对文件名;
- exports:对 module.exports 的引用;
- module:(对象)对当前模块的引用;
- require:(方法)用于导入其他模块(核心模块、文件模块、第三方模块);
exports
exports 变量:是对 module.exports 的引用 —— ❗ 不能对 exports 进行赋值操作,否则只是让其不再对 module.exports 进行引用,而变成一个局部变量。
正确用法:
exports.area = function(x) {console.log(x)};
错误用法:
exports = function(x) {console.log(x)};
module
module 变量:是一个代表当前模块的对象
它提供了以下属性:
- module.id:模块的识别符,通常是模块的完全解析文件名。
- module.filename:模块的完全解析文件名(带有绝对路径的文件名)。
- module.parent:(对象)表示调用该模块的模块。
- module.children:(数组)表示该模块用到的其他模块。
- module.isPreloading:(布尔值)表示模块是否在 Node.js 预加载阶段运行。
- module.loaded:(布尔值)表示模块是否已经完成加载。
- module.exports:表示模块对外输出的值。
module.exports 属性表示当前模块对外输出的接口。加载某个模块,其实是加载该模块的 module.exports 属性。 ❗ 不建议同时使用 exports 与 module.exports:可能导致对外输出失效。
示例:
exports.hello = function() { return 'hello'; }; module.exports = 'Hello world';
- 如上,exports 的输出会因 module.exports 重新赋值而失效。
require
require 变量:通常就是模块的 require() 方法
它提供了以下属性、方法:
- require(id):用于导入模块、JSON 和本地文件。
- id 即 module.id(模块识别符), 可以使用相对路径(将根据 __dirname 或当前工作目录进行解析);
- require.cache:(对象)缓存对象,模块在需要时缓存在此对象中。
- require.main:(对象)代表 Node.js 进程启动时加载的入口脚本。
- 如果程序的入口点不是 CommonJS 模块,则为 undefined;
- require.resolve(request[, options]):查找模块位置,但不加载模块,只返回解析的文件名。
- request:要解析的模块路径;
- 如果找不到模块,则会抛出 MODULE_NOT_FOUND 错误;
- require.resolve.paths(request):如果 request 字符串引用核心模块(如 http 或 fs),则返回包含在解析 request 或 null 期间搜索的路径的数组。
- request:要解析的模块路径;
Module
Module 对象:Node 内部的构建函数,提供了当与 Module 的实例交互时提供通用的实用方法 所有模块(module)都是 Module 的实例。
它提供了以下属性、方法:
- module.builtinModules:Node.js 提供的所有模块的名称列表。
- 要访问它,需要 Node 内部的 Module 模块:
import { builtinModules as builtin } from 'node:module';
- 可用于:验证模块是否由第三方维护;
- 要访问它,需要 Node 内部的 Module 模块:
- module.createRequire(filename):用于构造 require 函数的文件名。
- filename:(string / URL)用于构造 require 函数的文件名(必须是文件网址对象、文件网址字符串、或绝对路径字符串);
- module.isBuiltin(moduleName):用于判断一个模块是否为“内置模块”。
- moduleName:模块名称
- module.syncBuiltinESMExports():用于更新内置的 ES 模块的所有实时绑定,以匹配 CommonJS 导出的属性。❓
加载过程
要加载的内容可能包括:内置模块、文件模块、第三方模块。
内置模块:即“原生模块”,在 Node.js 源代码中定义,位于“lib/”文件夹中
- 解析
module.id
:得到filename
为原生模块标识符(如“fs”); - 判断
Module_cache[filename]
是否命中缓存:- 缓存命中:则从缓存中返回该模块的
module.exports
—— 【结束加载】 - 缓存未命中:加载该原生模块,然后返回其
module.exports
并缓存 —— 【结束加载】
- 使用
node:
前缀来识别核心模块,可以绕过 require 缓存(即使有该名称的 require.cache 条目);
- 缓存命中:则从缓存中返回该模块的
文件模块:“相对路径”或“绝对路径”指向的本地模块
- 解析
module.id
:得到一个“绝对路径”(文件 / 目录); - 根据“绝对路径”查找“
filename
”:- 将
filename
作为“文件”进行查找:- 直接命中文件:
filename
即为该文件的全解析路径; - 没有直接命中:为
filename
添加扩展名(.js
、.json
、.node
),再尝试“查找”
- 直接命中文件:
- 将
filename
作为“目录”进行查找:- 目录下存在“package.json”文件:根据其“main”字段得到新的
filename
,再尝试“查找” - 若没有“package.json”文件(或其中没有“main”字段):查找目录下的“index”文件,将其路径作为
filename
,再尝试“查找”
- 目录下存在“package.json”文件:根据其“main”字段得到新的
- (如果以上皆未能命中)抛出错误
MODULE_NOT_FOUND
。
- 将
- 判断
Module_cache[filename]
是否命中缓存:- 缓存命中:则从缓存中返回该模块的
module.exports
—— 【结束加载】 - 缓存未命中:加载该原生模块,然后返回其
module.exports
并缓存 —— 【结束加载】
- 缓存命中:则从缓存中返回该模块的
第三方模块:第三方提供的模块,位于“node_modules”目录中
- 在“node_modules”查找“
filename
”:- “查找”过程,与【文件模块】类似
- 从“当前目录的 node_modules”到“根目录的 node_modules”依次查找
[ '/root/path/to/module/node_modules', '/root/path/to/node_modules', '/root/path/node_modules', '/root/node_modules', '/node_modules', ]
- 判断
Module_cache[filename]
是否命中缓存:- 缓存命中:则从缓存中返回该模块的
module.exports
—— 【结束加载】 - 缓存未命中:加载该原生模块,然后返回其
module.exports
并缓存 —— 【结束加载】
- 缓存命中:则从缓存中返回该模块的
循环引用
对于循环 require() 调用:模块在返回时可能尚未完成执行,但不会出现无限循环。
a.js:
console.log('a starting'); exports.done = false; const b = require('./b.js'); console.log('in a, b.done = %j', b.done); exports.done = true; console.log('a done');
b.js:
console.log('b starting'); exports.done = false; const a = require('./a.js'); console.log('in b, a.done = %j', a.done); exports.done = true; console.log('b done');
main.js:
console.log('main starting'); const a = require('./a.js'); const b = require('./b.js'); console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
输出:
$ node main.js main starting a starting b starting in b, a.done = false b done in a, b.done = true a done in main, a.done = true, b.done = true