JS模块化

模块与模块化

随着现代web应用的功能日渐增多,js代码中的逻辑也越加复杂,往往一个js文件中存储了几百上千行代码,各种互不相关的逻辑高度耦合在一起,全局变量大行其道,稍不注意便会造成命名冲突,这些问题的本质就是在JavaScript中无法更好的管理代码,在C#中我们可以使用命名空间,在Java中我们可以使用,但在JavaScript中,最起码在ES6之前,我们什么也没有

一次又一次的事实证明,小的、组织良好的代码远比庞大的代码更容易理解、更易于维护

因此,将代码优化为小的、耦合度较低的片段是很自然的事情,这些片段被称为模块模块是比对象和函数更大的代码单元,它们能够帮助我们对代码进行分类。不仅如此,模块还能隐藏自己的内部实现,外部使用者无需了解模块内部的实现细节,此外,模块的使用还能让我们轻松地在不同的地方复用之前已经写好的模块

在ES6之前实现模块化

ES6之前,因为JavaScript没有提供类似于命名空间这类特性,想要实现模块化便只能创新性的使用目前已有的特性
在我们开始实现之前我们先来确定一下一个模块他究竟该具有哪些特性

  1. 隐藏内部实现
    外部的使用者不需要知道内部的实现细节,外部的访问者也不能使用模块内部未被暴露出来的变量,针对这一点我们可以是使用函数
  2. 暴露公共接口
    我们可以将一些方法或属性暴露出来给其他人使用,因为我们使用函数实现模块,这意味着只能在模块内部访问变量。所以我们必须使用闭包,思路就是在函数内部返回一个接口并使用一个对象承接

分析完毕,我们先实现第一个特性隐藏内部实现

//a.js
(function func() {
    let date = new Date()
    console.log("func模块" + date.getTime)
})()

这里我们使用立即执行函数来创建一个函数作用域
如果不使用立即执行函数的话func需要继续调用一次,因为这个函数只会调用一次所以使用立即执行函数
接下来我们就需要定义模块接口

//a.js
const a = (function func() {
    let date = new Date()
    console.log("func模块" + date.getTime)
    return {
        getTime: function () {
            return new Date().getTime()
        }
    }
})()

我们在立即执行函数里返回了一个对象,对象里有一个getTime的方法,由变量a承接,因为aconst,所以不会污染全局变量,我们后续在引入模块后调用

a.getTime()

来执行此方法

参考代码如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <script src="a.js"></script>
    <script>
        console.log(a.getTime())
    </script>
</body>

</html>

结果

模块化的扩展

有时候,我们的模块可能会依赖于另外一个或多个模块,那该如何解决呢
我们可以将所依赖的模块作为参数传入立即执行函数
我们还是有一个a模块

const a = (function func() {
    let date = new Date()
    console.log("func模块" + date.getTime())
    return {
        getTime: function () {
            return new Date().getTime()
        }
    }
})()

同时我们还有一个b模块

const b = (function () {
    return {
        getYear: function getYear() {
            return new Date(a.getTime()).getFullYear()
        }
    }
})(a)

b模块暴露了一个方法getYear,这个方法能通过a模块中的时间戳获得这个时间戳年份,我们现在将它引入试一下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <script src="b.js"></script>
    <script src="a.js"></script>
    <script>
        console.log(a.getTime())
        console.log(b.getYear())
    </script>
</body>

</html>

结果
结果并不如人意
这就是通过这种形式实现模块化的最大缺陷,我们必须十分小心的维护每一个模块的依赖关系我们必须按照正确的顺序将其引入,否则就会出现以上问题
让我们调整一下顺序再看看结果

<body>
    <script src="a.js"></script>
    <script src="b.js"></script>
    <script>
        console.log(a.getTime())
        console.log(b.getYear())
    </script>
</body>

结果
成功了

在后来,人们为了解决以上的问题,于是便推出了两个不同的标准,分别是CMJAMD标准

CMJ

CMJ全称为CommonJS,是一个专用于node.js模块化标准
CMJ中规定,每个文件都是一个模块,文件中的所有变量函数都包括在模块当中,不需要担心全局污染,如果模块中的属性或方法想要被其他模块使用就需要使用导出导入

export

CMJ提供变量module,该变量具有属性exports,我们可以通过module.exports作为模块的公共接口

const a = 1;
const b = 2;
function sum() {
    return a + b;
}
module.exports = {
    a,
    b,
    sum
}

require

模块的导入更加简单

const exp = require("./export.js");
console.log(exp.sum())

结果如下
结果

AMD

AMD标准全称为Asynchronous Module Definition
AMD会提供一个define的函数用于导出模块,这个函数需要传入3个参数,一个为当前模块名,一个为当前模块依赖的模块列表,一个为初始化模块的回调函数,该回调函数的参数依赖的模块回调函数会返回一个接口
可以看出,AMD有以下几项优点。

  1. 自动处理依赖,我们无需考虑模块引入的顺序
  2. 异步加载模块,避免阻塞
  3. 在同一个文件中可以定义多个模块
    如今AMD标准已几乎不再使用,所以不再演示用法
    演示图
    1

ESM

ES6中推出了一种新的标准ESM,该标准适用于浏览器环境

export

ESM中的模块导出有两种方式,分为具名导出默认导出一个模块可以存在多个导出方式,最后将会合为一个对象导出

具名导出

export const a = 1;
export function add(a, b) {
    return a, b
}
const b = 2;
export { b as c }

默认导出

const a = 1;
const b = 2;
export default 3;
export default function sum() {
    return a + b;
}
export { a, b as default };

import

ESM中模块的导入需要根据不同的导入方式来使用不同的语法

// 默认导出
import a from "export"
// 具名导出
import { a, b } from "export"
// 具名+默认导出
import c, { a, b as d } from "export"

  1. 图源JavaScript忍者秘籍 ↩︎

  • 35
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值