浅谈JavaScript模块化

2006年,ajax的概念被提出,前端拥有了主动向服务端发送请求并操作返回数据的能力,随着Google将此概念的发扬光大,传统的网页慢慢的向“富客户端”发展。前端的业务逻辑越来越多,代码也越来越多,于是一些问题就暴漏了出来:

1. 全局变量的灾难

小明定义了 i=1

小刚在后续的代码里:i=0

小明在接下来的代码里:if(i==1){…} //悲剧

 2. 函数命名冲突

项目中通常会把一些通用的函数封装成一个文件,常见的名字有utils.js、common.js…

小明定义了一个函数:function formatData(){   }

小刚想实现类似功能,于是这么写:function formatData2(){   }

小光又有一个类似功能,于是:function formatData3(){   }

……

避免命名冲突就只能这样靠丑陋的方式人肉进行。

 3. 依赖关系不好管理

b.js依赖a.js,标签的书写顺序必须是

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>

顺序不能错,也不能漏写某个。在多人开发的时候很难协调。

然而,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,那么什么是模块呢?

一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,否则就都乱套了。

模块化面临什么问题

从不停的尝试中,可以归纳出js模块化需要解决哪些问题:

1. 如何安全的包装一个模块的代码?(不污染模块外的任何代码)

2. 如何唯一标识一个模块?

3. 如何优雅的把模块的API暴漏出去?(不能增加全局变量)

4. 如何方便的使用所依赖的模块?

围绕着这些问题,js模块化开始了一段艰苦而曲折的征途。

模块化开发规范: AMD CMD commonjs

源自nodejs的规范CommonJs

2009年,nodejs横空出世,开创了一个新纪元,人们可以用js来编写服务端的代码了。如果说浏览器端的js即便没有模块化也可以忍的话,那服务端是万万不能的。

CommonJS是服务器端模块的规范,根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。

输出模块变量的最好方法是使用module.exports对象。

var i = 1;
var max = 30;

module.exports = function () {
  for (i -= 1; i++ < max; ) {
    console.log(i);
  }
  max *= 1.1;
};

上面代码通过module.exports对象,定义了一个函数,该函数就是模块外部与内部通信的桥梁。

加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的module.exports对象。

CommonJs的问题在于,他的加载是同步的,这在服务端很正常,但是在充满了异步的浏览器里,就不适用了。为了适应浏览器,社区内部发生了分歧。

AMD/RequireJs的崛起与妥协

AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。AMD的思想正如其名,异步加载所需的模块,然后在回调函数中执行主逻辑。这正是我们在浏览器端开发所习惯了的方式,其作者亲自实现了符合AMD规范的requirejs,AMD/RequireJs迅速被广大开发者所接受。

AMD规范规定用全局函数define来定义模块,用法为define(id, dependencies, factory);其中,id为模块标识,dependencies是一个数组,数组里边是该模块依赖的其他模块,factory则是一个匿名函数,里边是该模块的逻辑。

//main.js
require(['a', 'b'], function(a, b){
     console.log('main.js执行');
     a.hello();
     $('#b').click(function(){
          b.hello();
     });
})

requireJs的问题在于,加载一个模块时,会预先加载该模块的所有依赖模块,但是这些依赖很可能一开始并不用到。同时依赖写起来一长串,也很麻烦。比较好的是AMD保留了commonJs中的require、exprots、module3个功能,可以不把依赖都写在dependencies中,而是在需要时使用require引入。

兼容并包的CMD/seajs

既然requirejs有上述种种不甚优雅的地方,所以必然会有新东西来完善它,这就是后起之秀seajs,seajs的作者是国内大牛淘宝前端步道者玉伯。seajs全面拥抱Modules/Wrappings规范,不用requirejs那样回调的方式来编写模块。而它也不是完全按照Modules/Wrappings规范,seajs并没有使用declare来定义模块,而是使用和requirejs一样的define,或许作者本人更喜欢这个名字吧。(然而这或多或少又会给人们造成理解上的混淆),用seajs定义模块的写法如下:

//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风格的代码。

AMD和CMD 的区别

AMD和CMD最明显的区别就是在模块定义时对依赖的处理不同 
AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块 
CMD推崇就近依赖,只有在用到某个模块的时候再去require 

参考资料:JavaScript模块化历程

猜你喜欢

转载自blog.csdn.net/weixin_42217154/article/details/83715201