CommonJS和 ES6 模块系统是 JavaScript 中两种主要的模块处理方式。它们在语法、加载方式和使用场景上都有显著的区别。
Node.js 中的每个文件都可以作为一个模块,默认处于独立的作用域内。
Node.js 支持以下几种模块:
fs
、http
、path
等。express
、lodash
等。type
字段的产生用于定义package.json
文件和该文件所在目录根目录中.js
文件和无拓展名文件的处理方式。值为'moduel'
则当作es模块处理;值为commonjs
则被当作CommonJs模块处理pacakage.json
没有定义type
字段,则按照CommonJs规范处理package.json
中type
字段的值package.json
中的type
字段为何值,.mjs
的文件都按照es模块来处理,.cjs
的文件都按照CommonJs模块来处理module.exports
和 require
。export
和 import
。CommonJS 和 ES6 模块系统各有优缺点,选择哪种模块系统取决于你的项目需求和运行环境。对于新的前端项目,推荐使用 ES6 模块,因为它是 JavaScript 的官方标准,并且支持更好的优化。对于现有的 Node.js 项目,CommonJS 仍然是一个可靠的选择。
CommonJs主要为了解决ES6之前无模块概念的问题
引入核心模块不需要写路径,引入文件模块需要写路径(一般是相对路径./
或../
开头)
other.js
var msg = 'this is a model';
function myFunc(){
console.log('this is a function by model');
}
// 批量导出
module.exports = {
msg,
myFunc,
name: 'my name'
}
index.js
// 变量名字可以随便起,一般为文件名
// 引入同目录下的model.js,如果是相对路径,必须以./开头
// 此处的model.js的.js可以省略
var other = require('./other')
other.myFunc();
console.log(other.name);
console.log(other.msg);
other.js
var msg = 'this is a model';
function myFunc(){
console.log('this is a function by model');
}
// 逐一导出
exports.msg = msg;
exports.myFunc = myFunc;
exports.name = 'my name'
index.js
// 无论是批量导出还是逐一导出都可以如下选择性导入
var {myFunc,msg,name} = require('./other')
myFunc();
console.log(name);
console.log(msg);
module.exports
生效other.js
export var msg = 'this is a model';
export function myFunc(){
console.log('this is a function by model');
}
index.js
// 导入方式一:导入的多项组成一个对象
import * as other from './other.js'
other.myFunc();
console.log(other.msg);
// 导入方式二:选择性导入
import {myFunc,msg} from './other.js'
myFunc();
console.log(msg);
other.js
var msg = 'this is a model';
function myFunc(){
console.log('this is a function by model');
}
// 批量导出,一个模块中只能声明一个 export default
export default {
msg,
myFunc,
name: 'my name'
}
index.js
import other from './other.js'
other.myFunc();
console.log(other.msg);
console.log(other.name);
注意:export default
导出的不可以使用import {}
解构导入!
内置模块是由 Node.js 自带的模块,在安装 Node.js 时就已经包含在环境中,因此无需额外安装。常见的内置模块包括 fs、http、path、os、crypto 等。
导入内置模块只需要使用require()
函数并传入模块名称即可。例如,要导入文件系统模块 fs、path、http,可以这样做:
// 导入文件系统模块
const fs = require('fs');
// 导入路径模块
const path = require('path');
// 导入 HTTP 模块
const http = require('http');
第三方模块是开发者或开源社区发布的模块,可以通过 npm(Node 包管理器)安装到项目中,常见的第三方模块有 express、lodash、axios 等。
第三方模块安装后,这些模块会被放置在项目的 node_modules
目录下。要导入一个第三方模块,同样使用 require()
函数,但传入的是模块的名称。例如,要导入 express 框架,可以这样做:
# 在导入第三方模块之前,确保已经通过 npm 安装了该模块。
npm install express
然后导入
// 导入第三方模块 express
const express = require('express');
按照导出方式的不同(CommonJS或ES6Modules),使用绝对或相对路径进行导入即可。
**1、核心模块:**如 http 和 fs,在 Node.js 安装时就包含,可以直接加载。
const http = require('http');
**2、本地文件模块:**使用相对路径或绝对路径加载,需指定 ./
或 /
。
const myModule = require('./myModule');
**3、第三方模块:**位于 node_modules
目录中,只需输入模块名称即可加载。
const express = require('express');
**模块缓存机制:**Node.js 会将已加载的模块缓存起来,以提高性能。再次 require()
同一模块时,直接返回缓存中的模块,而不是重新加载。
**刷新缓存:**要重新加载模块,可以删除缓存:
delete require.cache[require.resolve('./myModule')];
当两个或多个模块相互导入时,称为循环依赖。Node.js 能处理简单的循环依赖,但可能导致部分模块导出的对象未完全初始化。
// a.js
const b = require('./b');
console.log('a.js:', b.message);
module.exports = { message: 'Hello from a' };
// b.js
const a = require('./a');
console.log('b.js:', a.message);
module.exports = { message: 'Hello from b' };
// main.js
require('./a');
// b.js: undefined
// a.js: Hello from b
// (node:25426) Warning: Accessing non-existent property 'message' of module exports inside circular dependency
// (Use `node --trace-warnings ...` to show where the warning was created)
b.js
导入 a.js
时,由于 a.js
尚未完全执行,a.message
为 undefined
Node.js 会将每个模块包装在一个函数中,使得每个模块都有独立的作用域。
// Node.js 内部将模块包装为类似这样的函数:
(function(exports, require, module, __filename, __dirname) {
// 模块代码在这里
});
这意味着在模块中定义的变量不会污染全局作用域。
尽管原生模块与文件模块的优先级不同,但是都会优先从文件模块的缓存中加载已经存在的模块。
原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个 http
、http.js
、http.node
、http.json
文件,require("http")
都不会从这些文件中加载,而是从原生模块中加载。
原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js 会解析 require 方法传入的参数,并从文件系统中加载实际的文件。