什么是模块化
什么是模块?
模块 = 数据(内部属性) + 操作数据的行为(内部函数)
- 将一个复杂的程序依据一定的规范封装成几个模块(文件),并组合在一起
- 模块的内部数据/实现是私有的
- 需要向外部暴露一些接口(方法),来与外部其他模块通信
什么是模块化?
编码时按照模块依次编码,整个项目就是一个模块化的项目。
模块化演化过程
全局 function 模式
-
编码:全局变量,将不同的功能封装成全局函数
-
问题:污染全局命名空间,容易引起命名冲突,数据不安全
-
module1.js
// 数据 let data = 'hello world'; // 操作数据的函数 function foo() { console.log(`foo() ${data}`); } function bar() { console.log(`bar() ${data}`); }
-
module2.js
let data2 = 'other data'; // 与module1模块中的函数冲突了 function foo() { console.log(`foo() ${data2}`) }
-
test1.html
<script type="text/javascript" src="module1.js"></script> <script type="text/javascript" src="module2.js"></script> <script type="text/javascript"> let data = "修改后的数据"; foo(); bar(); </script>
namespace 模式
-
编码:简单对象封装,将数据、行为封装到对象中
-
解决:命名冲突(减少了全局变量)
-
问题:数据不安全(外部可以直接修改模块内部的数据)
-
module1.js
let mymodule = { data: 'hello world 1', foo() { console.log(`foo() ${this.data}`); }, bar() { console.log(`bar() ${this.data}`); } }
-
module2.js
let myModule2 = { data: 'hello world 2', foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } }
-
test2.html
<script type="text/javascript" src="module1.js"></script> <script type="text/javascript" src="module2.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() myModule2.foo() myModule2.bar() myModule.data = 'other data' //能直接修改模块内部的数据 myModule.foo() </script>
IIFE 模式
-
IIFE : immediately-invoked function expression(立即调用函数表达式)
-
IIFE:立即调用函数表达式 → 匿名函数自调用(闭包)
-
作用: 数据是私有的, 外部只能通过暴露的方法操作
-
module3.js
(function(window) { // 数据 let data = 'hello world 3'; // 操作数据的函数 funtion foo() { // 用于暴露的函数 console.log(`foo() ${data}`); }; function bar() { // 用于暴露的函数 console.log(`bar() ${data}`); otherFun(); // 内部调用 }; function otherFun() { // 内部私有的函数 console.log('otherFun()'); } // 暴露行为 window.myModule = {foo, bar}; })(window)
-
test3.html
<script type="text/javascript" src="module3.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() //myModule.otherFun() //myModule.otherFun is not a function console.log(myModule.data) //undefined 不能访问模块内部数据 myModule.data = 'xxxx' //修改的不是模块内部的data myModule.foo() //没有改变 </script>
-
问题: 如果当前这个模块依赖另一个模块怎么办?
- IIFE 增强
IIFE 增强
-
编码:将数据和行为封装到一个函数内部,通过给window添加属性来向外暴露接口
-
引入依赖:通过函数形式来引入依赖模块
-
这就是现代模块实现的基石
-
module4.js
(function(window, module2){ // 数据 let data = 'hello world 4'; // 操作数据的函数 function foo() { // 用于暴露的函数 module2.xxx() console.log('foo()'+data) } function bar() { // 用于暴露的函数 console.log('bar()'+data) otherFun() // 内部调用 } function otherFun() { // 内部私有的函数 console.log('otherFun()') } // 暴露行为 window.module = {foo, bar}; })(window, module2)
页面加载多个 js 的问题
-
页面:
<script type="text/javascript" src="module1.js"></script> <script type="text/javascript" src="module2.js"></script> <script type="text/javascript" src="module3.js"></script> <script type="text/javascript" src="module4.js"></script>
-
说明
- 一个页面需要引入多个 js 文件
- 问题:
- 请求过多
- 依赖模糊
- 难以维护
- 这些问题可以通过现代模块化编码和项目构建来解决
模块化规范
CommonJS
-
Node.js:服务器端
-
Browserify:浏览器端,也称为 js 的打包工具
-
基本语法:
-
定义暴露模块:exports
exports.xxx = value; module.exports = value;
-
引入使用模块:require
var module = require('模块名/模块相对路径')
-
-
何时引入模块
- Node.js:运行时,动态同步引入。
- Browserify:
- 运行前,对模块进行编译/转译/打包的处理,将依赖的模块包含进来;
- 运行时,运行的时打包生成的 js,不再需要从远程引入依赖模块。
AMD
-
Asynchronous Module Definition(异步模块定义):专门用于浏览器端, 模块的加载是异步的
-
基本语法
-
定义暴露模块
define([依赖模块名],function() {return 模块对象})
-
引入使用模块
require(['模块1','模块2','模块3'],function(m1,m2) {//使用模块对象})
-
配置
require.config({ //基本路径 baseUrl : 'js/', //标识名称与路径的映射 paths : { '模块1' : 'modules/模块1', '模块2' : 'modules/模块2', 'angular' : 'libs/angular', 'angular-messages' : 'libs/angular-messages' }, //非AMD的模块 shim : { 'angular' : { exports : 'angular' }, 'angular-messages' : { exports : 'angular-messages', deps : ['angular'] } } })
-
CMD
-
Common Module Definition(通用模块定义):专门用于浏览器端, 模块的加载是异步的
-
sea.js
-
基本语法
-
定义暴露模块
define(function(require,module,exports) { 通过require引入依赖模块 通过module/exports来引入模块 exports.xxx = value })
-
引入使用模块
seajs.use(['模块1','模块2'])
-
ES6
-
ES6内置了模块化的实现
-
基本语法
-
定义暴露模块:export
-
暴露一个对象:
export default 对象;
-
暴露多个:
export var xxx = value1; export let yyy = value2; var xxx = value1; let yyy = value2; export {xxx, yyy};
-
-
引入使用模块:import
-
default 模块:
import xxx from '模块路径/模块名';
-
其他模块:
import {xxx, yyy} from '模块路径/模块名'; import * as module1 from '模块路径/模块名';
-
-
-
问题: 所有浏览器还不能直接识别ES6模块化的语法
- 解决:
- 使用Babel将ES6转换成ES5(使用了CommonJS) ----浏览器还不能直接运行
- 使用Browserify进行打包处理----浏览器可以运行
- 解决: