JavaScript 模块化历程
这是一篇关于js模块化历程的长长的流水账,记录js模块化思想的诞生与变迁,展望ES6模块化标准的未来。经历过这段历史的人或许会感到沧桑,没经历过的人也应该知道这段历史。
无模块时代
在ajax还未提出之前,js还只是一种“玩具语言”,由Brendan Eich花了不到十天时间发明,用来在网页上进行表单校验、实现简单的动画效果等等,你可以回想一下那个网页上到处有公告块飘来飘去的时代。
这个时候并没有前端工程师,服务端工程师只需在页面上随便写写js就能搞定需求。那个时候的前端代码大概像这样:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
|
if(xx){
//.......
}
else{
//xxxxxxxxxxx
}
for(var i=0; i<10; i++){
//........
}
element.onclick = function(){
//.......
}
|
代码简单的堆在一起,只要能从上往下依次执行就可以了。
模块萌芽时代
1
2
|
<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
|
顺序不能错,也不能漏写某个。在多人开发的时候很难协调。
JavaScript
1
2
3
4
5
6
7
8
9
10
11
|
modA = function(){
var a,b; //变量a、b外部不可见
return {
add : function(c){
a + b + c;
},
format: function(){
//......
}
}
}()
|
这样function内部的变量就对全局隐藏了,达到是封装的目的。但是这样还是有缺陷的,modA这个变量还是暴漏到全局了,随着模块的增多,全局变量还是会越来越多。
JavaScript
1
2
3
|
app.util.modA = xxx;
app.tools.modA = xxx;
app.tools.modA.format = xxx;
|
Yahoo的YUI早期就是这么做的,调用的时候不得不这么写:
JavaScript
1
|
app.tools.modA.format();
|
这样调用函数,写写都会觉得恶心,所以这种方式并没有被很多人采用,YUI后来也不用这种方式了。
JavaScript
1
2
3
4
5
|
(function(window){
//代码
window.jQuery = window.$ = jQuery;//通过给window添加属性而暴漏到全局
})(window);
|
jQuery的封装风格曾经被很多框架模仿,通过匿名函数包装代码,所依赖的外部变量传给这个函数,在函数内部可以使用这些依赖,然后在函数的最后把模块自身暴漏给window。
模块化面临什么问题
源自nodejs的规范CommonJs
1. 模块的标识应遵循的规则(书写规范)2. 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API3. 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖4. 如果引入模块失败,那么require函数应该报一个异常5. 模块通过变量exports来向往暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
JavaScript
1
2
3
4
5
6
7
8
|
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
|
JavaScript
1
2
3
4
5
|
//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
|
JavaScript
1
2
3
4
|
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
|
服务端向前端进军
1. 全局有一个module变量,用来定义模块2. 通过module.declare方法来定义一个模块3. module.declare方法只接收一个参数,那就是模块的factory,次factory可以是函数也可以是对象,如果是对象,那么模块输出就是此对象。4. 模块的factory函数传入三个参数:require,exports,module,用来引入其他依赖和导出本模块API5. 如果factory函数最后明确写有return数据(js函数中不写return默认返回undefined),那么return的内容即为模块的输出。
JavaScript
1
2
3
4
5
|
//可以使用exprots来对外暴漏API
module.declare(function(require, exports, module)
{
exports.foo = "bar";
});
|
JavaScript
1
2
3
4
5
|
//也可以直接return来对外暴漏数据
module.declare(function(require)
{
return { foo: "bar" };
});
|
AMD/RequireJs的崛起与妥协
1. 用全局函数define来定义模块,用法为:define(id?, dependencies?, factory);2. id为模块标识,遵从CommonJS Module Identifiers规范3. dependencies为依赖的模块数组,在factory中需传入形参与之一一对应4. 如果dependencies的值中有”require”、”exports”或”module”,则与commonjs中的实现保持一致5. 如果dependencies省略不写,则默认为[“require”, “exports”, “module”],factory中也会默认传入require,exports,module6. 如果factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx7. 如果factory为对象,则该对象即为模块的返回值
JavaScript
1
2
3
4
5
6
7
8
9
|
//a.js
define(function(){
console.log('a.js执行');
return {
hello: function(){
console.log('hello, a.js');
}
}
});
|
JavaScript
1
2
3
4
5
6
7
8
9
|
//b.js
define(function(){
console.log('b.js执行');
return {
hello: function(){
console.log('hello, b.js');
}
}
});
|
JavaScript
1
2
3
4
5
6
7
8
|
//main.js
require(['a', 'b'], function(a, b){
console.log('main.js执行');
a.hello();
$('#b').click(function(){
b.hello();
});
})
|
上面的main.js被执行的时候,会有如下的输出:
b.js执行
main.js执行
hello, a.js
1
|
hello, b.js
|
JavaScript
1
|
define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){ ..... })
|
编码过程略有不爽。
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
|
define(function(){
console.log('main2.js执行');
require(['a'], function(a){
a.hello();
});
$('#b').click(function(){
require(['b'], function(b){
b.hello();
});
});
});
|
我们在define的参数中未写明依赖,那么main2.js在执行的时候,就不会预先加载a.js和b.js,只是执行到require语句的时候才会去加载,上述代码的输出如下:
a.js执行
hello, a.js
JavaScript
1
2
3
4
5
6
7
|
var a = require('a');
a.hello();
$('#b').click(function(){
var b = require('b');
b.hello();
});
|
于是,AMD也终于决定作妥协,兼容Modules/Wrappings的写法,但只是部分兼容,例如并没有使用module.declare来定义模块,而还是用define,模块的执行时机也没有改变,依旧是预先执行。因此,AMD将此兼容称为Simplified CommonJS wrapping,即并不是完整的实现Modules/Wrappings。
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//d.js
define(function(require, exports, module){
console.log('d.js执行');
return {
helloA: function(){
var a = require('a');
a.hello();
},
run: function(){
$('#b').click(function(){
var b = require('b');
b.hello();
});
}
}
});
|
注意定义模块时候的轻微差异,dependencies数组为空,但是factory函数的形参必须手工写上require,exports,module,(这不同于之前的dependencies和factory形参全不写),这样写即可使用Simplified CommonJS wrapping风格,与commonjs的格式一致了。
JavaScript
1
2
3
|
require(['d'], function(d){
});
|
上面的代码会输出
b.js执行
d.js执行
兼容并包的CMD/seajs
JavaScript
1
2
3
4
5
6
7
8
9
|
//a.js
define(function(require, exports, module){
console.log('a.js执行');
return {
hello: function(){
console.log('hello, a.js');
}
}
});
|
JavaScript
1
2
3
4
5
6
7
8
9
|
//b.js
define(function(require, exports, module){
console.log('b.js执行');
return {
hello: function(){
console.log('hello, b.js');
}
}
});
|
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//main.js
define(function(require, exports, module){
console.log('main.js执行');
var a = require('a');
a.hello();
$('#b').click(function(){
var b = require('b');
b.hello();
});
});
|
定义模块时无需罗列依赖数组,在factory函数中需传入形参require,exports,module,然后它会调用factory函数的toString方法,对函数的内容进行正则匹配,通过匹配到的require语句来分析依赖,这样就真正实现了commonjs风格的代码。
a.js执行
hello, a.js
hello, b.js
JavaScript
1
2
|
var b = require.async('b');
b.hello();
|
b.js就不会在一开始的时候就加载了。这个API可以说是简单漂亮。
面向未来的ES6模块标准
JavaScript
1
2
3
4
|
//方式一, a.js
export var a = 1;
export var obj = {name: 'abc', age: 20};
export function run(){....}
|
JavaScript
1
2
3
4
5
|
//方式二, b.js
var a = 1;
var obj = {name: 'abc', age: 20};
function run(){....}
export {a, obj, run}
|
使用模块的时候用import关键字,如:
JavaScript
1
2
|
import {run as go} from 'a'
run()
|
如果想要使用模块中的全部API,也可以不必把每个都列一遍,使用module关键字可以全部引入,用法:
JavaScript
1
2
3
|
module foo from 'a'
console.log(foo.obj);
a.run();
|
在花括号中指明需使用的API,并且可以用as指定别名。
JavaScript 模块化历程的更多相关文章
- 知识点【JavaScript模块化】
JavaScript模块化历程 JavaScript发展变迁大概是一下几个步骤: 工具(浏览器兼容) 组件(功能模块) 框架(功能模块组织) 应用(业务模块组织) 但是经过了长长的后天努力过程Java ...
- js模块化历程
这是一篇关于js模块化历程的长长的流水账,记录js模块化思想的诞生与变迁,展望ES6模块化标准的未来.经历过这段历史的人或许会感到沧桑,没经历过的人也应该知道这段历史. 无模块时代 在ajax还未提出 ...
- javascript模块化应用
这是一篇关于js模块化历程的长长的流水账,记录js模块化思想的诞生与变迁,展望ES6模块化标准的未来.经历过这段历史的人或许会感到沧桑,没经历过的人也应该知道这段历史. 无模块时代 在ajax还未提出 ...
- JavaScript模块化开发整理
在网上已经有很多关于模块化开发的文章了,这里还是按照自己的理解来整理一下. 随着项目文件的越来越大和需求的越来越贴近现实(我发现现在客户不如:一个领导说我要审批你们报上来的资料,系统发布以后用的还不错 ...
- 《前端之路》之 Javascript 模块化管理的来世今生
目录 第二章 - 04: Javascript 模块化管理的来世今生 一.什么是模块化开发 1-1.模块化第一阶段 1-2.封装到对象 1-3. 对象的优化 二.模块化管理的发展历程 2-1.Comm ...
- Javascript模块化编程(三):require.js的用法
Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...
- Javascript模块化编程(二):AMD规范
Javascript模块化编程(二):AMD规范 作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...
- Javascript模块化编程(一):模块的写法
Javascript模块化编程(一):模块的写法 作者: 阮一峰 原文链接:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html ...
- Javascript模块化编程(二):AMD规范(转)
这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...
随机推荐
- 开源一个上架 App Store 的相机 App
Osho 相机是我独立开发上架的一个相机 App,App Store地址:https://itunes.apple.com/cn/app/osho/id1203312279?mt=8.它支持1:1,4 ...
- Mysql 锁基础
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/53 lock与latch 在数据库中,lock与latch都可以 ...
- bzoj 4199: [Noi2015]品酒大会
Description 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品酒家"和"首席猎手&quo ...
- 自动化测试辅助工具(Selenium IDE等)
本随表目录 Selenium IDE安装和使用 FireBug安装和使用 FirePath安装和使用 Selenium IDE安装 方式一:打开Firefox-->添加组件-->搜索出 ...
- 如何更改 iOS 和安卓浏览器上的 input[type="radio"] 元素的默认样式?
Safari 上的默认样式是这样的, 背景颜色可以使用background-color改变,但中间那个点始终无法去掉. 我查了一些jQuery插件,如iCheck.js,但是那说明写得我都看不明白,根 ...
- 使用C#开发数据库应用系统 习题
错题积累 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
- 使用XML序列化实现系统配置 - 开源研究系列文章
在实际的C#软件系统开发过程中,会遇到系统配置的保存问题,以及系统存储问题.在以前的系统开发过程中,笔者使用的是INI文件配置管理的方式.到了现在,INI文件配置保存仍然是一个平常使用的方式.在博客园 ...
- Linux入门篇(一)——基本命令
这一系列的Linux入门都是本人在<鸟哥的Linux私房菜>的基础上总结的基本内容,主要是记录下自己的学习过程,也方便大家简要的了解 Linux Distribution是Ubuntu而不 ...
- ASP.NET Core学习之四 在CentOS上部署.net core
一.安装CentOs 以前在大学学过linux,但是对命令行总是有一种深深的排斥感,几年之后,还是又回来了. 1.下载 现在没法FQ,就算是FQ网速也是蜗牛一样慢,我使用阿里云的镜像站进行下载速度还是 ...
- Head First设计模式之外观模式
一.定义 外观模式提供了一个统一的接口,用来访问子系统中的一群接口.外观定义了一个高层接口,让子系统更容易使用. 外观模式不只是简化了接口,也将客户从组件的子系统中解耦. 外观和适配器可以包装许多类, ...