当前位置: 首页 > news >正文

JavaScript模块化-CommonJS规范和ESM规范

1 ES6模块化

1.1 ES6基本介绍

        ES6 模块是 ECMAScript 2015(ES6)引入的标准模块系统,广泛应用于浏览器环境下的前端开发。Node.js环境主要使用CommonJS规范。ESM使用import和export来实现模块化开发从而解决了以下问题:

  • 全局作用域下的变量命名冲突问题,模块化变量不会暴露在全局。
  • 解决了依赖管理混乱的问题。
  • 模块化提高了代码的可读性和维护性

1.2 导出方法

        首先创建Index.html文件,再分别创建user.js和article.js以及index.js,index.js将导入user和article内部的变量和方法,user和articel分别导出需要暴露的函数和变量,在index.html中引入index.js。

        user.js文件代码如下所示:

const name = "User 1"const getData = () => {const res = "Data from User 1"return res
}const getAge = () => {return 30
}

         article.js初始代码如下所示:

const name = "Article 1"const getData = () => {const res = "Data from Article 1"return res
}const getColunmn = () => {return 3
}

        index.html如下所示:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body></body>
</html>

1.2.1 分别导出

        在需要导出的变量前加上export即可导出该变量,user.js如下所示:

export const name = "User 1"export const getData = () => {const res = "Data from User 1"return res
}

1.2.2 按需命名导出

        将需要导出的变量放入一个对象中,统一使用export {}导出,article文件代码如下所示。

export {name, getData}

1.2.3 默认导出

        使用export default 变量 实现默认导出,注意,一个模块只能有一个默认导出,默认导出的变量将会视为一个整体,在使用imprt * 导出时存放在default属性下,如下所示。

const getAge = () => {return 30
}export default getAgeexport default getAge = () => {
//     return 30
// }

1.3 导入方法

        在index.js进行导入前,需要将index.js引入index.html的body中,加入如下语句:

<body><script src="index.js" type="module"></script>
</body>

        注意:在js文件中使用了import后需要将type="javascript/text"变为type="module",表明以内的是一个js模块,否则报错,此时模块内的变量和方程在浏览器控制台内就无法访问,隔离了变量。

1.3.1 全局导入

        使用import * as 别名 from "./user.js"来引入全部导出的变量,如下所示:

import * as user from './user.js';
import * as article from './article.js';console.log(user)
console.log(article)

        在控制台打印结果如下所示:

 

        可以看到分别导出和按需命名导出变量都在该对象上,按需导出的内容存储在对象的default属性上。 

1.3.2 命名导入

        命名导入使用形如import {name, getData} from "./user.js"来导入,类似解构赋值,如果变量重名可以使用as 别名来解决命名冲突问题,如下所示:

import {name as username, getData as getUserdata} from './user.js';
import {name, getData} from './article.js';console.log(username)
console.log(getUserdata())
console.log(name)
console.log(getData())

        可以看到控台输出如下所示:

 

1.3.3 默认导入

        默认导入不需要解构赋值(形如 import user<-任意名称 from "./user.js"),可以以任意不冲突变量名来导入,如下所示: 

import getAge from './user.js';
import anyName from './article.js';console.log(getAge()) // output: 3
console.log(anyName.getData()) // output: 30

1.3.4 混合导入

        混合导入可以同时导入默认导出和命名导出的内容,如下所示:

import user, {name, getData} from './user.js';console.log(user) 
console.log(name) 
console.log(getData())

1.4 浏览器环境使用CommonJS规范-使用Browserify

       将CommonJS模块文件转换为浏览器可识别的格式有许多方法,常用的工具有:ESbuild、Babel、Browserify等。本文将介绍Browserify,可以将 CommonJS 模块转换为浏览器可用的格式,只需要转换后在浏览器中引入即可,使用流程如下所示:

1. 安装Browserify

        使用下面的命令安装Browserify:

npm install --save-dev browserify

2. 准备CommonJS规范的导入和导出文件

        修改user.js文件内容如下所示:

const name = "User 1"
const getData = () => {const res = "Data from User 1"return res
}const getAge = () => {return 30
}module.exports = {name,getData,getAge
}

        修改index.js文件内容如下所示:

const user = require('./user.js');
console.log(user)

3. 使用工具打包编写好的CommonJS模块文件

        运行如下命令将index.js打包未可以识别的bundle.js:

npx browserify index.js -o bundle.js

4. 引入打包后的文件bundle.js到html文件当中

<script src="bundle.js" type="module"></script>

        即可在控制台中查看输出。 

1.5 注意事项 

1.5.1 默认导出和命名导出的差异

命名导出:

  • 可以在一个模块中导出多个内容。
  • 导入时需要使用大括号 {} 并匹配导出的名称。
  • 更适合导出多个功能或工具。

默认导出:

  •  每个模块只能有一个默认导出。
  • 导入时可以使用任意名称,不需要大括号。
  • 适合导出一个主要的功能或对象。

1.5.2 模块的静态结构与运行时动态加载运行

        在ESM中模块的依赖关系在编译时就已经决定了,模块之间的依赖关系和加载顺序是静态的,带来一下好处:

  • 性能优化:构建阶段进行代码分析和优化,预加载、并行加载;
  • 提前的错误检测:问题可以在编译阶段就提前发现
  • 静态分析:准确的类型检查和自动补全

         同时模块也可以通过事件进行动态的添加并执行(一次),在dynamic_code.js文件中添加console.log("content loaded"),然后再script中添加如下代码:

<body><button id="btn">Click</button><script>const btn = document.getElementById('btn');btn.addEventListener('click', async () => {await import('./dynamic_code.js')});</script>
</body>

         同时需要注意到import返回的是一个Promise对象,需要使用then或者await来等待加载完成状态。

1.5.3 导出变量的修改变化

        我们先来看一个例子,来猜一猜它的输出:

function test(){let sum = 0function increment(){sum ++}return {sum, increment}
}let {sum, increment} = test()console.log(sum) // 0
increment()
console.log(sum) // 0

        解构赋值得到的只是一个拷贝,并不是原始的引用,所以increment增加的是闭包环境内的sum对象,和赋值得到的sum没有关系。但是如果是返回的是引用对象(数组和对象),那么就不同了:

function test(){const obj = {sum:0}function increment(){obj.sum ++}return {obj, increment}
}let {obj, increment} = test()console.log(obj.sum) // 0
increment()
console.log(obj.sum) // 1

        像obj.sum一样在ESM中导出的变量进行修改是会修改原始模块中的变量的值,如果意外修改了模块内的变量,且模块变量被多个其它模块所共用可能造成意外的错误。如下所示是一个ESM中的修改影响原始模块变量的例子:

         首先编写count.js文件,导出let声明的sum和方程increment:

let sum = 0
function increment(){sum ++
}
export {sum, increment}

         在index.js中导入sum和increment函数,在网页控制台中查看输出:

import { sum, increment } from "./count";
console.log(sum); // 0
increment();
increment();
increment();
console.log(sum); // 3

        可以看到输出0和3,对导出变量的修改会影响模块内的原始值,所以为了让模块内的变量不被修改,需要将导出的变量使用const 声明,如下所示。:

const sum = 0
const increment = () =>{sum ++
}
export {sum, increment}

2 CommonJS模块

2.1 CommonJS基本介绍

        CommonJS 是 Node.js 最初采用的模块系统,基于 require 和 module.exports以及exports 实现动态模块加载。尽管ESM才是官方的模块化标准,但是CommonJS是现在官方认可且Node.js环境中广泛使用的模块化规范。

2.2 导出导出方法

        在CommonJS中导出的内容相当于挂载在一个对象上,exports.属性名相当于在对象上添加属性,module.exports相当于修改整个导出的对象。

2.2.1 exports简化导出-require导入

        分别编写index.js和user.js及运行的输出如下所示,使用exports.变量名挂载到导出对象上,require( 'filepath')获得导入对象

// index.js
const user = require('./user');
console.log(user); // output: { name: 'User 1', age: 30, getData: [Function (anonymous)] }// user.js
exports.name = "User 1"
exports.age = 30
exports.getData = () => {const res = "Data from User 1"return res
}

        导入的时候可以使用{ }进行赋值的解构,如果有重名使用ES6的重命名语法,如下所示:

const {name:username} = require('./user');
console.log(username);

2.2.2 module.exports整体导出

        使用module.exports整体导出对象,导入方式不变,如下所示:

// user.js
const name = "User 1"
const age = 30
const getData = () => {const res = "Data from User 1"return res
}module.exports = {username: name,age,getData
}

2.2.3 混合导出存在的问题

        首先不能使用exports = {}来进行导出,如果混合使用exports.变量名和module.exports = {}来进行导出,会以最后的module.exports值进行确认,如下所示:

const name = "User 1"
const age = 30
const getData = () => {const res = "Data from User 1"return res
}exports.a = 1module.exports = {username: name,age,getData
}exports.b = 4module.exports = {username: "Usernawm",age,getData,b:32
}exports.c = 3

        导出的对象如下所示:

{ username: 'Usernawm', age: 30, getData: [Function: getData], b: 32 }

2.3 实现原理

        CommonJS的原理实际是将每一个模块封装在一个函数中,module、exports和require都是函数传递的参数,我们使用console.log(arguments.callee.toString())来查看,输出内容如下。

function (exports, require, module, __filename, __dirname) {
exports.name = "User 1"
exports.age = 30
exports.getData = () => {const res = "Data from User 1"return res
}
console.log(arguments.callee.toString())
}

2.4 Node.js环境中的ESM和CommonJS中的兼容问题

1. 包配置修改-type字段

        通过 package.json 中的 "type" 字段也可以指定整个包的模块类型,"module" 指的就是ESM。

// package.json
{"type": "module"
}

2. 文件扩展名设置

        Node.js 使用文件扩展名(.mjs 表示 ESM,.cjs 表示 CommonJS)来区分模块类型。

3. 默认导出和命名导出的处理

        在 CommonJS 中,module.exports 可以是任何类型(对象、函数、类等),而 ESM 的默认导出是一个单一的值。 当从 CommonJS 模块导入到 ESM 时,整个 module.exports 对象被视为默认导出。

// CommonJS 模块
module.exports = {add: (a, b) => a + b
};// ESM 导入
import math from './math.js';
console.log(math.add(2, 3)); // 正常工作

         反之,从 ESM 导入到 CommonJS 时,需要通过 default 属性访问默认导出。

// ESM 模块
export default function log(message) {console.log(message);
}// CommonJS 导入
const log = require('./logger.mjs').default;
log('Hello, Mixed Modules!');

相关文章:

  • 论文阅读(十一):CBAM: Convolutional Block Attention Module
  • C++入门(有C语言基础)
  • 并行编程实战——TBB框架的应用之一Supra的基础
  • 【2024】前端学习笔记11-网页布局-弹性布局flex
  • 常用bash脚本
  • 在大数据爬取中选择合适的IP
  • OpenCV学堂 | YOLOv8官方团队宣布YOLOv11 发布了
  • uniapp 知识点
  • 中九无科研无竞赛保研经验帖——上交软院、中科大计算机、复旦工程硕、南大工程硕、浙大软件
  • android 原生加载pdf
  • 【Linux笔记】在VMware中,为基于NAT模式运行的CentOS虚拟机设置固定的网络IP地址
  • 3. 轴指令(omron 机器自动化控制器)——>MC_MoveRelative
  • 随身 WiFi 扩展 USB 接口 可用于外接 U 盘 有线网卡 打印机
  • 计算机毕业设计 基于协同过滤算法的个性化音乐推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • 链表OJ经典题目及思路总结(一)
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【笔记】你不知道的JS读书笔记——Promise
  • C++11: atomic 头文件
  • cookie和session
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • CSS中外联样式表代表的含义
  • Javascript基础之Array数组API
  • java正则表式的使用
  • leetcode388. Longest Absolute File Path
  • Python中eval与exec的使用及区别
  • Redis 懒删除(lazy free)简史
  • windows下mongoDB的环境配置
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 基于axios的vue插件,让http请求更简单
  • 移动端解决方案学习记录
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​configparser --- 配置文件解析器​
  • ‌移动管家手机智能控制汽车系统
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • ###C语言程序设计-----C语言学习(6)#
  • #include到底该写在哪
  • #NOIP 2014#Day.2 T3 解方程
  • #VERDI# 关于如何查看FSM状态机的方法
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (JS基础)String 类型
  • (Oracle)SQL优化基础(三):看懂执行计划顺序
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (十八)三元表达式和列表解析
  • (四十一)大数据实战——spark的yarn模式生产环境部署
  • (一)基于IDEA的JAVA基础12
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)德国人的记事本
  • (转载)Linux 多线程条件变量同步
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET 发展历程
  • .NET 通过系统影子账户实现权限维持
  • .net(C#)中String.Format如何使用
  • .net和php怎么连接,php和apache之间如何连接