NodeJS模块化

169 阅读3分钟

Node.js 和浏览器的 JavaScript 最大的不同就在于 Node.js 是模块化的。

  • Node.js只是一个平台,不是新的语言。

什么是模块化?

代码具有模块结构,整个应用可以自顶向下划分为若干个模块,每个模块彼此独立,代码不会相互影响。模块化的目的是使代码可以更好地复用,从而支持更大规模的应用开发。

为什么使用js定义模块报错了?

Node.js 目前默认用CommonJS规范定义 .js 文件的模块,用ES Modules定义 .mjs 文件的模块。如果我们直接将index.mjs文件改成index.js,然后运行node index.js,则会报错

解决方案?

如果要用ES Modules定义 .js 文件的模块,可以在 Node.js 的配置文件package.json中设置参数type: module,如下:

{
  "type": "module"
}

模块定义与引用

1. ES Modules模式

// model.js
const log = (message) => {
  console.log(message)
};
const err = (message) => {
  console.err(message)
};
const USERANME = '张三';
export { log };
// index.js
import { log, err as errorMessage, USERANME } from './model.js'
log(USERANME);
errorMessage('我是错误文案')

ES Modules支持default模式,可以用任意名称表示

// model.js
// 先定义再批量导出api
const default log = (message) => {
  console.log(message)
};
export log;

// 最后统一导出api
const default log = (message) => {
  console.log(message)
};
export default log;
// index.js
import logFn from './model.js'
logFn('hello world');

批量导入

// model.js
const USERANME = '张三';
const default log = (message) => {
  console.log(message)
};
const err = (message) => {
  console.err(message)
};
export log;
export { USERANME, err };
// index.js
import * as funcs from './model.js'
funcs.err(funcs.USERANME);
funcs.default(funcs.USERANME)

这里需要注意一下,default格式的方法,是通过.default进行访问的,不再是model.js里面定义的方法的名字。

2. CommonJS

// model.js
const log = (message) => {
  console.log(message)
};
module.exports log;
// index.js
const logFun = require('./model.js');
logFun('hello world')

实际上CommonJSES Modules类似,和ES Modules不同的地方是,CommonJS采用module.exports/requireES Modules则采用export/import

同样,CommonJS也支持同时导出多个方法及参数,因为CommonJS导出的是真正的对象,所以可以起别名

// model.js
const USERANME = '张三';
const log = (message) => {
  console.log(message)
};
const err = (message) => {
  console.err(message)
};
module.exports = { USERANME, log, err as errMessage }
// index.js
const { USERANME, log, errMessage } = require('./model.js')
log(USERANME);
errMessage(USERANME);

下面这种情况,会重写导出的默认空间

// model.js
defaults.USERANME = '张三';
defaults.log = (message) => {
  console.log(message)
};
defaults.err = (message) => {
  console.err(message)
};
const overWrite = () => {
  console.log('我重写了默认导出空间')
}
module.default = { overWrite } // 在这里重写了导出默认空间
const { overWrite } = require('./model.js')
overWrite()

需要注意的是,两种写法不能混用,因为:

const log = (message) => {
  console.log(message)
};
const err = (message) => {
  console.err(message)
};
module.exports = { log, err }

等效于

const log = (message) => {
  console.log(message)
};
const err = (message) => {
  console.err(message)
};
// const value = { log, err };
// export default value;
export default { log, err };

ES Modules与CommonJS的主要区别

  1. 导出方式及导出重命名方式不同

    • ES Modules导出时候要写作 export { log as logMessage }
    • CommonJS导出时候要写作module.exports = { logMessage: log }
  2. 引用方式不同

    • ES Modules导出时候要写作import { log } from './model.js',文件后缀不可省;
    • CommonJS导出时候要写作const { log } = require('./model.js'),文件后缀可以省;
  3. 引用位置可以不同

    • ES Modules使用import方式引用时候,只能写在文件最外层,不能再局部引用;

    • CommonJS使用require方式引用时候,可以写在最外层,也可以写在局部作用

      const { log } = require('./model')
      if(true) {
        const { err } = require('./model')
      }
      
  4. 引用路径限制不同

    • ES Modules使用import方式引用时候,路径必须是固定的;

    • CommonJS使用require方式引用时候,路径可以是有变量的;

      const path = 'model.js'
      const { log } = require(`./${path}`)
      log('hello world')
      
    • 虽然ES Modules不允许再语句块中设置动态参数,但是它提供了一个将import作为异步函数的机制,所以可以这样引用:

      (async ()=>{
        await import { log } from './model.js'
        log('hello world')
      })()
      

      一般我们引用都是写在最外面,除非在项目中,不同平台环境下,引用的不是一个模块,这种时候就必须要动态加载了(如果加载所有模块,会消耗性能)。