一、JS模块化概念
-
什么是模块?
将一个复杂的程序依据一定的规范封装程几个块(文件),并进行组合在一起。块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与外部其他模块通讯。 -
为什么要模块化?
- 代码功能间节解藕,降低代码复杂度
- 代码功能方便服用
- 方便部署
-
模块化的好处和问题
- 好处:避免命名冲突(减少命名空间污染),更好的分离, 按需加载,更高复用性,高可维护性
- 问题:页面引入加载script,产生的问题:请求过多,依赖模糊,难以维护
-
JS模块化发展历史
-
全局function模式:
- 将不同的功能封装成不同的全局函数
<script type='text/javascript' src='module1.js'></script> <script type='text/javascript' src='module2.js'></script> // module1.js function getName () {} // module2.js function getName () {} // 导致问题:每个模块定义的方法都在同一个全局作用域环境中,后面模块的方法/属性会覆盖前面模块的同名方法和属性- 问题: Global被污染了, 很容易引起命名冲突
-
namespace模式
- 将不同模块的方法/属性放在一个指定的对象下面,简单对象封装,减少了全局变量
// module1.js let myModule1 = { name: 'myModule1', foo() { console.log(`foo() ${this.name}`) } } // module2.js let myModule2 = { name: 'myModule2', foo() { console.log(`foo() ${this.name}`) } }- 问题: 本质就是对象,不安全(数据不是私有的, 外部可以直接修改)
myModule1.name = 'other name' //能直接修改模块内部的数据 -
IIFE模式: 匿名函数自调用(闭包)
- IIFE : immediately-invoked function expression(立即调用函数表达式)
- 作用: 数据是私有的, 外部只能通过暴露的方法操作
- 问题: 如果当前这个模块依赖另一个模块怎么办
(function (window) { let name = 'myModuleName' // 模块内部数据 function bar() { //用于暴露有函数 console.log(`bar() ${name}`)//操作数据的函数 otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = { bar } })(window) -
IIFE模式增强 : 引入依赖
- 现代模块实现的基石
(function (window, $) { let name = 'myModuleName' // 数据 function bar() { // 用于暴露有函数 console.log(`bar() ${name}`) $('body').css('background', 'red') otherFun() // 内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = {foo, bar} })(window, jQuery) // 注入依赖
-
二、JS模块化规范
异步模块定义(AMD)是Asynchronous Module Definition的缩写,是 RequireJS 在推广过程中对模块定义的规范化产出。
通用模块定义(CMD)是Common Module Definition的缩写,是SeaJS 在推广过程中对模块定义的规范化产出。
RequireJS 和 SeaJS 都是模块化框架的代表,AMD和CMD,是他们各自定义模块化的方式,大同小异,主要是代码风格和API不同
1. CommonJs
- Node.js就是基于CommonJs模块化规范实现的
- 规范说明
- 每个文件都可以当做一个模块
- 在服务器端:模块的加载是运行时同步加载的
- 在浏览器端:浏览器默认不支持模块规范的功能,模块需要提前编译打包处理
- 规范使用
- 暴露模块: 本质就是导出了一个名为 exports 的对象
module.exports = { xxx: value } exports.xxx = value - 引入
// 第三方模块:xxx为模块名 // 自定义模块:xxx为模块文件路径 require(xxx)
- 暴露模块: 本质就是导出了一个名为 exports 的对象
- 规范实现
- 服务器端实现
- Node.js
- 浏览器端实现
- Browserify
- 也称为commonJs的浏览器端打包工具
- 服务器端实现
2. AMD
- 规范说明
- Asynchronous Module Definition(异步模块定义)
- 专门用于浏览器端,模块的加载时异步的
- 基本用法
- 暴露模块:
// 使用没有依赖的模块 define(function() { return 模块 }) // 定义有依赖的模块 define(['module1', 'module2'], function(m1, m2) { 使用m1/m2实现一个功能的模块 return 模块 }) - 引入使用模块
// 使用有依赖的模块 require(['module1', 'module2'], function(m1, m2) { 使用m1/m2 })
- 暴露模块:
- 规范实现(浏览器端)
- Require.js
- www.requirejs.cn/
// mian.js (function () { requirejs.config({ baseUrl: 'js/', // 显示声明指定的模块 paths: { dataService: './modules/dataService', alerter: './modules/alerter', jquery: './libs/jquery-1.10.1', // 库不一样,暴路的方式不一样,有的库支持AMD格式,有的不支持。angular就不支持 // angular: './libs/angular', }, shim: { angular: { exports: 'angular' } } }) // 使用 requirejs(['alerter'], function (alerter){ // ... }) })()<!-- 引入require.js并指定js主文件的入口 --> <script data-main="js/mian.js" src="js/require.js"></script>
3. CMD
- 规范说明
- Common Module Definition(通用模块定义)
- 专门用于浏览器端,模块的加载时异步的
- 模块在使用的时候才会加载
- 基本用法
- 暴露模块:
// 定义没有依赖的模块 ./module1.js define(function(require, exports, module) { exports.xxx = value module.exports = {xxx: value} }) // 定义有依赖的模块 ./module4.js define(function(require, exports, module) { // 同步引入依赖模块 var module2 = require('./module2') // 异步引入依赖模块 var module3 = require.async('./module3', function(m3){ // m3 就是异步引入的模块module3 }) // 暴露模块 exports.xxx = value }) - 引入使用模块
// 使用有依赖的模块 ./mian.js define(function(require, exports, module) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show() })
- 暴露模块:
- 规范实现(浏览器端)
- Sea.js
- www.zhangxinxu.com/sp/seajs
<!-- 引入require.js并指定js主文件的入口 --> <script src="js/libs/sea.js"></script> <script> seajs.use("./js/modules/mian.js") </script>
4. ESModule
- 规范说明
- 依赖模块需要编译打包处理
- 语法
- 导出模块
export.xxx = valueexport default {xxx: value}
- 引入使用模块
import {xxx} from './nodule.js'import module from './nodule.js'
- 导出模块
- 规范实现(浏览器端)
- 使用Babel将ES6编译为ES5代码
- 使用Browserify编译打包js