JS模块化
模块化的理解
- 什么是模块?
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起;
- 块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;
- 一个模块的组成
- 数据—>内部的属性;
- 操作数据的行为—>内部的函数;
- 模块化是指解决一个复杂的问题时自顶向下把系统划分成若干模块的过程,有多种属性,分别反映其内部特性;
- 模块化编码:编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目;
非模块化的问题
- 页面加载多个js的问题:
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript" src="module4.js"></script>
- 发生问题:
- 难以维护 ;
- 依赖模糊;
- 请求过多;
- 所以,这些问题可以通过现代模块化编码和项目构建来解决;
模块化的优点
- 更好地分离:避免一个页面中放置多个script标签,而只需加载一个需要的整体模块即可,这样对于HTML和JavaScript分离很有好处;
- 更好的代码组织方式:有利于后期更好的维护代码;
- 按需加载:提高使用性能,和下载速度,按需求加载需要的模块
- 避免命名冲突:JavaScript本身是没有命名空间,经常会有命名冲突,模块化就能使模块内的任何形式的命名都不会再和其他模块有冲突。
- 更好的依赖处理:使用模块化,只需要在模块内部申明好依赖的就行,增加删除都直接修改模块即可,在调用的时候也不用管该模块依赖了哪些其他模块。
模块化的发展历程
原始写法
- 只是把不同的函数简单地放在一起,就算一个模块;
function fun1(){
//...
}
function fun2(){
//...
}
//上面的函数fun1,fun2组成了一个模块,使用的时候直接调用某个函数就行了。
- 缺点:
- "污染"了全局变量,无法保证不与其他模块发生变量名冲突;
- 模块成员之间看不出直接关系。
对象写法
- 为了解决污染全局变量的问题,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
count : 0,
fun1 : function (){
//...
},
fun2 : function (){
//...
}
});
//这个里面的fun1和fun2都封装在一个赌侠宁里,可以通过对象.方法的形式进行调用;
module1.fun1();
- 优点:
- 减少了全局上的变量数目;
- 缺点:
- 本质是对象,而这个对象会暴露所有模块成员,内部状态可以被外部改写。
立即执行函数(IIFE模式)
- 避免暴露私有成员,所以使用立即执行函数(自调函数,IIFE);
- 作用: 数据是私有的, 外部只能通过暴露的方法操作
var module1 = (function(){
var count = 0;
var fun1 = function(){
//...
}
var fun2 = function(){
//...
}
//将想要暴露的内容放置到一个对象中,通过return返回到全局作用域。
return{
fun1:fun1,
fun2:fun2
}
})()
//这样的话只能在全局作用域中读到fun1和fun2,但是读不到变量count,也修改不了了。
//问题:当前这个模块依赖另一个模块怎么办?
IIFE的增强(引入依赖)
- 如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"增强模式";
- IIFE模式增强:引入依赖;
- 这就是现代模块实现的基石;
var module1 = (function (mod){
mod.fun3 = function () {
//...
};
return mod;
})(module1);
//为module1模块添加了一个新方法fun3(),然后返回新的module1模块。
//引入jquery到项目中;
var Module = (function($){
var _$body = $("body"); // we can use jQuery now!
var foo = function(){
console.log(_$body); // 特权方法
}
// Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo();
js模块化需要解决那些问题:
- 1.如何安全的包装一个模块的代码?(不污染模块外的任何代码)
- 2.如何唯一标识一个模块?
- 3.如何优雅的把模块的API暴漏出去?(不能增加全局变量)
- 4.如何方便的使用所依赖的模块?
模块化规范
- Node: 服务器端
- Browserify : 浏览器端
CommonJS:服务器端
-
概述
- Node 应用由模块组成,采用 CommonJS 模块规范。
- CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
-
特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
-
基本语法:
- 定义暴露模块 : exports
exports.xxx = value // 通过module.exports指定暴露的对象value module.exports = value
- 引入模块 : require
var module = require('模块相对路径')
-
引入模块发生在什么时候?
- Node:运行时, 动态同步引入;
- Browserify:在运行前对模块进行编译/转译/打包的处理(已经将依赖的模块包含进来了), 运行的是打包生成的js, 运行时不需要再从远程引入依赖模块;
CommonJS通用的模块规范(同步)
- Node内部提供一个Module构建函数。所有模块都是Module的实例。
- 每个模块内部,都有一个module对象,代表当前模块。
- module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
- Node为每个模块提供一个exports变量,指向module.exports。
- 如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。
- Modules/1.0规范包含内容:
- 模块的标识应遵循的规则(书写规范)
- 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为模块暴露出来的API;
- 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖;
- 如果引入模块失败,那么require函数应该报一个异常;
- 模块通过变量exports来向外暴露API,exports赋值暴露的只能是一个对象
exports = {Obj}
,暴露的API须作为此对象的属性。exports本质是引入了module.exports的对象。不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。 - 如果暴露的不是变量exports,而是module.exports。module变量代表当前模块,这个变量是一个对象,它的exports属性&#x