JavaScript模块化开发演进:从全局变量到IIFE的四种模式解析

作者:闭包旅人

时间:2026-10-26

浏览:303

本文详细梳理了JavaScript模块化开发的演变历程,从最初的全局命名空间、对象写法,到利用jQuery风格的命名空间模式,再到基于闭包的IIFE(立即执行函数)方案。文章分析了各阶段的优缺点及代码实现,重点探讨了如何解决变量冲突、内部状态保护及模块依赖管理等核心问题,为理解现代JS模块化标准奠定基础。

文章目录

在接触Vue模块化开发之前,许多开发者可能并未深入关注JavaScript组件化开发的持续演变。过去,只要代码能调试运行且无报错即可,主要依赖的语言标准是ECMAScript5和ECMAScript6(2015年发布)。使用ECMAScript6规范编写的程序通常需要借助转译器将其编译为ECMAScript5,以确保浏览器兼容性。作为前端开发者,了解这些背景以及ECMAScript5与ECMAScript6在写法上的区别已足够。

例如,ECMAScript5的写法示例如下:

var MyModuleName = {
  Property1: "",
  Property2: "",
  Config: {
    SetName1:"",
    SetName2:""
  },
  Method1: function() {},
  Method2: function() {}
};

对应的ECMAScript6写法则为:

export const MyModuleName = {
  Property1: "",
  Property2: "",
  Config: {
    SetName1: "",
    SetName2: ""
  },
  myMethod1() {},
  myMethod2() {}
};

然而,仅了解前端开发的大概概念是远远不够的。接下来,我们将对这一部分内容进行详细梳理。

1、使用传统的全局命名空间

这种模式下的缺点显而易见:

⑴全局变量无法控制是否冲突;

⑵成员函数之间看不出直接关系。

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>最原始的模式</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        contentId.innerHTML="a+b="+add(a,b);
        viewId.appendChild(contentId);
        function add(a,b){
            return a+b;
        }        
        function subtract(a,b){
            return a-b;
        }
    </script>
</body>
</html>

虽然可以得出正确结果,但如果进行所谓的模块化开发,即将代码分散到不同文件中,情况将发生变化。

主文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>最原始的模式</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
    </script>
    <script src="module2.js"></script>
    <script src="module1.js"></script>
</body>
</html>

module1.js代码:

const viewId=document.getElementById('view');
const contentId=document.createElement('div');
contentId.innerHTML="a+b="+add(a,b);
viewId.appendChild(contentId);

module2.js代码:

var add=function(a,b){
    return a+b;
}        
var subtract=function(a,b){
    return a-b;
}

但这可能导致因文件引入顺序不同而出现运行错误。如果文件数量增多,依赖关系难以检查,纠错工作将变得极其繁琐。

2、使用对象的写法

缺点:

⑴暴露所有模块成员;

⑵内部状态可以被外部改写;

⑶多文件化后依赖关系不好处理。

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用对象写法</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
        var MyUtils=new Object({
            Property1: "as",
            add:function(a,b){
                return a+b;
            },
            subtract:function(a,b){
                return a-b;
            }
        });
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyUtils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
    </script>
</body>
</html>

3、使用命名空间的写法

前端开发者基本上都使用过JQuery.js来进行前端开发,JQuery.js主要使用命名空间模式来组织代码,定义了一个全局变量 $ 或 jQuery,该变量是一个对象,包含了所有 jQuery 提供的功能。当使用 $或者jQuery 时,实际上就是访问这个全局变量。而 jQuery 库中所有的方法和属性都是在这个全局变量上定义的。

缺点:

⑴无法解决相互依赖问题特别是多文件化后;

⑵代码组织形式逐渐复杂化;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用命名空间的写法</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
 
        var MyApp = {
            UI: {
                //定义UI操作内容
            },
            Data: {
                //定义Data内容
            },
            Service: {
                //定义Service的内容
            },
            Utils: {
                //定义工具类内容
                add:function(a,b){
                    return a+b;
                },
                subtract:function(a,b){
                    return a-b;
                }                
            }
        };        
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyApp.Utils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
    </script>
</body>
</html>

最开始常见的写法也可以是这样的:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用IIFE(立即执行函数)写法</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
        //定义全局变量
        var MyUtils={};
        //定义子命名空间
        var MyUtils.UI={};
        var MyUtils.Data={};
        var MyUtils.Service={};
        var MyUtils.Utils={};
        //在子命名空间中定义内容
        var MyUtils.Utils.add=function(a,b){
                return a+b;
        };
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyUtils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
    </script>
</body>
</html>

4、使用IIFE的写法

缺点:

⑴外部代码无法读取内部的变量;

⑵无法彻底解决模块间的相互依赖问题。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用IIFE(立即执行函数)写法</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
        var MyUtils=(function(){
            Property1: "as";
            var add=function(a,b){
                return a+b;
            };
            var subtract=function(a,b){
                return a-b;
            }
            return {
                add:add,
                subtract:subtract
            }
        })();
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyUtils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
        //外部代码无法读取内部的变量。
    </script>
</body>
</html>

要解决外部访问对象内部数据,可以对外暴露方法:

        var a=1,b=2;
        var MyUtils=(function(){
            Property1: "as";
            var add=function(a,b){
                return a+b;
            };
            var subtract=function(a,b){
                return a-b;
            };
            var setProperty=function(str){
                this.Property1=str;
            };
            return {
                add:add,
                subtract:subtract,
                setProperty
            }
        })();
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyUtils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
        MyUtils.setProperty("123");
        console.log(MyUtils.Property1);

上面的代码暴露了setProperty方法,可以操作对象的内部属性值。

遵循IIFE(立即执行函数)规范很好地使用了闭包的特点,可以进行多模块的开发:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用IIFE(立即执行函数)写法</title>
</head>
<body>
    <div id="view"></div>
    <script>
        var a=1,b=2;
        var MyUtils1=(function(){
            Property1: "as";
            var add=function(a,b){
                return a+b;
            };
            var setProperty=function(str){
                this.Property1=str;
            };
            return {
                add:add,
                setProperty
            }
        })();
        var MyUtils2=(function(){
            Property1: "untils2";
            var add=function(a,b){
                return a+b;
            };
            var setProperty=function(str){
                this.Property1=str;
            };
            return {
                add:add,
                setProperty
            }
        })();
        //"继承"前面两个模块
        var MyUtils=(function(MyUtils1,MyUtils2){
            MyProperty: "MyUntils";
            function add(a,b){
                return MyUtils1.add(a,b);
            };
            function subtract(a,b){
                return MyUtils1.subtract(a,b);
            };
            return {
                add:add,
                subtract:subtract
            }
        })(MyUtils1,MyUtils2);
        const viewId=document.getElementById('view');
        const contentId=document.createElement('div');
        var result=MyUtils.add(a,b);
        contentId.innerHTML="a+b="+result;
        viewId.appendChild(contentId);
    </script>
</body>
</html>

但是最终的模块严格意义上并不是真正地继承前面的两个模块,只是依赖这两个模块的注入。

本想在这一篇把所有的演变模式总结完毕,可是后续的内容太多了并且后面的内容才是重点,写到这里文字已经有点多了,还是先写到这里。

到此这篇关于JavaScript模块化开发流程分步讲解的文章就介绍到这了,更多相关JS模块化开发内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章