天天看点

以代码爱好者角度来看AMD与CMD

 随着浏览器功能越来越完善,前端已经不仅仅是切图做网站,前端在某些方面已经媲美桌面应用。越来越庞大的前端项目,越来越复杂的代码,前端开发者们对于模块化的需求空前强烈。后来node出现了,跟随node出现的还有commonjs,这是一种js模块化解决方案,像node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,commonjs 加载模块是同步的,所以只有加载完成才能执行后面的操作。但是浏览器环境不同于node,浏览器中获取一个资源必须要发送http请求,从服务器端获取,采用同步模式必然会阻塞浏览器进程出现假死现象。在这方面dojo曾经做了伟大尝试,早期dojo便是采用xhr+eval的方式,结果可想而知,阻塞现象是必然的。后来出现无阻塞加载脚本方式在开发中广泛应用,在此基础结合commonjs规范,前端模块化迎来了两种方案:amd、cmd.

以代码爱好者角度来看AMD与CMD

  关于二者的区别,前人之述备矣:

<a href="http://my.oschina.net/felumanman/blog/263330?p=1">关于 commonjs amd cmd umd</a>

<a href="http://www.infoq.com/cn/articles/browser-resource-loading-optimization">让我们再聊聊浏览器资源加载优化</a>

<a href="http://www.douban.com/note/283566440/">seajs与requirejs最大的区别</a>

<a href="http://www.zhihu.com/question/21347409#answer-2323656">yui modules 与 amd/cmd,哪一种方式更好?</a>

<a href="http://blog.chinaunix.net/uid-26672038-id-4112229.html">javasript模块规范 - amd规范与cmd规范介绍</a>

  而在本文,我们仅从代码爱好者的角度来一窥二者api、模块管理、加载、执行的异同。

  对比amd与cmd规范,二者最大的区别在于依赖模块的执行时期,cmd规范中明确要求延迟执行(execution must be lazy.)。这一点从二者在模块的定义方法define的函数签名上可以看出:

  amd中define如下定义:

 id:string类型,它指定了模块被定义时的id;可选的,如果省略,模块id默认使用加载器请求的响应脚本的模块id。

dependencies是一个模块定义时要求的依赖项的模块id数组字面量。这些依赖项必须在factory方法执行前被解析,解析值应当被当做参数传递给factory函数;factory的参数位置符合模块在依赖项中的索引。

factory,是一个被用来执行模块初始化的参数或者是一个对象。如果factory是一个函数,它应当只能被用来执行一次。如果factory参数是一个对象,这个对象呗用来作为模块的输出值。如果factory函数返回一个值(对象、函数、任何可以被强制转换为true的值),这个值将会被作为模块的输出值。

 cmd中模块如下定义:

  一个模块使用define函数来定义

define函数只接受一个模块工厂作为参数

factory必须是一个函数或者其他有效值

如果factory是一个函数,如果指定参数的话,前三个必须是“require”,“exports”,“module”

如果factory不是一个函数,那么模块的exports属性被设置为那个有效对象

  需要提一下的是二者对待依赖模块的加载是一致的,在factory执行时,依赖模块都已被加载。从代码上来看,amd中在begin处a、b的factory都是执行过的;而cmd中虽然a、b模块在begin已被加载,但尚未执行,需要调用require执行依赖模块。这就是cmd中着重强调的延迟执行。如果这个例子不明显的话,我们来看一下条件依赖:

  amd:

cmd:

  条件依赖意思是我们根据条件使用依赖项,在amd中begin位置处a、b模块都需要被执行一次。cmd中begin处a、b都没有被执行,在end处,a、b只有一个被实际执行过。

  那么问题来了,javascript作为脚本语言,代码肯定是顺序执行的,作为amd与cmd的实现者,requirejs与seajs是如何知道需要加载的所有文件呢?又是如何做到异步加载?对于seajs,factory中代码肯定是顺序执行的,但是这必须导致require时的阻塞加载,而她又是如何保证异步加载的?

  每一个卓越的思想都有一份朴实的代码实现。所以无论amd与cmd都要面临以下几个问题:

  1、模块式如何注册的,define函数都做了什么?

  2、他们是如何知道模块的依赖?

  3、如何做到异步加载?尤其是seajs如何做到异步加载延迟执行的?

  辩证法第一规律:事物之间具有有机联系。amd与cmd都借鉴了commonjs,宏观层面必有一致性,比如整体处理流程:

以代码爱好者角度来看AMD与CMD

  模块的加载解析到执行过程一共经历了6个步骤:

  1、由入口进入程序

  2、进入程序后首先要做的就是建立一个模块仓库(这是防止重复加载模块的关键),javascript原生的object对象最为适合,key代表模块id,value代表各个模块,处理主模块

  3、向模块仓库注册一模块,一个模块最少包含四个属性:id(唯一标识符)、deps(依赖项的id数组)、factory(模块自身代码)、status(模块的状态:未加载、已加载未执行、已执行等),放到代码中当然还是object最合适

  4、模块即是javascript文件,使用无阻塞方式(动态创建script标签)加载模块 

  5、模块加载完毕后,获取依赖项(amd、cmd区别),改变模块status,由statuschange后,检测所有模块的依赖项。

  由于requirejs与seajs遵循规范不同,requirejs在define函数中可以很容易获得当前模块依赖项。而seajs中不需要依赖声明,所以必须做一些特殊处理才能否获得依赖项。方法将factory作tostring处理,然后用正则匹配出其中的依赖项,比如出现require(./a),则检测到需要依赖a模块。

以代码爱好者角度来看AMD与CMD

  同时满足非阻塞和顺序执行就需要需要对代码进行一些预处理,这是由于cmd规范和浏览器环境特点所决定的。

  6、如果模块的依赖项完全加载完毕(amd中需要执行完毕,cmd中只需要文件加载完毕,注意这时候的factory尚未执行,当使用require请求该模块时,factory才会执行,所以在性能上seajs逊于requirejs),执行主模块的factory函数;否则进入步骤3.

  最后,无论requirejs还是seajs都已被广泛应用于web开发中,实际选取时应根据以下几方面综合平衡选取:

  1、功能能否满足项目需求

  2、文档、demo的详尽程度

  3、框架的学习曲线

  4、社区的活跃度

继续阅读