1. 概念
什么是模块化?
将一个复杂的程序依据一定的规范封装成几个块(文件),并进行组合 在一起
块的内部数据/实现 是私有的,只是向外暴露了一些接口(方法)与外部其他模块进行通信
为什么要模块化?
降低复杂度,提高解耦性,
模块化的好处?
避免命名冲突
更好地分离,按需加载
更高的复用性
更好的可维护性
2. 模块化模式进化史
2.1 全局function模式
将不同的功能封装成不同的函数,这样全局变量容易被污染,可能产生命名冲突
// 全局函数模式定义
// module1.js
let msg = 'module1'
function foo(){
console.log('foo',msg)
}
function bar(){
console.log('bar',msg)
}
// 全局模式使用
// 另一个引用了module1.js的html文件或者js文件
foo();
bar();
// 下面会造成打印值不一样
msg = 'msg';
foo();
2.2 namespace模式
简单对象封装,只是减少了全局变量的数量,本质上是对象,还是能进行修改,不安全
// namespace 命名空间模式
// module2.js
let obj = {
msg:'module2',
foo(){
console.log('foo',this.msg)
}
};
// namespace使用
// 另一个引用了module2.js的html文件或者js文件
obj.foo();
// 下面会造成打印值变化
obj.msg = 'NBA';
obj.foo();
2.3 IIFE模式
匿名函数自调用(闭包)
// IIFE模式:匿名函数自调用
// module3.js
(function(window){
let msg = 'module3'
function foo(){
console.log('foo',msg)
}
//下方如果不写是不能访问的
window.module3 = {
foo
}
})(window)
// IIFE模式使用
// 另一个引用了module3.js的html文件或者js文件
module3.foo();
2.4 IIFE增强模式:引入依赖
这是现代模块实现的基石
// IIFE模式增强:引入依赖
// module4.js
(function(window,$){ // 这里是形参
let msg = 'module4'
function foo(){
console.log('foo',msg)
}
window.module4 = foo;
$('body').css('background','red')
})(window,jQuery)// 传入window和jQuery
// IIFE模式增强使用
// 另一个引用了module4.js的html文件或者js文件
// 在引用modules4.js之前引入jQuery
module4();
哪怕上述方案是比较好的了,但是也存在一些问题
一个页面需要引入多个js文件,请求过多、依赖模糊、难以维护
通过现代模块化编码和项目构建来解决以上问题
3. 模块化规范
3.1 CommonJs
node是使用CommonJs
特点:
每个js文件都可以当作一个模块
在服务器端,模块的加载是运行时同步加载的(有可能会堵塞)
在浏览器端,模块需要提前编译打包处理(有可能会出现页面空白)
基本语法:
(1)定义暴露模块
/**
* 使用module.exports = value 定义和暴露
*/
module.exports = {
m1() {
console.log('moudle1 m1()')
}
}
module.exports = function (){
console.log('module2 m2()');
}
/**
* 使用export.xxx = value 定义和暴露
*/
exports.m3 = function (){
console.log('module3 m3()');
}
(2) 引入模块
/**
* 使用require(xxx) xxx是路径 引入模块
*/
let module1 = require('./module/module1');
let module2 = require('./module/module2');
let module3 = require('./module/module3');
module1.m1();
module2();
module3.m3()
(3)实现:
在服务器端使用node.js实现
直接使用node main.js 可得到结果
在浏览器端使用Browserify进行编译和打包,最后页面引用的是打包之后的js文件
1.需要下载Browserify
npm install browserify -g
2.打包
browserify main.js -o bundle.js
3.在html中引用打包好的bundle.js文件
3.2 AMD
异步模块定义
专门用于浏览器端,模块的加载是异步的
AMD需要配置和使用requirejs
下载require.js的地址:www.requirejs-cn.cn/docs/downlo…
(1)定义暴露模块
//定义没有依赖的模块:
define(function(){
let msg = 'module1';
function getMsg(){
return msg;
}
return {getMsg};
})
// 定义有依赖的模块:
define(['module1','jquery'],function(m1,m2){
let msg = 'module2';
function getMsg(){
m2('body').css('background', 'blue')
console.log('m1:'+ m1.getMsg());
console.log('m2:'+ msg);
}
return {getMsg}
})
(2)引入使用模块
(function () {
//配置
requirejs.config({
//基本路径
baseUrl: "./",
//模块标识名与模块路径映射
paths: {
'jquery': 'lib/jquery-1.10.1',
"module1": "module/module1",
"module2": "module/module2",
}
})
//引入使用模块
requirejs( ['module2'], function(module2) {
module2.getMsg()
})
})()
(3)实现
在index.html中引用require.js和main.js
<!--
require.js 在加载的时候会检察data-main 属性
data-main指向的脚本中设置模板加载 选项,然后加载第一个应用模块
-->
<script data-main="./main" src="./lib/require.js"></script>
3.3 CMD
通用模块定义
专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行
实现需要使用seajs
下载sea.js地址:seajs.github.io/seajs/docs/…
(1)定义暴露模块
//定义没有依赖的模块:
define(function (require, exports, module) {
//内部变量数据
let data = 'module1 data'
//内部函数
function show() {
console.log('module1 show() and the module1 data is ' + data)
}
//向外暴露
exports.show = show
})
define(function (require, exports, module) {
module.exports = {
data: 'module2 data'
}
})
// 定义有依赖的模块:
define(function (require, exports, module) {
//引入依赖模块(同步)
let module2 = require('./module2')
function show() {
console.log('module3 show() and the module2 data is ' + module2.data)
}
exports.show = show
//引入依赖模块(异步)
require.async('./module1', function (m1) {
console.log('异步引入依赖模块1 and the module1 show is ' + m1.show)
})
})
(2)引入使用模块
// 引入使用模块
define(function (require) {
let m1 = require('./module/module1');
let m3 = require('./module/module3');
m1.show();
console.log('-----------------');
m3.show();
});
(3)实现
在index.html中引入sea.js,使用seajs.use()
<!--
使用seajs:
1. 引入sea.js库
2. 如何定义导出模块 :
define()
exports
module.exports
3. 如何依赖模块:
require()
4. 如何使用模块:
seajs.use()
-->
<script type="text/javascript" src="lib/sea.js"></script>
<script type="text/javascript">
seajs.use()
</script>
3.4 ES6
依赖模块需要编译打包处理
ES6模块化需要使用babel-cli, babel-preset-es2015和browserify
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
定义.babelrc文件,将ES6转成ES5
{
"presets": ["es2015"]
}
(1) 导出模块export
// 分别暴露
export function foo() {
console.log('module1 foo()');
}
export let bar = function () {
console.log('module1 bar()');
}
export const DATA_ARR = [1, 3, 5, 1]
// 统一暴露
let data = 'module2 data'
function fun1() {
console.log('module2 fun1() ' + data);
}
function fun2() {
console.log('module2 fun2() ' + data);
}
export {fun1, fun2}
// 默认暴露
export default {
name: 'Tom',
setName: function (name) {
this.name = name
}
}
(2) 引入模块import
// 引入模块
import {foo, bar} from './module1'
import {DATA_ARR} from './module1'
import {fun1, fun2} from './module2'
import person from './module3'
// 使用引入模块内变量
foo()
bar()
console.log(DATA_ARR);
fun1()
fun2()
person.setName('JACK')
console.log(person.name);
// 接口改名
// export { foo as myFoo } from 'my_module';
// 整体输出
// export * from 'my_module';
(3) 编译、打包js
使用Babel将ES6编译为ES5代码(但包含CommonJS语法)
babel ./module -d ./build
./module是ES6的js文件目录,./build 是babel转换的ES5的js文件目录
使用Browserify编译js
browserify ./build/main.js -o ./dist/bundle.js
页面引入并测试bundle.js
<!--
index.html文件引入最后打包好的bundle.js文件
控制台显示结果:
module1 foo()
module1 bar()
Array(4)0: 11: 32: 53: 1length: 4[[Prototype]]: Array(0)
module2 fun1() module2 data
module2 fun2() module2 data
JACK
-->
<script type="text/javascript" src="./dist/bundle.js"></script>
import()
1)按需加载。
import()可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
(2)条件加载
import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。
(3)动态的模块路径
import()允许模块路径动态生成。import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数
import(f())
.then(({export1, export2}) => {
// ...·
});
上面代码中,根据函数f的返回结果,加载不同的模块。
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的
require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段