前端模块化的演变过程
随着前端的发展,无模块化存在的问题日益显露。但在这个过程中,模块化也是有一定的发展。
stage1-文件划分方式
根据业务或功能封装某一类变量或者函数;
约定每一个文件都是一个模块;
当使用到这个模块的时候,通过script标签引入到html页面中,直接使用模块中的成员(变量|函数);
但当代码体积达到一定量的时候,这种方法的缺点就很明显了。
//module-a.js // module a 相关状态数据和功能函数 var name = 'module-a' function method1 () { console.log(name + '#method1') } function method2 () { console.log(name + '#method2') }
//module-b.js // module b 相关状态数据和功能函数 var name = 'module-b' function method1 () { console.log(name + '#method1') } function method2 () { console.log(name + '#method2') }
//index.html <script src="module-a.js"></script> <script src="module-b.js"></script> <script> // 命名冲突 method1() // 模块成员可以被修改 name = 'foo' </script>
缺点:
- 全部变量都暴露在全局作用域中,没有私有空间,所有成员都可被外部访问或者修改
- 当模块达到一定数量,通过约定的方式很难避免变量冲突
- 无法管理模块与模块之间的依赖关系
stage2-命名空间的方式
每个模块只暴露一个全局对象,所有的成员都挂载到这个全局对象上
//module-a.js var moduleA = { name: 'module-a', method1: function () { console.log(this.name + '#method1') }, method2: function () { console.log(this.name + '#method2') } }
//module-b.js var moduleB = { name: 'module-b', method1: function () { console.log(this.name + '#method1') }, method2: function () { console.log(this.name + '#method2') } }
//index.html <script src="module-a.js"></script> <script src="module-b.js"></script> <script> moduleA.method1() moduleB.method1() // 模块成员可以被修改 moduleA.name = 'foo' </script>
优点:
通过“命名空间”减小了命名冲突的可能
缺点:
模块内部的成员依然在外部可以被访问和修改
模块之间的依赖关系依然不明确
stage3-通过立即执行函数(IIFE)为模块提供私有空间
具体做法就是将模块内的代码放在一个立即执行函数中
若想把某个成员暴露出去,就把该成员挂载到window中
//module-a.js ;(function () { var name = 'module-a' function method1 () { console.log(name + '#method1') } function method2 () { console.log(name + '#method2') } window.moduleA = { method1: method1, method2: method2 } })()
//module-b.js ;(function ($) { var name = 'module-b' function method1 () { console.log(name + '#method1') } function method2 () { console.log(name + '#method2') } window.moduleB = { method1: method1, method2: method2 }
console.log($('#a').html()); })(jQuery)
//index.html <script src="module-a.js"></script> <script src="module-b.js"></script> <script> moduleA.method1() moduleB.method1() // 模块私有成员无法访问 console.log(moduleA.name) // => undefined </script>
优点:
实现了成员私有化,模块内的变量外界不能随意访问
通过立即执行函数,可以传递该模块所依赖的模块,使模块对外部的依赖更加清晰
小结:综上所述,随着模块化的发展,之前所提到的问题也逐一被解决,但还是存在一部分没有解决的问题。先前所有模块都是通过script标签引入的。若我们需要某个模块时,通过script标签引入了该模块,但可能会忘记引入该模块所依赖的模块。或者当我们删除某个模块时,我们也可能会忘记该模块所依赖的模块。
stage4-模块化规范的出现
1.CommonJS
特点:
1.一个文件就是一个模块
2.每个模块都有一个单独的作用域
3.每个模块暴露出去的成员都由module.exports导出
4.使用其他模块的成员时使用require函数导入
CommonJS规范主要引用在Node项目中,该规范加载模块时同步加载,倘若用在浏览器中,会有大量的同步请求导致应用比较慢,但在Node中不会有问题(因为Node是启动时加载模块)
2.AMD规范(Asynchronous Module Definition)
代表库:require.js
特点:
不同于CommonJS的同步加载,该模块可以异步加载模块,提高程序在浏览器的执行效率
但使用起来相对复杂
3.CMD规范( Common Module Definition )
代表库:Sea.js
特点:
使用上类似于require.js,写法类似于CommonJS,算是个重复的轮子
4.ES Modules(ES2015的新语法)
特点:
语言层面推出的新语法,比较完善
满足在web端开发使用模块化的需求
综上所述:目前前端模块化基本统一成了CommonJS(Node.js)和ES Modules(浏览器端开发),我们只需掌握这两种规范开发即可,后续会对着两个模块重点展开讨论