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模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...
随机推荐
- 简述ES6其他的东西
第一是修饰器是ES7的一个提案,现在Babel转码器已经支持.那么什么是修饰器呢,修饰器是对类的行为的改变,在代码编译时发生的,而不是在运行时发生的且修饰器只能用于类和类的方法.修饰器可以接受三个函数 ...
- ReactNative实现图集功能
需求描述: 图片缩放.拖动.长按保存等基础图片查看的功能: 展示每张图片文本描述: 实现效果,如图: 实现步骤 使用第三方插件:react-native-image-zoom-viewer 插件Git ...
- windows日志监控
bat脚本,主要作用,每个五分钟读取日文本件中新增内容,进行错误赛选,如果有错误信息,将错误信息用邮件发送给管理员. 其中awk和sed需要手动下载 :读取number.txt文档,获取上一次执行时文 ...
- 与apk签名有关的那些概念与命令
一.概念篇 1.消息摘要-Message Digest 消息摘要:在消息数据上,执行一个单向的hash函数,生成一个固定长度的hash值,这个Hash值就是消息摘要,也成为数字指纹. 消息摘要特点: ...
- 安全扫描工具 Netsparker
Netsparker是一款web应用安全漏洞扫描工具 Netsparter官网:https://www.netsparker.com/web-vulnerability-scanner/,与其他安全扫 ...
- js选中文字兼容性解决
function selectText(){ if(document.selection){ //ie return document.selection.createRange().text; } ...
- Nginx集群之SSL证书的WebApi微服务
目录 1 大概思路... 1 2 Nginx集群之SSL证书的WebApi微服务... 1 3 HTTP与HTTPS(SSL协议)... 1 4 Ope ...
- eclipse环境下日志打印输出
1.先将jdk配置一下 选Preferences---- 找到自己的jdk所在的位置 2.配置Tomcat window-----preferences------- 找到自己的tomcat所在位置 ...
- 前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例
使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基础与模块化的内容再使用vue-cli开发 ...
- FFmpeg AVCodec
FFmpeg编解码 FFmpeg支持绝大多数视频编解码格式,如何遍历FFmpeg编解码器? 编解码器以链表形式存储,使用av_codec_next() 函数可以获取编解码器指针,当参数为NULL时,获 ...