所谓模块化,就是把系统分离成具有独立功能的方法,这样,我们需要什么功能,就可以只加载某些功能。
- 每个模块都是独立的,经过良好设计的模块会尽量撇清与外部代码的关系,以便于对其进行改进和维护
- 可以进行重复利用,而而必经常复制自己之前写过的代码
模块化主要解决两个问题:命名冲突和文件依赖
命名冲突
在一个网页中引入了多个js外部文件,但这几个js文件中如果存在相同的变量被赋予不同的值,就会产生命名冲突。
// demo1.js
var x = 1;
//demo2.js
var x = 2;
文件依赖
如果·一个js库是基于某一个js库开发的,有很多基于JQuery的插件就是这样的,那么作为依赖文件的那个js库要先被导入。
// 如果demo1.js依赖demo2.js,则引用标签的书写顺序必须是:
<script src="demo2.js"></script>
<script src="demo1.js"></script>
解决冲突,依赖
使用命令空间
let module = {
name: 'likang xie',
sayName() {
console.log(this.name);
}
}
这样写虽然简单,但会暴露模块内的所有成员,这样一来,内部状态就可能被外部改写:
// 上面的module是一个对象
module.name = 'modify';
立即执行函数 + 闭包
let module = (function() {
let privateName = 'private'; // 私有变量
let privateFn = function(){} // 私有函数
// 下面的对外暴露
return {
name: 'likang xie', // 公有变量
sayName() { // 公有函数
console.log(this.name);
}
}
})();
可见,上面这样写module内的私有部分不能被外部修改,因为这里module不是一个对象,而是一个立即执行函数,外部只能得到它return的值。
module.sayName(); // likang xie
使用立即执行函数 + javascript类
var People(function() {
var privateName = 'private'; // 私有变量
var fn = function() {}; // 私有方法
return class People { // 返回一个类
constructor() {
this.name = 'likang xie'// 公有变量
}
// 公有方法
sayName() {
console.log(this.name);
}
}
})()
javascript中的类和java不同,它没有public等作用域修饰符,它的底层还是由函数思想来设计的,但是它可以与立即执行函数结合在一起使用,从而达到私有和公有分开的效果。
People p = new People();
p.sayName(); // likang xie
CommonJS(用于Node环境)
根据CommonJs规范,每个文件都是一个模块,有自己的作用域。在一个文件里定义的变量,函数,类都是私有的,对其他文件是不可见的。
值得注意的是,CommonJS规范加载模块是同步的,也就是说只有按照顺序将某个模块加载完成后才可以执行之后的操作。
Nodejs主要用于服务器编程,模块一般是存在本地硬盘中,加载速度比较快,所以Nodejs采用CommonJS规范。
定义模块
// module.js
let name = 'likang xie';
let sayName = function() {
console.log(name);
};
module.exports = { name, sayName }
// 或者分开导出
exports.sayName = sayName;
加载模块
// 通过require引入依赖
let module = require('.module.js');
module.sayName(); // likang xie
AMD(用于游览器环境)
AMD是"Asynchronous Module Definition"的简写,也就是异步模块定义。它采用异步方式加载模块,通过define方法定义模块,require方法去加载模块。
AMD主要是基于require.js的。
定义模块
// define('模块名', 模块内容)
define(['module'], function() {
let name = 'likang xie';
function sayName() {
console.log(name);
}
return { sayName }
})
使用模块
// 异步加载,使用回调
require(['module'], function(mod) {
mod.sayName(); // likang xie
})
这种异步加载的方式其实在游览器加载外部资源时很常用。
CMD(用于游览器环境)
CMD的语法基本是AMD和CommonJS语法的混合,它主要是基于sea.js的。
define(function(require, exports, module){
// 通过require引入外部依赖
var otherModule = require('./otherModule');
// 通过exports对外提供接口
exports.myModule = function() {};
// 或者通过module.exports提供整个接口
module.exports = function() {};
})
ES6模块(用于游览器环境)
ES6提供的模块化功能主要由两个命令组成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
定义模块,输出变量
// m1.js
export default { name: 'likang xie' }
// m2.js
export let name = 'likang xie';
export let sayName = () => console.log(name);
使用模块
import people from 'm1.js';
console.log(people); // { name: 'likang xie' }
import * as people from 'm2.js';
console.log(people); // 这里people是一个module对象,{ name: 'likang xie', sayName: sayName() }
使用webpack
Webpack其实就是一个打包工具,他的思想是一切皆模块,css是模块,js是模块,图片是模块。并且提供了一些列模块加载(各种-loader)来编译模块。官方推荐使用CommonJs规范,但是也支持CMD、AMD、ES6模块。