JS模块化进程
一、上古时代
第一步
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。Global全局污染,容易命名冲突
function foo(){
//...
}
function bar(){
//...
}
第二步 Namespace
虽然减少了Global变量的数量,但是这样的写法会暴露所有模块成员,内部状态可以被外部改写。
var name = {
foo: function(){
},
bar: function(){
}
}
// 使用
name.foo();
第三步 IIFE模式
闭包模式,通过立即执行函数写法,可以达到不暴露私有变量的目的。且外部无法访问内部的值。
var module = (function(){
var _private = 'sss';
var foo = function(){
}
return {
foo: foo
}
})();
module.foo();
module._private; // undefined
引入依赖
// 改进一些
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();
// 依赖其他
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
// 其他扩展
var MODULE = (function (my) {
// add capabilities...
return my;
}(MODULE || {}));
// 可以传入空对象
var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;
my.moduleMethod = function () {
// 方法重载
// 可通过 old_moduleMethod 调用以前的方法...
};
return my;
}(MODULE));
// 有时我们要求在扩展时调用以前已被定义的方法,这也有可能被用于覆盖已有的方法。这时,对模块的定义顺序是有要求的。
- 这就是模块模式,也就是现代模块实现的基石
二、远古时代
Script Loader
通过script标签引入一堆的依赖,按序执行。同样难以维护,依赖过多。
body
script(src="zepto.js")
script(src="jhash.js")
script(src="fastClick.js")
script(src="iScroll.js")
script(src="underscore.js")
script(src="handlebar.js")
script(src="datacenter.js")
script(src="deferred.js")
script(src="util/wxbridge.js")
script(src="util/login.js")
script(src="util/base.js")
script(src="util/city.js")
script(src="util/date.js")
script(src="util/cookie.js")
script(src="app.js")
这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
三、中古时代
CommonJS 征服世界的第一步是跳出浏览器。
CommonJS规范的提出,主要时为了弥补js没有标准的缺陷,使得通过CommonJS Api编写的应用可以在不同的宿主环境中执行。
因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。因此Node就在Commonjs的基础上应运而生了。
【NodeJS
是CommonJS
规范的实现,webpack
也是以CommonJS
的形式来书写】
三、近代奇兵
AMD/CMD 浏览器环境模块化方案
AMD(Asynchronous Module Definition:异步模块定义)
是 RequireJS
在推广过程中对模块定义的规范化产出。
CMD(Common Module Definition:公共模块定义)
是 SeaJS
在推广过程中对模块定义的规范化产出。
1. AMD
有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。
但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。下面的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?
var math = require('math');
math.add(2, 3);
第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
因此,在浏览器就需要"异步加载"(asynchronous)。这就是AMD
规范诞生的背景。
目前,主要有两个Javascript库实现了AMD规范:require.js
和curl.js
。
Require.JS
Require.JS 的基本功能"模块化加载"。
实例1
// math1.js
/*
语法结构:
1. define({函数方法})
*/
// 一个没有依赖性的模块可以直接定义对象
define({
name: '测试',
add: function(num1, num2) {
return num1+num2;
}
})
// test1.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./lib/require.js" charset="utf-8"></script>
<script type="text/javascript">
require(['math1'], function(math) {
console.log(math.name);
console.log(math.add(1,3));
})
</script>
</body>
</html>
实例二
// math2.js
/*
语法结构:
2. define([引入其他模块地址],回调函数(引入模块别名));
别名可以在函数里面去调用其他模块提供的方法
*/
// 一个返回对象的匿名模块
define(['math1'], function(math) {
var sub = function(num1,num2) {
return num1 - num2;
}
return {
add: math.add,
sub: sub
}
})
// test2.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./lib/require.js" charset="utf-8"></script>
<script type="text/javascript">
require(['math2'], function(math) {
console.log(math.sub(4,2));
})
</script>
</body>
</html>
实例三
// math3.js
// 定义一个命名模块
define('mymath', ['math2'], function(math) {
var multiplication = function(num1, num2) {
return num1 * num2;
}
return {
add: math.add,
sub: math.sub,
mult: multiplication
}
})
// test3.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./lib/require.js" charset="utf-8"></script>
<script type="text/javascript">
//需要配置一下引入的模块的地址
require.config({
paths:{
'mymath': 'math3'
}
});
require(['mymath'], function(math){
console.log(math.mult(3,5));
})
</script>
</body>
</html>
下图有一些注意事项,但是截图不是本实例的截图
实例四
// math4.js
// 一个使用了简单CommonJS转换的模块定义
define(function(require,exports,module){
// 引入其他模块
var math = require('js/1_math');
console.log(math);
// 导出(暴露方法:2种方式)
// 第一种
// exports.a = math.add;
// 第二种
module.exports = {
a : math.add
}
});
// test4.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript">
require(['js/4_math'],function(mytool){
console.log(mytool.a(11,22));//33
});
</script>
</html>
一个小demo
- 目录结构
- 代码
// index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>My Sample Project</h1>
<script src="./script/require.js" data-main="./script/main" charset="utf-8"></script>
</body>
</html>
// main.js
require.config({
baseUrl: 'script',
paths: {
math: './lib/math1',
utils: './utils/utils',
jquery: './lib/jquery.min'
}
});
require(['math','utils','jquery'], function(math, utils, $) {
$(function(){
console.log(math.add(1,2));
console.log(utils.utils.sub(4,2));
alert('在加载完math、utils、jquery之后,我执行了');
})
})
//math.js
define({
add:function(num1, num2) {
return num1+num2;
}
});
// utils.js
define(function(require, exports, module) {
var utils = {
sub:function(num1, num2) {
return num1-num2;
}
};
module.exports = {
utils: utils
}
})
2.CMD
SeaJS 是一个适用于 Web 浏览器端的模块加载器。这里就先不写了~
大家可以参考文档学习
四、展望未来
面向未来的ES6模块标准
2015年6月,ECMAScript2015也就是ES6发布了,JavaScript终于在语言标准的层面上,实现了模块功能,使得在编译时就能确定模块的依赖关系,以及其输入和输出的变量,不像 CommonJS、AMD之类的需要在运行时才能确定(例如FIS这样的工具只能预处理依赖关系,本质上还是运行时解析),成为浏览器和服务器通用的模块解决方案。
更多关于ES6 Modules的资料,可以看一下ES6阮一峰
感谢您的支持!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。