1 模块化
1.1 模块化的含义
模块化是指将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起。
最早我们开发将所有的代码写在一个js文件中,随着需求越来越复杂,代码量越来越大,如果仍然把所有代码写在一个js文件中,那么代码耦合度过高不方便后期维护,也不方便程序员找到某一个功能点的代码,所有东西写在一个文件也容易污染全局环境。模块化将一个复杂的js文件按共同或类似的逻辑拆分成多个js文件,拆封的文件内部数据是私有的,只是向外部暴露一些接口(方法)与其他模块通信,不仅方便找到某一块功能点的代码,也可以达到复用的效果。
1.2 模块化的发展历史
- 最早直接在一个js文件中定义,全局环境污染,很容易命名冲突。
function foo() {
//...
}
function fun() {
//...
}
- Namespace模式,把数据放到对象里面简单封装,减少全局的变量数目,但本质上是对象,可以通过对象.属性修改数据,一点都不安全。
var obj = {
foo: function() {},
fun: function() {}
}
obj.foo();
- IIFE(立即执行函数)模式,在全局看不到函数内部数据,相对来说安全。
var Module = (function() {
var _private = "hello";
var foo = function() {
//...
}
return {
foo: foo
}
})()
Module.foo();
console.log(Module._private); // undefined 全局下获取不到函数内部数据_private
- 引入依赖,通常依赖以实参的形式注入。
var Module = (function($) {
var _$body = $("body"); // 使用依赖
var foo = function() {
//...
console.log(_$body);
}
return {
foo: foo
}
})(jQuery)
Module.foo();
2 模块化规范
在模块化之前原本只有一个js文件,只需要用一个script标签,发送一次http请求,现在将一个js文件拆分成多个,意味着需要使用多个script标签,发送多次请求。并且如果模块之间有依赖,引入加载script标签的顺序不能换。
模块化规范就是为解决这些问题,常用的模块化规范有:
- CommonJS
- AMD
- CMD
- ES6
2.1 CommonJS
Node.js的实现让js也可以成为后端开发语言,但在早先Node.js开发过程中并没有像其他后端语言一样有包引入和模块系统的机制,于是CommonJS创造了一套js模块系统的规范。
CommonJS在服务器端渲染时,模块的加载运行是同步的。如果浏览器端向服务器端发请求要一个模块,而服务器端前面还需要加载好几个模块,那么意味着浏览器端发送了请求但需要排队等待,用户体验差。还有浏览器端不认识CommonJS的require语法,所以在浏览器端使用CommonJS,模块需要提前编译打包处理。
基本语法:
- 暴露模块
module.exports = value
或exports.xxx = value
,暴露的模块是一个对象 - 引入模块
require(xxx)
,引入第三方模块xxx为模块名,引入自定义模块xxx为模块文件路径
2.1.1 基于服务器端Node.js应用
// 使用 module.exports = value 暴露一个对象
module.exports = {
msg: 'module1',
foo() {
console.log(this.msg);
}
}
// 使用 module.exports = value 暴露一个函数
module.exports = function() {
console.log('module2');
}
// 使用 exports.xxx = value 暴露
exports.foo = function() {
console.log('foo() module3');
};
exports.fun = function() {
console.log('fun() module3');
};
exports.arr = [1, 2, 1];
// 将其他模块汇集到主模块
// 引入下载的第三方模块(npm install uniq)
let uniq = require('uniq');
// 引入自定义模块
let module1 = require('./xxx/module1');
let module2 = require('./xxx/module2');
let module3 = require('./xxx/module3');
// 使用这些模块
module1.foo();
module2();
module3.foo();
module3.fun();
let result = uniq(module3.arr);
通过node命令运行app.jsnode app.js
2.1.2 基于浏览器端Browserify应用
- 下载Browserify
全局安装:npm install browserify -g
局部安装:npm install browserify --save-dev
- 打包处理app.js
browserify xxx/src/app.js -o xxx/dist/bundle.js
- index.html页面使用引入
<script type="text/javascript" src="xxx/dist/bundle.js"></script>
2.2 AMD
AMD模块化规范专门用于浏览器端,在浏览器端模块的加载是异步的,依赖库require.js(RequireJS官网下载地址)。
基本语法:
- 定义暴露模块
定义没有依赖的模块define(function() { return 模块 })
定义有依赖的模块define(['module1', 'module2'], function(m1, m2) { return 模块 })
- 引入使用模块
require(['module1', 'module2'], function(m1, m2) { 使用m1 m2 })
2.2.1 require.js应用
// 定义没有依赖的模块
define(function() {
let name = 'dataService.js';
function getName() {
return name;
}
// 暴露模块
return { getName };
});
// 定义有依赖的模块
define(['dataService'], function(dataService) {
let msg = 'alerter.js';
function showMsg() {
console.log(msg, dataService.getName());
}
// 暴露模块
return { showMsg };
});
// 将其他模块汇集到主模块
(function() {
// 配置路径
requirejs.config({
baseUrl: 'xxx/xxx',
paths: {
dataService: './xxx/dataService',
alerter: './xxx/alerter',
}
});
// 引入使用模块
requirejs(['alerter'], function(alerter) {
alerter.showMsg();
})
})()
index.html页面使用引入<script data-main="xxx/main.js" src="xxx/require.js"></script>
2.3 CMD
2.4 ES6
ES6语法目前还有浏览器不支持,开发结束还需要使用Babel将ES6编译为ES5代码,在转化过程中使用的require语法浏览器引擎不认识,依赖模块需要编译打包处理。
基本语法:
- 暴露模块
export
- 引入模块
import
2.4.1 应用
// 分别暴露
export function foo() {
console.log('foo() module1');
}
export function fun() {
console.log('fun() module1');
}
export let arr = [1, 2, 3]
// 统一暴露
function fn() {
console.log('fn() module2');
}
function fn2() {
console.log('fn2() module2');
}
export { fn, fn2 };
// 默认暴露
export default () => {}
export default {
msg: 'hello',
foo(){
console.log(this.msg);
}
}
// 引入其他模块
import $ from 'jquery'; // 引入第三方模块
import {foo, fun, arr} from './xxx/module1';
import {fn, fn2} from './xxx/module2';
import module3 from './xxx/module3';
- 安装babel-cli,babel-preset-es2015和browserify
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
- 在根目录下配置.babelrc文件
{
"presets": ["es2015"]
}
- 编译
使用Babel将ES6编译为ES5代码babel js/src -d js/lib
使用Browserify编译jsbrowserify js/lib/main.js -o js/lib/bundle.js
- index.html页面使用引入
<script type="text/javascript" src="js/lib/bundle.js"></script>