一、CommonJS 与 ECMAScript
1、概念阐明
- CommonJS 与 ECMAScript 都是编写 JS 的规范。
- ECMAScript 规范让不同浏览器上执行雷同 js 代码能失去雷同后果,是现有 js 语言 的通用规范。
- CommonJS 规范让雷同 js 代码在 Node.js 环境下运行失去雷同后果,只是 Node.js 下的规范。
2、区别是什么?
Node.js 既反对 CommonJS 规范,也齐全反对 ECMAScript 规范。Node.js 环境下用 js 语言编写的文件,有三种格局:.js
、.mjs
、.cjs
。
.mjs
:此类文件只用能 ECMAScript 规范解析执行;.cjs
:此类文件只用能 CommonJS 规范解析执行;-
.js
:依据具体情况决定,采纳什么规范来执行:- 状况 1:如果
.js
没有其余非凡阐明,默认应用 CommonJS 规范解析执行; - 状况 2:
package.json
文件中 type 属性值为缺省值 或 等于commonjs
,那么采纳 CommonJS 规范解析执行.js
文件;如果 type 属性等于module
,那么采纳 ECMAScript 规范解析执行.js
文件。 -
状况 3:命令行中有 flag,
--input-type=module
示意采纳 ECMAScript 规范解析执行.js
文件;--input-type=commonjs
示意采纳 CommonJS 规范解析执行.js
文件。node --input-type=module --eval "import {sep} from'path'; console.log(sep);" echo "import {sep} from'path'; console.log(sep);" | node --input-type=module
- 状况 1:如果
require
只能导入 CommonJS 标准文件;import
反对两种规范的文件导入。
二、CommonJS 规范的简略示例
1、写个模块
Node.js 中,一个 js 文件
被看做一个 模块
,譬如上面 circle.js
就是一个模块,导出两个办法。
// circle.js
const {PI} = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;
2、导入模块
在另一个 js 文件中应用 circle.js
模块的办法,实现代码重用。
// foo.js
const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
三、ECMAScript 规范的简略示例
1、写个模块
// addTwo.mjs
function addTwo(num) {return num + 2;}
export {addTwo};
2、导入模块
// app.mjs
import {addTwo} from './addTwo.mjs';
// Prints: 6
console.log(addTwo(4));
四、导入模块的形式
1、require
require 只能被用来加载 CommonJS 模块。/home/ry/projects/foo.js
文件加载其它模块的形式如下:
// foo.js
const circle1 = require('./circle.js');
const circle2 = require('../circle.js');
const circle3 = require('/home/marco/circle.js');
const circle4 = require('circle');
const circle5 = require('./some-library');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
寻找导入模块的办法阐明:
./circle.js
:在foo.js
所在文件夹下,去寻找加载circle.js
../circle.js
:在foo.js
所在文件夹的上一层文件夹,去寻找加载circle.js
/home/marco/circle.js
:依照这个绝对路径,去寻找加载circle.js
-
circle
:先从 Node.js 内置模块去寻找加载,没有再去node_modules
文件夹下寻找circle
模块,且会始终向上一层文件夹寻找,如下/home/ry/projects/node_modules/circle.js /home/ry/node_modules/circle.js /home/node_modules/circle.js /node_modules/circle.js
-
./some-library
:先从我的项目根目录寻找package.json
,再去foo.js
所在文件夹下,寻找两个 index 模块,没有就返回Error: Cannot find module 'some-library'
// 1、package.json 寻找如下内容 { "name" : "some-library", "main" : "./lib/some-library.js" } // 2、查找是否有 index.js 模块 ./some-library/index.js // 3、查找是否有 index.node 模块 ./some-library/index.node
2、import
-
CommonJS 模块
和ECMAScript 模块
都能够用import
来导入,三种应用形式// 相对路径 import {sep} from './startup.js' import {sep} from './config.mjs' // 绝对路径 import {sep} from '/home/project/startup.js' import {sep} from '/home/project/startup.mjs' // 模块名,寻找模块的形式与 require 一样 import {sep} from 'some-package'
-
import.meta.url
:示意模块的相对 URL// 通过模块相对 url,来读取相对路径的文件 import {readFileSync} from 'fs'; const buffer = readFileSync(new URL('./data.proto', import.meta.url));
3、动静导入
下面的形式都是动态导入,某些场景可通过动静导入来提早模块加载,取得更好的页面体验感。CommonJS 和 ECMAScript,都反对动静导入。
// CommonJS 动静导入
import('/modules/my-module.js')
.then((module) => {// Do something with the module.});
// ECMAScript 动静导入
import('/modules/my-module.mjs')
.then((module) => {// Do something with the module.});
五、示例:抉择解析规范
如果 Node.js 我的项目根目录有 my-app.js
和 package.json
两个文件,那么终端启动我的项目 node my-app.js
,各个模块会以什么规范被导入?
// /home/project/my-app.js
// my-app.js 会以 ES 规范导入,因为同文件夹 package.json 中的 type 属性所致。// 如果 startup 目录下没有 package.json,那么应用上一层目录中 package.json 的设置
// 即 用 ES 规范导入 init.js
import './startup/init.js';
// 依据 ./node_modules/commonjs-package/package.json 中 type 属性值
// 缺省 就用 commonjs 规范,否则就依照属性值规范。import 'commonjs-package';
// 依据 ./node_modules/commonjs-package/package.json 中 type 属性值
// 缺省 就用 commonjs 规范,否则就依照属性值规范。import './node_modules/commonjs-package/index.js';
// /home/project/package.json
{"type": "module",}
六、其余补充
1、其余文件
- 如果模块没有被找到,零碎会尝试其余后缀的文件,别离为:
.js
,.json
, and finally.node
.
2、同一个对象
- 第一次调用
require('foo')
后,模块对象会被缓存,前面再调用require('foo')
只会返回被缓存的对象,不会反复加载。
3、内置模块
- 内置模块优先级最高。应用
require('http')
时,就算有雷同的 js 模块名http.js
也会被疏忽,而应用内置模块 http,当然,能够用require('node:http')
形式,让代码更易于了解。
4、包裹模块
Node.js 会用函数包裹加载的模块,避免多个模块外部全局变量名的抵触,同时传递几个必要要参数给模块,不便写模块代码。
(function(exports, require, module, __filename, __dirname) {// 理论模块代码,在这里});
5、ECMAscript 规范中应用 require
创立 example.mjs
文件,此后缀文件是 ECMAscript 规范,所以无奈间接应用 require,但能够用 Node.js 的内置模块 Module
来实现 require。
// example.mjs
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
// sibling-module.js is a CommonJS module.
const siblingModule = require('./sibling-module');
七、参考文档
- Node.js 中 CommonJS 和 ECMAScript 有什么区别?