前言
随着项目越来越复杂,我们写的代码也越来越多了。代码越来越难维护,全局的命名污染,依赖不明确等等。
为了解决此问题,衍生有许多模块化规范。前端模块化发展历程: 无模块化
==> CommonJS规范
==> AMD规范
==> CMD规范
==> ES6模块化
模块化是个啥?
在ES5中,只有全局作用域和函数作用域,一旦在函数体中声明变量没有添加 var
,就会造成全局污染。在ES6中有了let
,const
,这样就有了块作用域。
模块化:指解决一个复杂的问题时自顶向下把系统划分成若干模块的过程,有多种属性,分别反映其内部特性;将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起;块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;
模块化组成:数据(内部属性),操作数据的行为(内部函数)。
ES5中怎么实现模块化?
- 原始写法:无模块可言,会污染全局作用域,看不出依赖关系。
1
2
3
4
5
6function foo(){
//doSomethings
}
function bar(){
//doSomethings
} - 对象写法:对象会暴露所有模块成员,内部状态可以被外部改写。
1
2
3
4
5
6
7
8
9var myModule = new Object({
count:0,
foo:function(){
//doSomethings
},
bar:function(){
//doSomethings
}
}) - 立即执行函数(IIFE):避免暴露私有成员,数据是私有的, 外部只能通过暴露的方法操作。
1
2
3
4
5
6
7
8
9
10
11
12var myModule = (function(){
var count = 0
var increaseCount = function(){
count++
//doSomethings
},
var decreaseCount = function(){
count--
//doSomethings
}
return {increaseCount,decreaseCount} //ES6简写,ES5 ==> {increaseCount:increaseCount,...}
})() - **IIFE的增强(依赖引入)
1
2
3
4
5
6
7
8var myModule = (function($){
var _$body = $("body")
var logBody = function(){
console.log(_$body)
//doSomethings
}
return {logBody}
})(jQuery)无模块化
比如以下代码:简单粗暴引入文件即可,但是顺序不能错,因为后面的代码依赖着前面的代码。而且代码变量的命名可能冲突,可能在1
2
3
4<script src="jquery.js"></script>
<script src="bootstrap.min.js"></script>
<script src="main.js"></script>
<script src="do.js"></script>main.js
里面声明了变量foo
在do.js
文件里面又声明了,或者直接用了该变量,又或者修改了该变量。
无模块化带来的问题:
- 污染全局作用域
- 依赖关系不明显
- 维护成本高
CommonJS规范
该规范最初是用在服务器端的node的,它有四个重要的环境变量为模块化的实现提供支持:module
、exports
、require
、global
。
实际使用时,用module.exports
定义当前模块对外输出的接口(不推荐直接用exports
),用require加载模块(同步)。注意这是同步加载,在浏览器同步加载是会阻塞的,所以在浏览器不用此规范。
优点:解决了依赖、全局变量污染的问题;CommonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS不适合浏览器端模块module.exports
和 exports
的区别:module.exports导出一个对象,exports可以导出多个对象。不过 module.exports.foo == exports.foo
也就说exports.属性
会自动挂载到没有命名冲突的module.exports.属性
,但是不要给exports
赋值,一旦有了新值,它就不再绑定到module.exports;
1 | module.exports = {foo: 'bar'} //正确 |
1 | // 这是add.js文件 |
1 | // 这是main.js文件 |
AMD规范
AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS;
基本语法:
- 定义暴露模块:
define([依赖模块名], function(){return 模块对象})
; - 引入模块:
require(['模块1', '模块2', '模块3'], function(m1, m2){//使用模块对象})
- 指定引用路径:
require.config()
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写;优点:适合在浏览器环境中异步加载模块、并行加载多个模块;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//页面引入 打印I am LiSi;I am 18 years old
<script data-main="js/main.js" src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.min.js"></script>
js/logFn.js文件
define(function(){
var logName = function(name){
console.log(`I am ${name}`)
}
var logAge = age => {
console.log(`I am ${age} years old`)
}
return {logName,logAge}
})
js/main.js文件
require(['logFn'],function(logFn){
logFn.logName('LiSi')
logFn.logAge(18)
})
缺点:不能按需加载、开发成本大;
CMD规范
AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。
在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。
说白了就是AMD引入依赖要先执行依赖再跳出执行主程序,而CMD则需要的时候引入,执行完主程序再回来执行依赖;CMD是按需加载,就近原则。
1 | //页面引入 |
ES6模块化
在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的;
但是由于ES6在一些浏览器中无法执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。
基本用法:
- export 命令用于规定模块的对外接口;
- import 命令用于输入其他模块提供的功能。
export default
暴露一个对象export
暴露多个对象
es6在导出的时候有一个默认导出,export default
,使用它导出后,在import的时候,不需要加上{},模块名字可以随意起。该名字实际上就是个对象,包含导出模块里面的函数或者变量。1
2
3
4
5
6
7
8
9
10//add.js
export const add = (a,b) =>{
return a + b
}
import {add} from 'add.js'
//add.js
export default const add = (a,b) =>{
return a + b
}
import add from 'add.js'
CommonJS规范和 ES6的区别
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
Related Issues not found
Please contact @ChuanV to initialize the comment