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模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...
随机推荐
- jmeter监控服务资源
转:http://www.cnblogs.com/chengtch/p/6079262.html 1.下载需要的jmeter插件 如图上面两个是jmeter插件,可以再下面的链接中下载: https ...
- JavaWeb 例子 JDBC+JSP登陆注册留言板
注册页面: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnc ...
- Base64转换二进制文件对象 Blob/Base64转换 File 对象
function convertBase64UrlToBlob(urlData) { var arr = dataurl.split(','),//去掉url的头,并转换为byte type = ar ...
- 453. Minimum Moves to Equal Array Elements
Given anon-emptyinteger array of sizen, find the minimum number of moves required to make all array ...
- xamarin android listview的用法
listview也许是用的非常频繁的一个控件之一,下面我写一个xamarin的listview栗子,大家尝一尝xamarin android开发的乐趣.原谅我的大小写吧. listview绑定自定义的 ...
- 【二十七】php之绘图技术(gd、jpgraph、短信随机验证码)
1.绘图技术(GD库) 注意:使用该库,php.ini文件中的extension=php_gd2.dll必须是开启状态,不然无法使用 图片格式:目前网站开发常见的图片格式有gif,jpg/jpeg,p ...
- The Hungarian Abhorrence Principle
原文:http://www.butunclebob.com/ArticleS.UncleBob.TheHungarianAbhorrencePrinciple The Hungarian Abh ...
- .net 委托的使用方法以及使用委托的好处
使用方法: //无返回值无参数委托的定义方法 public delegate void NoReturnPara(); 给委托赋值的几种方式 //实例化委托,并传入方法 NoReturbNoPara ...
- 第一个Vue插件从封装到发布
前言 这是我封装的第一个Vue插件,实现的功能是滑动选择省市区,虽然只是一个简单的插件,但还是挺开心的,记录一下步骤. 插件地址:https://github.com/leichangchun/vue ...
- 房上的猫:if选择结构
一.基本if结构: 1.定义:if选择结构是根据条件判断之后再做处理的一种语法结构! 2.逻辑:首先对条件进行判断 >如果为真,则执行代码块 >如果为假,执行代码块后面的部分二.常用逻 ...