# 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;
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();
2
3
4
以下属性或方法,都是表示它当前所在模块的一些信息和方法。每个模块都有这些方法,这就导致它们看起来像是全局的globals
。
__dirname
:当前模块的目录名__filename
:当前模块的文件名。这是当前模块文件的绝对路径,符号链接已解析exports
:模块导出module
:对模块的引用require()
:导入模块或文件
其他的globals
console
:用于打印到 stdout 和 stderrprocess
:进程对象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; */
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();
2
3
可以看到exports
和module.exports
并不是完全相同的,exports
只能用exports.xxx = xxx
来导出并不能直接给exports
赋值,就算赋值了也是{}
空对象;而module.exports
可以是module.exports.xxx = xxx
也可以module.exports = xxx
,只是module.exports = xxx
只能保证最后一次赋值被导出。
# 模块导入
模块导入是const xx = require("xxx")
。它有一些注意点:
- require 导入模块时可以不添加导入模块的类型
- 如果没有指定导入模块的类型,那么会依次查找
.js
、.json
、.node
文件。 - 无论是三种类型中的哪一种,导入之后都会转换成 JS 对象返回给我们
- 如果没有指定导入模块的类型,那么会依次查找
- 导入自定义模块时必须指定路径
- 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 };
2
3
4
5
6
7
b.mjs:
import { name, sayName } from "./a.mjs";
console.log("name", name);
sayName();
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)。