# globals 和模块

# globals

globals 表示 NodeJs 中的全局属性和全局方法的,意思是在所有模块中都能使用。

globals中有个global,表示当前模块中的全局命名空间对象,如果将某些属性或方法放到global里的话,就能在其他模块中通过访问这些属性或方法(必须先require)。例如把var a放到浏览器的window上,那么在任何地方都能使用。而在 NodeJs 中就是放在它当前所在模块中 global 里,其他模块才能访问,否则就访问不了,具体就是下小一节的CommonJs 模块

a.js

const name = "ll";
const sayName = () => {
  console.log(`sayName: ${name}`);
};
console.log("module 1");

global.name = name;
global.sayName = sayName;
1
2
3
4
5
6
7
8

b.js

// 这里的require不能去掉,因为global仍属于模块的,这里必须让b.js与a.js建立依赖关系才能使用name和sayName
const aModule = require("./a");
console.log("name", name);
sayName();
1
2
3
4

以下属性或方法,都是表示它当前所在模块的一些信息和方法。每个模块都有这些方法,这就导致它们看起来像是全局的globals

  • __dirname:当前模块的目录名
  • __filename:当前模块的文件名。这是当前模块文件的绝对路径,符号链接已解析
  • exports:模块导出
  • module:对模块的引用
  • require():导入模块或文件

其他的globals

  • console:用于打印到 stdout 和 stderr
  • process:进程对象
  • queueMicrotask(callback):放入微任务队列
  • setImmediate(callback[, ...args]):本轮事件循环结束后调用
  • setInterval(callback, delay[, ...args]):间隔一段时间重复执行
  • setTimeout(callback, delay[, ...args]):间隔一段时间执行一次
  • clearImmediate(immediateObject):清除由setImmediate创建的定时器
  • clearInterval(timeout):清除由setInterval创建的定时器
  • clearTimeout(timeout):清除由setTimeout创建的定时器
  • TextDecoder:WHATWG 编码标准 TextDecoderAPI 的实现
  • TextEncoder:WHATWG 编码标准 TextEncoderAPI 的实现
  • URL:按照 WHATWG URL 标准实现的浏览器兼容 URL 类
  • URLSearchParams:URLSearchParams API 提供对 URL 查询的读写访问
  • WebAssembly:充当所有 W3C WebAssembly 相关功能的命名空间的对象

# 模块

# 何为模块

就是将一个项目中的 js 拆分成一个个独立的部分,以方便模块复用和维护。模块是不同于<script>的,它具有“依赖关系”、“命名空间”、“代码组织”等特点,可以解决“js 文件加载顺序”、“全局属性/方法”、“代码隔离”等问题。

# CommonJs 规范

NodeJs 采用 CommonJs 规范实现了模块系统。CommonJs 规范规定了如何定义一个模块,如何暴露(导出)模块中的变量函数,以及如何使用定义好的模块。

  • 在 CommonJs 规范中,一个文件就是一个模块。
  • 在 CommonJs 规范中,每个文件中的变量函数都是私有的,对其他文件不可见。
  • 在 CommonJs 规范中,每个文件中的变量函数必须通过exports或者module.exports暴露(导出)之后才能被其他文件引用。
  • 在 CommonJs 规范中,想要使用其他文件暴露的变量函数必须通过require导入模块才可以使用。

# 模块导出

globals就讲过了将属性或方法放到global供其他模块使用,但这不算是 CommonJs 规范式的模块使用,真正符合 CommonJs 规范式模块使用例子如下:

a.js

const name = "ll";
const sayName = () => {
  console.log(`sayName: ${name}`);
};
console.log("module 1");

module.exports = {
  name,
  say: sayName,
};
// 或者
/* exports.name = name;
exports.aaa = sayName;
 */
// 注意!!!导出是{},意味着导出失败
/* exports = {
  name,
  sayName
} */
// 注意!!!导出是sayName,意味着覆盖了,只导出了最后一次赋值
/* module.exports = name;
module.exports = sayName; */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

b.js

const { name, say } = require("./a");
console.log("name", name);
say();
1
2
3

可以看到exportsmodule.exports并不是完全相同的,exports只能用exports.xxx = xxx来导出并不能直接给exports赋值,就算赋值了也是{}空对象;而module.exports可以是module.exports.xxx = xxx也可以module.exports = xxx,只是module.exports = xxx只能保证最后一次赋值被导出。

# 模块导入

模块导入是const xx = require("xxx")。它有一些注意点:

  1. require 导入模块时可以不添加导入模块的类型
    • 如果没有指定导入模块的类型,那么会依次查找.js.json.node文件。
    • 无论是三种类型中的哪一种,导入之后都会转换成 JS 对象返回给我们
  2. 导入自定义模块时必须指定路径
    • require 可以导入“自定义模块(文件模块)”、“系统模块(核心模块)”、“第三方模块”
    • 导入“自定义模块”时,前面必须加上路径
    • 导入“系统模块”和“第三方模块”时,是不用添加路径的

# ES6 模块

NodeJs8.5 版本将 ES6 的模块添加了进来,NodeJs14.17 版本才稳定下来。

如何在 NodeJs 中使用 ES6 模块

  • 在以.mjs为文件扩展名的文件中,就可以使用 ES6 模块。
  • 不用.mjs的话,可以修改 package.json "type" 字段。如果该字段不填或者填写commonjs,那么 NodeJs 会认为你将使用 CommonJs 规范的模块。如果设为module,那就认为你使用 ES6 模块。
  • --input-type=module 标志告诉 Node.js。

a.mjs

const name = "ll";
const sayName = () => {
  console.log(`sayName: ${name}`);
};
console.log("module 1");

export { name, sayName };
1
2
3
4
5
6
7

b.mjs

import { name, sayName } from "./a.mjs";
console.log("name", name);
sayName();
1
2
3

ES6 模块和 CommonJs 模块差异

  • 没有 require、exports 或 module.exports。在大多数情况下,可以使用 ES 模块 import 加载 CommonJS 模块。如果需要,可以使用 module.createRequire() 在 ES 模块中构造 require 函数。

  • 没有 **filename 或 **dirname。这些 CommonJS 变量在 ES 模块中不可用。

  • 没有 JSON 模块加载。JSON 导入仍处于实验阶段,仅通过 --experimental-json-modules 标志支持。本地 JSON 文件可以直接用 fs 相对于 import.meta.url 加载。也可以使用 module.createRequire()。

    import { readFile } from "fs/promises";
    const json = JSON.parse(await readFile(new URL("./dat.json", import.meta.url)));
    
    1
    2
  • 没有原生模块加载,ES 模块导入当前不支持原生模块。它们可以改为加载 module.createRequire() 或 process.dlopen。

  • 没有 require.resolve。相对解析可以通过 new URL('./local', import.meta.url) 处理。对于完整的 require.resolve 替换,有标记的实验性 import.meta.resolve API。

  • 没有 NODE_PATH,NODE_PATH 不是解析 import 说明符的一部分。 如果需要这种行为,则使用符号链接。

  • 没有 require.extensions,require.extensions 没有被 import 使用。 期望加载器钩子在未来可以提供这个工作流。

  • 没有 require.cache,require.cache 没有被 import 使用,因为 ES 模块加载器有自己独立的缓存。

# 包、Npm、Yarn

是一个或多个模块组成的一个集合(或者说是一个项目),使用本地的包或者模块时直接使用require(路径+模块名),如果你想使用第三方包,那就需要包管理工具 Npm 或者 Yarn。

关于 Npm 可以看Npm 的使用。关于 Yarn 可以看Yarn 官网 (opens new window)