JavaScript是如何实现模块化的?

一. 为什么要有模块化?

1. 内联的JavaScript语句

JavaScript诞生之初的应用比较简单,例如用户输入校验。代码量少,一般直接写在html中。

<script>
var name="kobe"
console.log(name)
script>

2. 独立的JavaScript文件

随着用户体验要求变高,前端承载的功能变多了,代码量也随着膨胀

例如,axios的出现带来了前后端分离,前端通过后端接口获取数据,动态渲染页面;SPA让页面切换更丝滑,但需要实现前端路由,状态管理等功能。

为了提高代码的可复用性,人们开始将JavaScript从html解耦,封装成独立的模块

// index.html
<script src="moduleA.js">script>

// moduleA.js
var name="kobe";

引入的外部JavaScript文件中声明的变量是全局作用域的,当引用的第三方模块多了,势必会造成全局变量冲突

// index.html
<script src="moduleA.js">script>
<script src="moduleB.js">script>

// moduleA.js
var name="kobe";

// moduleB.js
var name="iverson";

3. 命名空间

解决全局变量冲突的方法之一是给每个模块分配命名空间,进行隔离。

// index.html
<script src="moduleA.js">script>
<script src="moduleB.js">script>

// moduleA.js
var moduleA = {};
moduleA.name="kobe";

// moduleB.js
var moduleB = {};
moduleB.name="iverson";

但是,JavaScript对象属性默认是公有的,这意味着模块内的变量既能被外部访问let scopeVar = moduleA.name,也可能被外部修改moduleA.name = ‘xxx’。那么,如何保护数据不被外部修改?

4. 闭包

为了保护数据不被外部修改,人们将模块封装在函数作用域内。

// index.html
<script src="moduleA.js">script>
<script src="moduleB.js">script>

// moduleA.js
let moduleA = (function (){
    let name = 'kobe';
    return {
        getName:function(){
            return name;
        }
    }
})();

// moduleB.js
let moduleB = (function (){
    let name = 'iverson';
    return {
        getName:function(){
            return name;
        }
    }
})();

使用立即调用的函数表达式(IIFE) 创建闭包,将模块封装在函数作用域内,并对外提供可访问模块的公共API。这样,在moduleB中可以通过moduleA.getName()访问数据,但是必须保证moduleA在moduleB之前完成加载。当模块数量多了,如何管理好模块依赖又是一个问题。

小结

综上所述,为了提高JavaScript代码的可复用性,开发者尝试利用JavaScript语言特性来模拟实现模块化。分别用命名空间闭包来解决全局变量冲突和实现数据保护。然而,管理模块依赖是比较复杂的问题,因此诞生了CommonJS,AMD,UMD等模块化方案。

二. 模块模式

模块化思想就是把代码拆分成独立的模块,逻辑独立,各自实现,然后再把它们连接起来实现完整功能。对应的实现模式就叫模块模式,它是所有模块化系统的基础。

1. 模块标识符

每个模块都有一个可用于引用它的标识符。

import axios form "axios"
import msgBox from "/src/ui.js"
  • 原生浏览器的模块标识符是绝对文件路径
  • Node.js环境默认会搜索node_modules目录,可省去路径。

2.模块依赖

模块化系统的核心是管理依赖,即保证模块正常运行时所需要的外部依赖能够完成加载和初始化。

// moduleA.js
import moduleB form "moduleB.js"

console.log(moduleB.getName())

3.模块加载

加载模块的一般步骤是: 加载模块及其依赖的代码,在所有依赖加载和初始化之后,才会执行入口模块。

a. 同步加载

浏览器加载JavaScript文件默认是同步的。

例如moduleA.js依赖了moduleB.js,moduleB.js依赖了moduleC.js,则必需按以下顺序加载依赖。

<script src="moduleC.js">script>
<script src="moduleB.js">script>
<script src="moduleA.js">script>

同步加载的缺点很明显:

  • 性能: 阻塞页面渲染,直到所有依赖按顺序加载完。
  • 复杂性: 需要手动管理依赖的加载顺序。

b. 异步加载

异步加载模块,不会阻塞页面,只需要在加载完成后执行回调。

例如上面同步加载的例子改用异步加载,则不会阻塞页面渲染,只需要在moduleC.js和moduleB.js完成加载和初始化之后,回调执行moduleA.js。

可使用

你可能感兴趣的:(编程语言,javascript,模块化,ES6,CommonJS,AMD)