前端知识库(lizh)
  • README
  • Bugs
    • 前端调试随笔
    • 浏览器常见问题概览
    • 浏览器兼容问题概览
    • HTML常见问题概览
    • CSS常见问题概览
    • JS常见问题概览
    • 移动端兼容性问题概览
    • 微信小程序开发
    • NodeJs常见问题概览
    • Mac常见问题概览
    • 微信开发遇坑指南
    • Npm包常见问题概览
    • 其他问题汇总
  • Css探索系列
    • CSS基础知识
    • CSS常见问答
    • CSS常见问答02
    • CSS应用示例
    • CSS应用示例02
    • 由Z Index引发的层叠上下文思考
    • 由浮动塌陷引发的块级格式上下文思考
    • CSS探索系列 Flex布局
    • CSS探索系列 Margin
    • CSS探索系列 Auto关键字
    • CSS探索系列 Gradient
    • CSS探索系列 Line Height
    • CSS探索系列 元素居中
    • CSS探索系列 动画
    • 为什么使用PostCSS处理CSS?
    • 重新认识伪类与伪元素
    • 自定义表单伪元素样式
    • 如何理解Css中的Display属性
    • 视口和软键盘对视口的影响
    • 关于Css
  • Frontend
    • 00 关于Web前端
    • 01 前端知识概览
    • 02 常用前端库概览
    • 基础 00 前端常见问题01
    • 基础 01 浏览器缓存
    • 基础 02 浏览器工作原理
    • 基础 03 谈谈前端跨源问题及解决方法
    • 进阶 01 Web性能优化
    • 进阶 02 搜索引擎优化(SEO)
    • 进阶 03 前端模块化编程
    • 进阶 04 规范代码:Linter、Prettier、EditorConfig
    • 进阶 11 前端自动化测试
    • 高级 01 前端安全
    • Vue2.X原理篇
    • Vue3初步了解及迁移指南
    • 重读Vue教程
    • React17.X原理篇
    • 你必须知道的React问题
    • 重读React教程
    • 聊一聊Cookie的一些问题
    • 如何理解HTTP响应的状态码
    • HTTP的历史演变及概述
    • Webpack4.X原理篇
    • Webpack基础入门篇
    • Webpack构建优化篇
    • TypeScript使用指南
    • 代码规范
      • 前端规范
      • HTML
      • CSS
      • JS
  • Html探索系列
    • HTML基础知识
    • HTML基础知识02
    • HTML常见问答
    • HTML经典实践用例
    • HTML元素的宽高及位置详解
    • Video元素的使用和常见问题总结
    • Html探索系列 Meta标签
    • DOCTYPE:文档类型与浏览器模式
    • DHTML(动态网页)简介
    • HTML标签详解
    • HTML布局的几种方式
    • HTML全局属性
    • 关于Html
  • Js探索系列
    • 基础知识
    • 常见问答
    • 应用示例
    • 趣味示例
    • 基础篇 05 AJAX
    • 基础篇 06 Window对象
    • 基础篇 07 Error、JSON、Math、Console对象
    • 基础篇 08 History、URL、Screen、Navigator、Location对象
    • 基础篇 09 文档对象模型(DOM)
    • 基础篇 10 Document对象
    • 基础篇 11 Element对象
    • 基础篇 12 Event对象
    • 基础篇 13 键盘、鼠标、触摸事件
    • 基础篇 15 CSS对象模型(CSSOM)
    • 进阶篇 01 Prototype对象和继承
    • 进阶篇 02 Promise对象
    • 进阶篇 07 迭代器(Iterator)
    • 进阶篇 08 Generator和Async函数
    • 进阶篇 09 JavaScript异步编程
    • Date对象和日期时间字符串格式
    • Canvas基础入门篇
    • Canvas进阶篇
    • SVG基础入门篇
    • 四种判断数据类型方法的优缺点
    • 深入理解JavaScript的浅拷贝和深拷贝
    • 谈谈JavaScript的作用域和上下文
    • 复制内容到剪贴板
    • 关于Javascript
  • NodeJs
    • 关于Node.Js
    • Node.Js:三种调试方法
    • Npm包管理器简介及一些机制
    • NPM:Package.Json详解(中文)
    • NPM:从零开始,开发一个软件包
    • NPM:常用命令
    • Node.Js:Fs(文件系统)
    • Node.Js:Global(全局变量)
    • Node.Js:HTTP
    • Node.Js:Module(模块)
    • Node.Js:Path(路径)
    • Node.Js:Readline(逐行读取)
  • Research
    • 极细边框(1px边框)实现方式
    • 如何监控前端异常?
    • H5页面跳转和刷新
    • Web主题切换和个性化定制方法总结
    • Vue SSR(服务端渲染)的简单实现
    • 基于Create React App打造代码规范化的React+Ts项目
    • H5可视化编辑
    • Web常用功能
    • Javascript加密混淆
    • Vue如何导入TypeScript
    • 移动端PDF预览
    • 纯CSS绘制箭头
    • 网站性能测量和优化方法
  • Tech
    • GOOGLE浏览器的搜索技巧
    • Curl的用法指南
    • Sublime3插件篇
    • Charles安装及使用
    • Nginx基础使用
    • 排序算法(Javascript)
    • 代码整洁之道(摘录笔记)
    • Java的24种设计模式与7大原则
    • 观察者和发布订阅模式
  • Tools
    • Git
      • Git基础教程
      • Git常见问题
    • Gitbook
      • Gitbook入门篇
      • Gitbook插件篇
      • Gitbook进阶篇
由 GitBook 提供支持
在本页
  • 访问主模块
  • 缓存
  • 循环
  • 模块加载
  • 文件模块
  • 目录作为模块
  • 从 node_modules 目录加载
  • 从全局目录加载
  • 模块作用域
  • __dirname
  • __filename
  • exports
  • module
  • require()
  • module 对象
  • module.children
  • module.exports
  • module.filename
  • module.id
  • module.loaded
  • module.parent
  • module.paths
  • module.require(id)
  • module 模块
  • module.builtinModules
  • module.createRequire(filename)
  • module.createRequireFromPath(filename)(弃用)
  • 参考资料

这有帮助吗?

  1. NodeJs

Node.Js:Module(模块)

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

Node.js 有些模块会被编译成二进制,称为核心模块,定义在 Node.js 源代码的 lib/ 目录下。require() 总是会优先加载核心模块。 例如,require('http') 始终返回内置的 HTTP 模块,即使有同名文件。

访问主模块

module 提供了一个 filename 属性(通常等同于 __filename)表示当前文件路径。当 Node.js 直接运行一个文件时,require.main 会被设为它的 module。

这意味着可以通过 require.main === module 来判断一个文件是否被直接运行:

console.log(require.main.filename)
console.log(require.main === module)

缓存

模块在第一次加载后会被缓存。 这也意味着(类似其他缓存机制),如果每次调用 require('foo') 都解析到同一文件,则返回相同的对象。

多次调用 require(foo) 不会导致模块的代码被执行多次。 这是一个重要的特性。 借助它, 可以返回“部分完成”的对象,从而允许加载依赖的依赖, 即使它们会导致循环依赖。

如果想要多次执行一个模块,可以导出一个函数,然后调用该函数。

模块缓存的注意事项:

  • 模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如,从 node_modules 目录加载),这样就不能保证 require('foo') 总能返回完全相同的对象。

  • 在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。

循环

重要问题:当我们在 A.js 中引用 B.js,在 B.js 中引用 A.js 时会发生什么?

官网上点出了这种模块循环的情况,并且解释清楚了原因:

When main.js loads a.js, then a.js in turn loads b.js. At that point, b.js tries to load a.js. In order to prevent an infinite loop, an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.

简单说就是,为了防止模块载入的死循环,Node.js 在模块第一次载入后会把它的结果进行缓存,下一次再对它进行载入的时候会直接从缓存中取出结果。所以在这种循环依赖情形下,不会有死循环。但需要注意的是,此时缓存可能是一个未完成的模块。

// a.js
console.log('a');
exports.str = 'a1';
const b = require('./b.js');
console.log('a:', b.str);
exports.str = 'a2';
console.log('a:结束');
// b.js
console.log('b');
exports.str = 'b1';
const a = require('./a.js');
console.log('b:', a.str);
exports.str = 'b2';
console.log('b:结束');
// main.js
console.log('main');
const a = require('./a.js');
const b = require('./b.js');
console.log('main:', a.str, b.str);

当 main.js 加载 a.js 时,a.js 又加载 b.js。 此时,b.js 去加载 a.js,会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。

该程序的输出会是:

main
a
b
b: a1
b:结束
a: b2
a:结束
main: a2 b2

模块加载

文件模块

如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、.json 或 .node 拓展名再加载。

  • .js 文件会被解析为 JavaScript 文本文件;.json 文件会被解析为 JSON 文本文件;.node 文件会被解析为通过 dlopen 加载的编译后的插件模块。

  • 以 / 为前缀的模块是文件的绝对路径。

  • 以 ./ 为前缀的模块是文件的相对路径,也就是说,加载同一目录下的文件。

  • 当没有以 /、./ 或 ../ 开头来表示文件时,则模块必须是一个核心模块或加载自 node_modules 目录。

如果给定的路径不存在,则 require() 会抛出一个 code 属性为 'MODULE_NOT_FOUND' 的 Error。

目录作为模块

可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。

如果,根目录下有 package.json 文件且指定了 main 字段的值:

// package.json
{
  "name" : "some-library",
  "main" : "./lib/some-library.js"
}

则 require('./some-library') 会尝试加载 ./some-library/lib/some-library.js,这就是 Node.js 处理 package.json 文件的方式。

注意: 如果 main 字段指定的文件不存在,则无法解析,Node.js 会将模块视为不存在,并抛出默认错误:Error: Cannot find module 'some-library'。

如果目录里没有 package.json 文件,则 Node.js 就会试图加载目录下的 index.js 或 index.node 文件。

从 node_modules 目录加载

如果传递给 require() 的模块标识符不是一个核心模块,也没有以 /、./ 或 ../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的 /node_modules 目录里加载模块。 Node.js 不会附加 node_modules 到一个已经以 node_modules 结尾的路径上。

如果还是没有找到,则移动到再上一层父目录,直到文件系统的根目录。

比如,在 /home/ry/projects/foo.js 文件里调用了 require('bar.js'),则 Node.js 会按以下顺序查找:

/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js

从全局目录加载

如果 NODE_PATH 环境变量被设为一个以冒号分割的绝对路径列表,则当在其他地方找不到模块时 Node.js 会搜索这些路径。

注意: 在 Windows 系统中,NODE_PATH 是以分号间隔的。

在当前的模块解析算法运行之前,NODE_PATH 最初是创建来支持从不同路径加载模块的。

虽然 NODE_PATH 仍然被支持,但现在不太需要,因为 Node.js 生态系统已制定了一套存放依赖模块的约定。 有时当人们没意识到 NODE_PATH 必须被设置时,依赖 NODE_PATH 的部署会出现意料之外的行为。 有时一个模块的依赖会改变,导致在搜索 NODE_PATH 时加载了不同的版本(甚至不同的模块)。

此外,Node.js 还会搜索以下位置:

$HOME/.node_modules
$HOME/.node_libraries
$PREFIX/lib/node

其中 $HOME 是用户的主目录,$PREFIX 是 Node.js 里配置的 node_prefix。

这些主要是历史原因。

注意: 强烈建议将所有的依赖放在本地的 node_modules 目录。 这样将会更快地加载,且更可靠。

模块作用域

__dirname

当前模块的文件夹名称。等同于 __filename 的 path.dirname() 的值。

__filename

当前模块的文件名,返回当前模块文件的绝对路径(符号链接会被解析)。

console.log(__filename) // '/Users/xxx/Documents/xxx/demo-lizh/node/nodejs/global.js'

注意: 对于主程序,这不一定与命令行中使用的文件名相同。

也就是说,运行 node main.js,main.js 导入 a.js 文件,a.js 文件中 console.log(__filename) 语句打印的是 a.js 文件的绝对路径,而不是 main.js 文件的绝对路径。

exports

exports 变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予 module.exports 的值。

它是一个对于 module.exports 的更简短的引用形式,以便 module.exports.f = ... 可以被更简洁地写成 exports.f = ...。

注意: 就像任何变量,如果一个新的值被赋值给 exports,它就不再绑定到 module.exports。

module.exports.hello = true; // 从对模块的引用中导出
exports = { hello: false };  // 不导出,只在模块内有效

当 module.exports 属性被一个新的对象完全替代时,也会重新赋值 exports,例如:

module.exports = exports = function Constructor() {
  // ... 及其他
};

module

对当前模块的引用。

require()

引入模块。

require.cache

被引入的模块将被缓存在这个对象中。从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块。

注意: 不能删除 native addons(原生插件),因为它们的重载将会导致错误。

require.resolve(request[, options])

使用内部的 require() 机制查询模块的位置,此操作只返回解析后的文件名,不会加载该模块。

参数说明:

  • request:字符串,需要解析的模块路径。

  • options:Object

    • paths:数组,解析模块的起点路径。此参数存在时,将使用这些路径而非默认解析路径。 注意:此数组中的每一个路径都被用作模块解析算法的起点,意味着 node_modules 层级将从这里开始查询。

require.resolve.paths(request)

返回一个数组,其值是包含解析 request 过程中被查询的路径。

如果 request 字符串指向核心模块(例如,http、fs),则返回 null。

参数说明:

  • request:字符串,需要解析的模块路径。

module 对象

在每个模块中,module 的自由变量是一个指向表示当前模块的对象的引用。

module 实际上不是全局的,而是每个模块本地的。

为了方便,module.exports 也可以通过全局模块的 exports 对象访问。

module.children

被该模块引用的模块对象。

module.exports

module.exports 对象是由模块系统创建的。

如果希望模块能够被其他模块引用,则需要将期望导出的对象赋值给 module.exports。

// c.js
module.exports = {
  a: 1,
  b: 2
}
// main.js
const ObjC = require('./c.js')
console.log(ObjC) // {a: 1, b: 2}

注意: 将期望的对象赋值给 exports 会简单地重新绑定本地 exports 变量,这可能不是期望的。

// c.js,正常。main.js 打印:{a: 1, b: 2}
exports.a = 1
exports.b = 2
// c.js,异常。main.js 打印:{}
exports = {
  a: 1,
  b: 2
}

注意: 对 module.exports 的赋值必须立即完成,不能在任何回调中完成。

module.filename

模块的完全解析后的文件名。

module.id

模块的标识符。 通常是完全解析后的文件名。

module.loaded

模块是否已经加载完成,或正在加载中。

module.parent

最先引用该模块的模块。

module.paths

模块的搜索路径。

module.require(id)

提供了一种类似 require() 从原始模块被调用的加载模块的方式。

注意: 为了做到这个,需要获得一个 module 对象的引用。 因为 require() 会返回 module.exports,且 module 只在一个特定的模块代码中有效,所以为了使用它,必须明确地导出。

module 模块

module 对象和 module 模块是不同的:module 对象是 Node.js 中的全局变量;而 module 模块,是通过 import 'module' 或 require('module') 访问。

const Module = require('module');
for (key in Module) {
  console.log(key)
}
_cache
_pathCache
_extensions
_debug
_findPath
_nodeModulePaths
_resolveLookupPaths
_load
_resolveFilename
_initPaths
_preloadModules

builtinModules
globalPaths
createRequireFromPath
createRequire
syncBuiltinESMExports
Module
runMain
findSourceMap
SourceMap

module.builtinModules

Node.js 提供的所有模块的名称列表。 可用于验证某个模块是否由第三方模块维护。

const builtinModules = require('module').builtinModules;
console.log(builtinModules)

module.createRequire(filename)

返回一个 require 函数。

参数说明:

  • filename:字符串,用于构造 require 函数的文件名。必须是一个文件 URL 对象、文件 URL 字符串、或绝对路径字符串。

// main.mjs。注意:import 只能在 ES module 中使用。
import { createRequire } from 'module';

// import.meta.url 是当前文件路径。
// file:///Users/xxx/Documents/xxx/demo-lizh/node/nodejs/main.mjs
const myRequire = createRequire(import.meta.url);
const objC = myRequire('./c.js')
console.log(objC)
// main.js
const { createRequire } = require('module');
const myRequire = createRequire(__filename);
const objC = myRequire('./c.js');
console.log(objC)

module.createRequireFromPath(filename)(弃用)

返回一个 require 函数。

注意: 该方法已弃用,改用 createRequire() 。

参数说明:

  • filename:字符串,用于构造 require 函数的文件名。必须是一个文件 URL 对象、文件 URL 字符串、或绝对路径字符串。

const { createRequireFromPath } = require('module');
const myRequire = createRequireFromPath('/Users/xxx/Documents/xxx/demo-lizh/node/nodejs/');
const objC = myRequire('./c.js');
console.log(objC)

参考资料

上一页Node.Js:HTTP下一页Node.Js:Path(路径)

最后更新于1年前

这有帮助吗?

Node.js 中文文档 - module(模块)
API Reference Document - module(模块)