JS模块化不是一蹴而就的,而是经历了长长的历史变迁。了解JS模块化思想的诞生与变迁历程是深入理解现代前端项目工程化的基础,只有在了解历史的基础上,我们才能更好地展望未来。相信你和我一样,已经迫不及待要了解这段神秘的发展史了,让我们一起开始这段神奇的旅程。
在ajax还未提出之前,JS主要用来进行一些简单的表单校验、只能实现非常简单的一些动画效果等等。因此,这是并没有前端工程师这个概念,因此并没有很复杂的业务逻辑需要处理,后端工程师基层就可以搞定这些简单的需求。那个年代的前端代码大概如下所示:
if (xx条件) {
// do something
} else {
// do another
}
for (var i = 0; i < arr.length; i++) {
// do something
}
element.onclick = function () {}
简简单单的代码逻辑。只需要按照顺序,从上至下依次执行即可。
2006年,ajax的概念被提出,前端拥有了主动向服务端发送请求并操作返回数据的能力,由于Google公司将此概念发扬光大,传统的网页功能已不能满足日常的需求,前端的业务逻辑越来越多,业务场景越来越复杂,因此代码量爆炸式增长,由此一系列问题逐渐暴露出来,亟待解决。
主要面临如下问题:
假如现在项目依赖三个模块, module_a.js, modulle_b.js, index.js
index.html代码如下:
<html>
<head></head>
<body>
<script src="./module_a.js"></script>
<script src="./module_b.js"></script>
<script src="./index.js"></script>
</body>
</html>
module_a.js代码如下:
var a = [1, 2, 3]
module_b.js代码如下:
var b = [4, 5, 6]
index.js 代码如下:
var c = a.concat(b)
如下我们现在要在index.js中定义一个a变量,那么就会造成a变量全局污染(因为这些模块中的变量或者方法都是暴露于全局的)
那么萌芽阶段是如何解决这个问题的呢
moduleA = function(){
var a,b; //变量a、b外部不可见
return {
add : function(){
return a + b;
}
}
}()
优点:function内部的变量对全局隐藏了,达到了封装的目的。
缺陷:moduleA这个变量还是暴露到全局了,随着模块的增多,全局变量还是会越来越多。
app.utils.moduleA= xxx;
app.tools.moduleA= xxx;
app.tools.moduleA.format = xxx;
Yahoo的YUI早期就是这么做的。但是这样调用函数的使用感觉不是很爽,不够轻快。
(function(window){
//代码
window.jQuery = window.$ = jQuery;//通过给window添加属性而暴露到全局
})(window);
jQuery的封装风格曾经被很多框架模仿,通过匿名函数包装代码,所依赖的外部变量传给这个函数,在函数内部可以使用这些依赖,然后在函数的最后把模块自身暴露给window。
如果需要添加扩展,则可以作为jQuery的插件,把它挂载到$上。
这种风格虽然灵活了些,但并未解决根本问题,所需依赖还是得外部提前提供、还是增加了全局变量。
以上是模块化萌芽阶段的一些尝试,可以看出要解决的主要问题如下:
1、 如何安全的包装一个模块的代码?(不污染模块外的任何代码)
2、 如何唯一标识一个模块?
3、 如何优雅的把模块的API暴露出去?(不能增加全局变量)
4、 如何方便的使用所依赖的模块?
如果说浏览器端的js即便没有模块化也可以勉强忍受的话,那服务端是万万不能的。高手云集且富有创造力的CommonJs社区在长期的酝酿中发力,制定了Modules/1.0规范,首次定义了一个模块应该长啥样。2009年,NodeJS横空出世,开创了JS发展史的一个崭新纪元,人们可以用js来编写服务端的代码了。
具体来说,Modules/1.0规范包含以下内容:
1、 模块的标识应遵循的规则(书写规范)
2、 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴露出来的API
3、 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
4、 如果引入模块失败,那么require函数应该报一个异常
5、 模块通过变量exports来向往暴露API,exports只能是一个对象,暴露的API须作为此对象的属性。
此规范一出,立刻产生了良好的效果,由于其简单而直接,在NodeJS中,这种模块化方案被迅速广泛推广。
下面是CommonJS规范的应用dmeo:
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
// increment.js
var add = require(‘math‘).add;
exports.increment = function(val) {
return add(val, 1);
};
// program.js
var inc = require(‘increment‘).increment;
var a = 1;
inc(a); // 2
Modules/1.0规范源于服务端,无法直接用于浏览器端,原因表现为:
外层没有function包裹,变量全暴露在全局。如上面例子中increment.js中的add。
资源的加载方式与服务端完全不同。服务端require一个模块,直接就从硬盘或者内存中读取了,消耗的时间可以忽略。而浏览器则不同,需要从服务端来下载这个文件,然后运行里面的代码才能得到API,需要花费一个http请求,也就是说,require后面的一行代码,需要资源请求完成才能执行。由于浏览器端是以插入
深入了解CommonJS, AMD, CMD, UMD的前世今生
原文:https://www.cnblogs.com/smart-elwin/p/15168431.html