JavaScript 模块化 CommonJS ES Module

129 阅读4分钟

解决问题

  • 全局变量污染
  • 依赖混乱
  • 模块化出现后,我们就可以把臃肿的代码细分到各个小文件中,便于后期维护管理

前端主要有两大模块化标准:

  • CommonJS,简称CMJ,这是一个社区规范,出现时间较早,目前仅node环境支持
  • ES Module,简称ESM,这是随着ES6发布的官方模块化标准,目前浏览器和新版本node环境均支持

CommonJS

node天生支持CommonJS模块化标准

node规定:

  1. node中的每个js文件都是一个CMJ模块,通过node命令运行的模块,叫做入口模块

  2. 模块中的所有全局定义的变量、函数,都不会污染到其他模块

  3. 模块可以暴露(导出)一些内容给其他模块使用,需要暴露什么内容,就在模块中给 module.exports赋值

  4. 一个模块可以导入其他模块,使用函数 require("要导入的模块路径")即可完成,该函数返回目标模块的导出结果

    1. 导入模块时,可以省略 .js
    2. 导入模块时,必须以 ./../开头
  5. 一个模块在被导入时会运行一次,然后它的导出结果会被node缓存起来,后续对该模块导入时,不会重新运行,直接使用缓存结果

小案例 动态打印

模块划分

  1. main.js 入口模块
  2. delay.js 延时模块
  3. print.js 打印模块
  4. config 数据模块

运行

node main.js

main.js

const delay = require("./delay.js");
const print = require("./print.js");
const config = require("./config.js");
/**
 * 运行该函数,会逐字打印config.js中的文本
 * 每个字之间的间隔在config.js已有配置
 */
async function run() {
  for (let i = 0; i < config.text.length; i++) {
    print(i);
    await delay(config.wordDuration);
    console.clear()
  }
}
run()

delay.js

/**
 * 该函数返回一个Promise,它会等待指定的毫秒数,时间到达后该函数完成
 * @param {number} ms 毫秒数
 * @returns {Promise}
 */
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

module.exports = delay;

print.js

const config = require("./config.js");
/**
 * 该函数会做以下两件事:
 * 1. console.clear() 清空控制台
 * 2. 读取config.js中的text配置,打印开始位置到index位置的字符
 * @param {number} index
 */
function print(index) {
  console.clear();
  console.log(config.text.slice(0, index));
}
module.exports = print;

config.js

module.exports = {
  wordDuration: 100, // 打印每个字的时间间隔
  text: `西风烈,
长空雁叫霜晨月。
霜晨月,
马蹄声碎,
喇叭声咽。
雄关漫道真如铁,
而今迈步从头越。
从头越,
苍山如海,
残阳如血。`, // 要打印的文字
};

ES Module

ES Module的导出

ES Module分为两种导出方式:

  • 具名导出(普通导出),可以导出多个
  • 默认导出,只能导出一个

一个模块可以同时存在两种导出方式,最终会合并为一个「对象」导出

导出

export const a = 1; // 具名,常用
export function b() {} // 具名,常用
export const c = () => {}  // 具名,常用
const d = 2;
export { d } // 具名
const k = 10
export { k as temp } // 具名

// export default 3 // 默认,常用
// export default function() {} // 默认,常用
// const e = 4;
// export { e as default } // 默认

const f = 4, g = 5, h = 6
export { f, g, h as default} // 基本 + 默认

// 以上代码将导出下面的对象
/*
{
	a: 1,
	b: fn,
	c: fn,
	d: 2,
	temp: 10,
	f: 4,
	g: 5,
	default: 6
}
*/

注意:导出代码必须为顶级代码,即不可放到代码块中

导入

针对具名导出和默认导出,有不同的导入语法

// 仅运行一次该模块,不导入任何内容
import "模块路径"
// 常用,导入属性 a、b,放到变量a、b中。a->a, b->b
import { a, b } from "模块路径"   
// 常用,导入属性 default,放入变量c中。default->c
import c from "模块路径"  
// 常用,default->c,a->a, b->b
import c, { a, b } from "模块路径" 
// 常用,将模块对象放入到变量obj中
import * as obj from "模块路径" 


// 导入属性a、b,放到变量temp1、temp2 中
import {a as temp1, b as temp2} from "模块路径" 
// 导入属性default,放入变量a中,default是关键字,不能作为变量名,必须定义别名
import {default as a} from "模块路径" 
//导入属性default、b,放入变量a、b中
import {default as a, b} from "模块路径" 
// 以上均为静态导入

import("模块路径") // 动态导入,返回一个Promise,完成时的数据为模块对象

注意:静态导入的代码必须为在代码顶端,也不可放入代码块中

另外,静态导入的代码绑定的符号是常量,不可更改