Node.js中CommonJS和ECMAScript有什么区别()

一、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

  • 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.jspackage.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 【Node.js中CommonJS和ECMAScript有什么区别()】创建 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有什么区别?

    推荐阅读