前言:
当项目功能越来越多,代码量也随之越来越大,后期维护难度会增大,此时在JS方面就会考虑使用模块化规范去管理。
一、模块化的理解
1、什么是模块?
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起。
- 块的内部数据与实现是私有的,只是向外暴露一些接口(方法)与外部其他模块通信
2、模块化的进化过程
1)全局function模式:将不同的功能封装成不同的全局函数
编码:将不同的功能封装成不同的全局函数
问题:污染全局命令空间,容易引起命令冲突或数据不全,而且模块成员之间看不出直接关系
function fun1(){
...
}
function fun2(){
...
}
2)namespace模式:简单对象封装
作用:减少全局变量,解决命令冲突
问题:数据不安全(外部可以直接修改模块内部的数据)
let myModule = {
data:'haha',
foo:function(){
console.log(`foo() ${this.data}`)
},
bar:function(){
console.log(`bar() ${this.data}`)
}
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() //foo() other data
这种写法会暴露所有的模块成员,内部状态可以被外部修改。
3)IIFE模式:匿名函数自调用(闭包)
作用:匿名是私有的,外部只能通过暴露的方法操作
编码:将数据和行为封装到一个函数内部,通过window添加属性向外暴露接口。
//index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
myModule.foo();
myModule.bar();
console.log(myModule.data); //undefined 不能访问模块内部数据
myModule.data = 'xxx'; //不是修改的模块内部的data
myModule.foo(); //没有改变
</script>
// module.js文件
(function (window, $) {
let data = 'haha';
//操作数据的函数
function foo() {
console.log(`foo() ${data}`);
$('body').css('backgrounp', 'red');
}
function bar() {
console.log(`bar ${data}`);
otherFun(); //内部调用
}
function otherFun() {
//内部私有函数
console.log('otherFun()');
}
window.myModule = { foo, bar }; //ES6写法
})(window, jQuery);
4)IIFE模式增强:引入依赖
这就是现代模块实现的基石
//module.js文件
(function (window, $) {
let data = 'haha';
//操作数据的函数
function foo() {
console.log(`foo() ${data}`);
$('body').css('backgrounp', 'red');
}
function bar() {
console.log(`bar ${data}`);
otherFun(); //内部调用
}
function otherFun() {
//内部私有函数
console.log('otherFun()');
}
window.myModule = { foo, bar }; //ES6写法
})(window, jQuery);
// index.html文件
// <!-- 引入的js必须有一定的顺序 -->
<script type="text/javascript" src="jquery-1.10.1.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
myModule.foo();
</script>
上面这个例子通过jquery方法将页面的背景颜色改成红色,所以必须先引入jQuery库,就把这个库当作参数传入。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
3、模块化的好处
- 避免命名冲突(减少命名空间污染)
- 更好的分离,按需加载
- 更高复用性
- 高可维护性
4、引入多个script后出现问题
- 请求过多
首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多。 - 依赖模糊
我们不知道他们具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。 - 难以维护
以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。
模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上问题。而这些问题可以通过模块化规范来解决,下面介绍最流行的CommonJS,AMD,ES6,CMD规范。
二、CommonJS规范
1、概述:
Node应用有模块组成,采用CommonJS模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,随其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器,模块需要提前编译打包处理。
2、特点
1)、所有代码都运行在模块作用域,不会污染全局作用域。
2)、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次缓存结果,必须清楚缓存。
3)、模块加载的顺序,按照其在代码中出现的顺序。
3、基础语法:
暴露模块:module.exports = value 或 exports.xxx = value
引入模块:require(xxx),如果是第三方模块,xxx为模块名;如果是自定义,xxx为文件路径。
此处我们有个疑问:CommonJS暴露的模块到底是什么?CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(既module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
//module就是当前example模块,exports属性x、add就是对外的接口
//example.js
var x = 1;
var add = function (val) {
return val + x;
};
module.exports.x = x;
module.exports.add = add;
上面代码通过module.exports输出变量x和函数add
var example = require('./example.js');
console.log(example.x); //1
console.log(example.add(2)); //3