一、模块化概述
1、概念
模块化:对于一个复杂的程序,将其按照一定的规范封装成几个文件块,每一块向外暴露一些接口, 但是块的内部数据是私有的, 块与块之间通过暴露的接口进行通信,这个过程称为模块化。
一个模块就是一个实现特定功能的文件,模块化通过把我们的复杂代码按照功能的不同,划分为不同的模块单独维护的方式。现在模块化是一种最主流的代码组织方式。
2、特点
- 代码封装,避免全局污染
- 具有唯一标识
- 暴露部分数据或者api方法供外部使用
- 模块使用方便快捷
3、模块化演变过程
(1)文件划分方式
将每个功能和相关的一些状态数据单独存放在不同的文件当中,此时一个文件就是一个独立的模块。然后将这个模块引入页面当中,直接调用模块中的成员(变量/函数),一个script标签就对应一个模块,所有模块都在全局范围内工作。
缺点:
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系
(2)对象封装
将所有模块成员封装在一个对象中,当要使用的时候就调用这个对象的属性
const suit = {
id: 'suit_1',
type: 'input',
value: '123',
getType() {
console.log(`type-${this.type}`);
return this.type;
}
getValue() {
console.log(`value-${this.value}`);
return this.value;
},
}
suit.type = 'checkbox' // 直接修改模块内部的数据
suit.getType() // 'checkbox'
缺点:
- 没有私有空间,模块成员仍然可以在外部被访问或修改
- 无法管理模块依赖关系
(3)立即执行函数
采用该方式为模块提供私有空间,将模块中每个成员都放在一个函数提供对的私有作用域中,可确保私有成员的安全。私有成员只能在模块成员内通过闭包的形式访问。
(function(){
var name='A';
function fn1(){
console.log(name+'-fn1');
}
window.A={
fn1
}
})()
(function(){
var name='B';
function fn1(){
console.log(name+'-fn1');
}
window.B={
fn1
}
})()
//
<script>
A.fn1();//A-fn1
B.fn1();//B-fn1
</script>
二、模块化规范
前端模块化规范有三种:CommonJs、AMD、CMD;
- CommonJs用在服务器端,AMD和CMD用在浏览器环境
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。提前执行(异步加载:依赖先执行)+延迟执行
- CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。延迟执行(运行到需加载,根据顺序执行)延迟执行(运行到需加载,根据顺序执行)
CommonJS
(1)CommonJS概述
- node应用由模块组成,采用common.js模块规范。每一个文件就是一个模块,拥有自己独立的作用域、变量、方法等,读其他的模块不可见
- CommonJS规范规定,module变量代表当前模块,其是一个对象,其属性exports,即module.exports,是对外的接口
- require方法用于加载模块,可以帮我们导入其他模块中的内容
(2)CommonJS特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
- 运行时同步加载
(3) commonjs实现原理
每个模块文件上有三个变量:
- module 记录当前模块信息
- require 引入模块方法
- exports 当前模块导出的属性
可以再commonjs规范下每一个js模块上直接使用他们
(function(exports,require,module,__filename,__dirname){
const setName = require('data.js')
module.exports = function set(){
return {
name: setName(),
title: 'CommonJs方法'
}
}
})
(4)CommonJS模块的加载机制
一个模块除了自己的函数作用域之外,最外层还有一个模块作用域,module代表这个模块,是一个对象,exports是module的属性,是对外暴露的接口。
require也在这个模块的上下文中,用来引入外部模块,其实就是加载其他模块的module.exports属性。
AMD规范
异步模块定义
(1)概述
由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
规范
define(id?, dependencies?, factory);
用全局函数define来定义模块
1、id
id为模块标识,遵从CommonJS Module Identifiers规范;
2、dependencies
dependencies为依赖的模块数组,在factory中需传入形参与之一一对应
如果dependencies的值中有"require"、“exports"或"module”,则与commonjs中的实现保持一致
如果dependencies省略不写,则默认为[“require”, “exports”, “module”],factory中也会默认传入require,exports,module;
3、factory
如果factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx
如果factory为对象,则该对象即为模块的返回值
具体分析AMD我们通过require.js来进行。require.js是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一;
RequireJS主要解决两个问题
- 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
- js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
RequireJs也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
第一个参数[module],是一个数组,里面的成员就是要加载的模块
第二个参数callback,则是加载成功之后的回调函数。math.add()与math模块加载不是同步的,浏览器不会发生假死。
CMD规范
CMD的全称是Common Module Definition,即通用模块定义。
实现的JavaScript库为sea.js。它和AMD的require.js很像,但加载方式不同,它是按需就近加载的,而不是在模块的开始全部加载完成。它有以下两大核心特点:
简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码。
自然直观的代码组织方式:依赖的自动加载、配置的简洁清晰,可以让我们更多地享受编码的乐趣。
在CMD规范中,一个文件就是一个模块,代码书写的格式是这样的:
define(factory);
当factory为函数时,表示模块的构造方法,执行该方法,可以得到该模块对外提供的factory接口,factory 方法在执行时,默认会传入三个参数:require、exports 和 module:
// 所有模块都通过 define 来定义
define(function(require, exports, module) {
// 通过 require 引入依赖
var a = require('./spinning');
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
});
require.js的做法是并行加载所有依赖的模块, 等完成解析后, 再开始执行其他代码, 因此执行结果只会"停顿"1次, 而Sea.js在完成整个过程时则是每次需要相应模块都需要进行加载,这期间会停顿是多次的,因此require.js从整体而言相对会比Sea.js要快一些。