目录
概述
什么是模块化开发
- 将软件产品看作为一系列功能模块的组合
- 通过特定的方式实现软件所需模块的划分、管理、加载
非模块化开发的问题
•命名冲突
添加命名空间YUI 、EXTJS---可以从一定程度上解决命名冲突,但是增加了开发人员记忆冗长api的难度。
•文件依赖
团队变大后,维护大量的文件依赖关系非常困难,公共模块的维护、升级很不方便。
为什么使用模块化开发
- https://github.com/seajs/seajs/issues/547
- 协同
- 代码复用
- 解决问题
- 大量的文件引入
- 命名冲突
- 文件依赖
- 存在
- 顺序
实现模块化的推演
step-01 全局函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>全局函数的形式</title>
<script>
// calc.js
// 计算模块
// 成员一:加法运算
function add(a, b) {
return parseFloat(a) + parseFloat(b);
}
function subtract(a, b) {
return parseFloat(a) - parseFloat(b);
}
function multiply(a, b) {
return parseFloat(a) * parseFloat(b);
}
function divide(a, b) {
return parseFloat(a) / parseFloat(b);
}
// 早期的开发过程中就是将重复使用的代码封装到函数中
// 再将一系列的函数放到一个文件中,称之为模块
// 约定的形式定义的模块,存在命名冲突,可维护性也不高的问题
// 仅仅从代码角度来说:没有任何模块的概念
// ==========================
window.onload = function() {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = add(ta.value, tb.value);
break;
case '-':
tres.value = subtract(ta.value, tb.value);
break;
case 'x':
tres.value = multiply(ta.value, tb.value);
break;
case '÷':
tres.value = divide(ta.value, tb.value);
break;
}
};
};
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
<!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>
</html>
step-02 封装对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>封装函数的方式</title>
<script>
// calc.js
// 计算模块
// var calculator = {
// // 成员一:加法运算
// add: function(a, b) {
// return parseFloat(a) + parseFloat(b);
// },
// subtract: function(a, b) {
// return parseFloat(a) - parseFloat(b);
// },
// multiply: function(a, b) {
// return parseFloat(a) * parseFloat(b);
// },
// divide: function(a, b) {
// return parseFloat(a) / parseFloat(b);
// }
// };
//
var math = {};
math.calculator = {};
math.calculator.add = function(a, b) {
return a + b;
};
math.convertor = {};
// math.calculator.add();
// 传统编程语言中的命名空间的概念
// 从代码层面就已经有了模块的感觉
// 还是污染全局(不是问题)
// 高内聚,低耦合
// 模块内部相关性强,模块之间没有过多相互牵连
// 没有私有空间,
//
// ==========================
window.onload = function() {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = calculator.add(ta.value, tb.value);
break;
case '-':
tres.value = calculator.subtract(ta.value, tb.value);
break;
case 'x':
tres.value = calculator.multiply(ta.value, tb.value);
break;
case '÷':
tres.value = calculator.divide(ta.value, tb.value);
break;
}
};
};
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
<!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>
</html>
step-03 划分私有空间
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>封装函数的方式</title>
<script>
// calc.js
// 计算模块
var calculator = (function() {
// 这里形成一个单独的私有的空间
// var name = '';
// 私有成员的作用,
// 将一个成员私有化,
// 抽象公共方法(其他成员中都会用到的)
// 私有的转换逻辑
function convert(input){
return parseInt(input);
}
function add(a, b) {
return convert(a) + convert(b);
}
function subtract(a, b) {
return convert(a) - convert(b);
}
function multiply(a, b) {
return convert(a) * convert(b);
}
function divide(a, b) {
return convert(a) / convert(b);
}
return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
}
})();
// ==========================
window.onload = function() {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = calculator.add(ta.value, tb.value);
break;
case '-':
tres.value = calculator.subtract(ta.value, tb.value);
break;
case 'x':
tres.value = calculator.multiply(ta.value, tb.value);
break;
case '÷':
tres.value = calculator.divide(ta.value, tb.value);
break;
}
};
};
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
<!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>
</html>
step-04 模块的扩展与维护
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>封装函数的方式</title>
<script>
// calc_v2015.js
// 计算模块
(function(calculator) {
function convert(input) {
return parseInt(input);
}
calculator.add = function(a, b) {
return convert(a) + convert(b);
}
window.calculator = calculator;
})(window.calculator || {});
// 新增需求
// calc_v2016.js
(function(calculator) {
function convert(input) {
return parseInt(input);
}
// calculator 如果存在的话,我就是扩展,不存在我就是新加
calculator.remain = function(a, b) {
return convert(a) % convert(b);
}
window.calculator = calculator;
})(window.calculator || {});
// 开闭原则,对新增开放,对修改关闭;
//
// http://api.douban.com/v2/movie
// http://api.douban.com/movie
// ==========================
window.onload = function() {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = calculator.add(ta.value, tb.value);
break;
case '-':
tres.value = calculator.subtract(ta.value, tb.value);
break;
case 'x':
tres.value = calculator.multiply(ta.value, tb.value);
break;
case '÷':
tres.value = calculator.divide(ta.value, tb.value);
break;
case '%':
tres.value = calculator.remain(ta.value, tb.value);
break;
}
};
};
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
<option value="%">%</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
<!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>
</html>
step-05 第三方依赖管理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>封装函数的方式</title>
<script src="jquery.js"></script>
<script>
// calc_v2015.js
// 计算模块
(function(calculator) {
// 对全局产生依赖,不能这样用
console.log($);
function convert(input) {
return parseInt(input);
}
calculator.add = function(a, b) {
return convert(a) + convert(b);
}
window.calculator = calculator;
})(window.calculator || {});
// 新增需求
// calc_v2016.js
(function(calculator, $) {
// 依赖函数的参数,是属于模块内部
// console.log($);
// $().
function convert(input) {
return parseInt(input);
}
// calculator 如果存在的话,我就是扩展,不存在我就是新加
calculator.remain = function(a, b) {
return convert(a) % convert(b);
}
window.calculator = calculator;
})(window.calculator || {}, jQuery);
// 开闭原则,对新增开放,对修改关闭;
//
// http://api.douban.com/v2/movie
// http://api.douban.com/movie
// ==========================
window.onload = function() {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = calculator.add(ta.value, tb.value);
break;
case '-':
tres.value = calculator.subtract(ta.value, tb.value);
break;
case 'x':
tres.value = calculator.multiply(ta.value, tb.value);
break;
case '÷':
tres.value = calculator.divide(ta.value, tb.value);
break;
case '%':
tres.value = calculator.remain(ta.value, tb.value);
break;
}
};
};
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
<option value="%">%</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
<!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>
</html>
在什么场景下使用模块化开发 业务复杂 重用逻辑非常多 扩展性要求较高
实现规范
CommonJS规范
AMD规范
CMD规范
•服务器端规范
CommonJS---nodejs
•浏览器端规范
AMD---RequireJS 国外相对流行
CMD---SeaJS 国内相对流行
CMD实现-SeaJS
SeaJS---阿里巴巴前端架构师玉伯
js文件的依赖管理、异步加载,方便前端的模块化开发。
官方网站:http://seajs.org/
AMD实现-RequireJS
RequireJS-James Burke AMD规范的创始人
与SeaJS 基本实现类似的功能
中文官网:http://www.requirejs.cn/
SeaJS和RequireJS对比
- 对于依赖的模块,AMD是提前执行,CMD是延后执行
- CMD推崇依赖就近,AMD推崇依赖前置
- AMD的API默认是一个当多个用,CMD的API严格区分,推崇职责单一
实现Seajs
使用步骤
- 在页面中引入sea.js文件
- 定义一个主模块文件,比如:main.js
- 在主模块文件中通过define的方式定义一个模块,并导出公共成员
- 在页面的行内脚本中通过seajs.use('path',fn)的方式使用模块
- 回调函数的参数传过来的就是模块中导出的成员对象
定义一个模块
- define
javascript define(function(require, exports, module) { exports.add = function(a, b) { return a + b; }; });
使用一个模块
- seajs.use
- 一般用于入口模块
- 一般只会使用一次
- require
- 模块与模块之间
<!-- 01-helloworld.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Seajs体验</title>
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
// 在Seajs中模块的引入需要相对路径完整写法
seajs.use('./01-calculator.js', function(calculator) {
var ta = document.getElementById('txt_a');
var tb = document.getElementById('txt_b');
var tres = document.getElementById('txt_res');
var btn = document.getElementById('btn');
var op = document.getElementById('sel_op');
btn.onclick = function() {
switch (op.value) {
case '+':
tres.value = calculator.add(ta.value, tb.value);
break;
case '-':
tres.value = calculator.subtract(ta.value, tb.value);
break;
case 'x':
tres.value = calculator.multiply(ta.value, tb.value);
break;
case '÷':
tres.value = calculator.divide(ta.value, tb.value);
break;
}
};
});
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
</body>
</html>
<!-- 01-calculator.js -->
// 定义一个模块,遵循Seajs的写法
define(function(require, exports, module) {
// 此处是模块的私有空间
// 定义模块的私有成员
// 载入01-convertor模块
var convertor = require('./01-convertor.js');
function add(a, b) {
return convertor.convertToNumber(a) + convertor.convertToNumber(b);
}
function subtract(a, b) {
return convertor.convertToNumber(a) - convertor.convertToNumber(b);
}
function multiply(a, b) {
return convertor.convertToNumber(a) * convertor.convertToNumber(b);
}
function divide(a, b) {
return convertor.convertToNumber(a) / convertor.convertToNumber(b);
}
// 暴露模块的公共成员
exports.add = add;
exports.subtract = subtract;
exports.multiply = multiply;
exports.divide = divide;
});
<!-- 01-convertor.js -->
/**
* 转换模块,导出成员:convertToNumber
*/
define(function(require, exports, module) {
// 公开一些转换逻辑
exports.convertToNumber = function(input) {
return parseFloat(input);
}
});
导出成员的方式
- module.exports
- exports.xxx
- return
- 三种方式的优先级
<!-- 02-export.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Seajs体验</title>
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
// 在Seajs中模块的引入需要相对路径完整写法
seajs.use('./02-calculator.js', function(e) {
// var p = new Person();
console.log(e);
});
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
</body>
</html>
<!-- 02-calculator.js -->
// 定义一个模块,遵循Seajs的写法
define(function(require, exports, module) {
// function add(a, b) {
// return parseFloat(a) + parseFloat(b);
// }
// function subtract(a, b) {
// return parseFloat(a) - parseFloat(b);
// }
// function multiply(a, b) {
// return parseFloat(a) * parseFloat(b);
// }
// function divide(a, b) {
// return parseFloat(a) / parseFloat(b);
// }
// // 暴露模块的公共成员
// exports.add = add;
// exports.subtract = subtract;
// exports.multiply = multiply;
// exports.divide = divide;
// console.log(module.exports === exports);
//
// function Person(name, age, gender) {
// this.name = name;
// this.age = age;
// this.gender = gender;
// }
// Person.prototype.sayHi = function() {
// console.log('hi! I\'m a Coder, my name is ' + this.name);
// };
// // exports.Person = Person;
// module.exports = Person;
// 最终导出的以 module.exports
// module.exports = { name: 'world' };
// // 此时module.exports 指向的是一个新的地址
// exports.name = 'hello';
// // exports是module.exports的快捷方式,指向的任然是原本的地址
// module.exports优先级第二
module.exports = { name: 'hello' };
console.log(module);
// return 的优先级最高
return { name: 'world' };
});
异步加载模块
- 默认require的效果是同步的,会阻塞代码的执行,造成界面卡顿
- require.async();
require.async('path',function(module) {
});
<!-- 03-async-require.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Seajs体验</title>
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
// 在Seajs中模块的引入需要相对路径完整写法
seajs.use('./03-module1.js', function(e) {
// var p = new Person();
console.log(e);
});
</script>
</head>
<body>
<input type="text" id="txt_a">
<select id="sel_op">
<option value="+">+</option>
<option value="-">-</option>
<option value="x">x</option>
<option value="÷">÷</option>
</select>
<input type="text" id="txt_b">
<input type="button" id="btn" value=" = ">
<input type="text" id="txt_res">
</body>
</html>
<!-- 03-module1.js -->
define(function(require, exports, module) {
// console.log('module1 ---- start');
// // require 必须执行完成过后(./module2.js加载完成)才可以拿到返回值
// var module2 = require('./03-module2.js'); // 阻塞代码执行
// // JS中的阻塞会有卡顿的情况出现
// console.log('module1 ---- end');
//
console.log('module1 ---- start');
require.async('./03-module2.js', function(module2) {
}); // 此处不会阻塞代码执行
console.log('module1 ---- end');
});
<!-- 03-module2.js -->
define(function() {
console.log('module2 exec');
});
使用第三方依赖(jQuery)
- 由于CMD是国产货,jquery默认不支持
- 改造
javascript // 适配CMD if (typeof define === "function" && !define.amd) { // 当前有define函数,并且不是AMD的情况 // jquery在新版本中如果使用AMD或CMD方式,不会去往全局挂载jquery对象 define(function() { return jQuery.noConflict(true); }); }
<!-- 04-dependences.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Seajs使用第三方依赖</title>
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
seajs.use('./04-main.js');
</script>
</head>
<body>
</body>
</html>
<!-- 04-main.js -->
'use strict';
define(function(require, exports, module) {
// 想用jquery怎么办
var $ = require('./jquery.js');
console.log($);
$(document.body).css('backgroundColor', 'red');
});
Seajs配置
- 配置
- seajs.config
- base
- alias
<!-- 05-config.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
seajs.config({
alias: {
// 变化点封装
calc: './modules/calc.js',
}
});
seajs.use('calc');
</script>
</head>
<body>
</body>
</html>
<!-- calc.js -->
'use strict';
define(function(require, exports, module) {
console.log(11111);
});
使用案例
- Tab标签页
<!-- 06-pagination.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用模块化的方式定义一个分页组件</title>
<link rel="stylesheet" href="../bootstrap.min.css">
<script src="node_modules/seajs/dist/sea.js"></script>
<script>
window.onload = function() {
seajs.use('./06-main.js');
};
</script>
</head>
<body>
<ul class="pagination"></ul>
<ul class="pagination1"></ul>
</body>
</html>
<!-- 06-main.js -->
define(function(require, exports, module) {
var Pagination = require('./modules/pagination.js');
var pager = new Pagination(1, 20, 7);
pager.render('.pagination');
pager.render('.pagination1');
});
<!-- pagination.js -->
define(function(require, exports, module) {
// format = 'http://www.baidu.com/page/@/'
function Pagination(current, total, show, format) {
this.current = current;
this.total = total;
this.show = show;
// 1. 根据显示数量算出正常情况当前页的左右各有几个
var region = Math.floor(show / 2);
// 2. 计算出当前界面上的起始值
var begin = current - region; // 可能小于 1
begin = begin < 1 ? 1 : begin;
var end = begin + show; // end必须小于total
if (end > total) {
end = total + 1;
begin = end - show;
begin = begin < 1 ? 1 : begin;
}
this.begin = begin;
this.end = end;
};
/**
* 渲染当前这个组件到界面上
*/
Pagination.prototype.render = function(containers) {
// 获取分页展示容器
// p.render('.pgfds');
if (typeof containers === 'string') {
containers = document.querySelectorAll(containers);
}
if (containers.length === undefined) {
// dom对象
containers = [containers];
}
for (var c = 0; c < containers.length; c++) {
var container = containers[c];
// 先append上一页
var prevElement = document.createElement('li');
prevElement.innerHTML = '<a href="?page=' + (this.current - 1) + '" aria-label="Previous"><span aria-hidden="true">«</span></a>';
if (this.current == 1) {
prevElement.classList.add('disabled');
}
container.appendChild(prevElement);
for (var i = this.begin; i < this.end; i++) {
var liElement = document.createElement('li');
liElement.innerHTML = '<a href="?page=' + i + '">' + i + '</a>';
if (i == this.current) {
// 此时是当前页
liElement.classList.add('active');
}
container.appendChild(liElement);
}
var nextElement = document.createElement('li');
nextElement.innerHTML = '<a href="#" aria-label="Next"><span aria-hidden="true">»</span></a>';
if (this.current == this.total) {
nextElement.classList.add('disabled');
}
container.appendChild(nextElement);
}
};
module.exports = Pagination;
});