乐趣区

关于node.js:Nodejs中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

创立 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 有什么区别?
退出移动版