AMD规范:简单而优雅的动态载入JavaScript代码

发布于 2022-09-11 00:53:38 字数 4497 浏览 24 评论 1

AMD规范:简单而优雅的动态载入JavaScript代码

      JavaScript代码的动态载入一直是八仙过海,各显神通,每个框架都有自己的做法。或者动态插入script标记,或者通过XMLHttpRequest获取后eval执行。AMD规范定义了一种非常简单的API,用于此目的,个人以为很好很强大,翻译过来与大家分享。

本文翻译自http://www.sitepen.com/blog/2010/11/04/requirejsamd-module-forms/,并加入部分自己的解释。

CommonJS 提出了一种用于同步或异步动态加载JavaScript代码的API规范,非常简单却很优雅,称之为AMD(Modules/AsynchronousDefinition)。RequireJS和NodeJS的Nodules已经实现了这个API,而Dojo也将马上完全支持(Dojo1.6)。规范本身非常简单,甚至只包含了一个API:

  1. define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

复制代码通过参数的排列组合,这个简单的API可以从容应对各种各样的应用场景,如下所述。

匿名模块
在这种场景下,无需输入模块名,即省略第一个参数,仅包含后两个参数:依赖模块的列表以及回调函数,例如一个简单的匿名模块可以用如下代码定义:

  1. define(["math"], function(math){
  2.   return {
  3.     addTen: function(x){
  4.       return math.add(x, 10);
  5.     }
  6.   };
  7. });

复制代码在这里,第一个参数表示依赖的模块列表,即math模块。一旦所有依赖的模块被载入完成,那么第三个参数定义的回调函数将被执行,依赖模块的引用作为参数传递给回调函数。

如例子中所示,如果模块名被省略不写,那么这是一个匿名模块。通过这种强大的方式,模块的源代码与它的标识可以做到不相关。从而可以在不改变模块代码的情况下移动源码文件的位置。这个技术遵循了基本的DRY(Don't Repeat Yourself)原则,避免了模块标识的多次存储(文件名/路径信息不会在代码中重复)。这不仅使得模块的开发变得更加容易,而且为模块的重用提供了极大的灵活性。

下面我们看如何从一个Web页面载入这个模块。我们假设上面的模块存储在文件adder.js中。使用RequireJS,我们可以用下面方式来载入这个模块:

  1. <script src="require.js"></script>
  2. <script>
  3. require(["adder"], function(adder){
  4.   // ready to use adder
  5. });
  6. </script>

复制代码一旦代码被执行
,RequireJS将会自动去调用adder模块所有的依赖模块。载入完毕之后,我们就可以通过回调函数的adder参数来使用前面定义的匿名模块。例子中可以看到,adder.js里存储的是定义的匿名模块,实际上我们可以用任何文件/路径来包含这个模块,为模块的重用提供了方便(Java中的文件名/路径和类名/包的必须一致性实际上就为类级别的重用造成了不便)。require函数用于载入任何一个模块,后面将多次使用。

对于匿名模块的使用有一些注意事项。比如每个文件中只能包含一个匿名模块,而且匿名模块只能被载入器载入,即只能用require来载入。也可以这么理解,实际上匿名模块并不是没有名字,而是在使用时进行命名的模块,例子中就是adder。

数据封装:新的JSON-P
对于一些仅仅提供数据或者独立方法(不依赖于其它模块的方法)的模块,可以简单的用如下方式来定义:

  1. define({
  2.   name:"some data"
  3. });

复制代码这个和JSON-P非常像,但是却有一个显著的优点:它使得JSON-P数据可以现在静态文件中,而并不需要动态的回调过程。这也使得内容是可cache的,而且是CDN友好的。

封装CommonJS模块
CommonJS也是一套RIA框架,其中的模块可以通过AMD来进行封装,从而可以用define的方式很容易的进行异步装载,在这里我们可以省略前2个参数,仅包含回调函数,但回调函数的第一个参数是require方法,第二个参数是exports对象,它定义了模块本身,回调函数里的require的使用将被自动进行动态加载。例如:

  1. define(function(require, exports){
  2. //math是标准CommonJS模块:
  3.   var math = require("math");
  4.   exports.addTen = function(x){
  5.     return math.add(x, 10);
  6.   };
  7. });

复制代码需要注意这种形式要求模块载入器扫描require函数。require调用必须写成require(“…”)的形式才能被正确识别从而正常工作。这在一些浏览器不能正常工作(例如默写版本的Opera移动版,以及PS3)。当然,如果在部署前对代码进行了build,这将完全不成问题。你也可以封装CommonJS模块,并手动的指定依赖,这种方式使得我们也可以引用CommonJS变量,从而我们可以包含标准的require和exports变量:

  1. define(["require", "exports", "math"], function(require, exports){
  2. // standard CommonJS module:
  3.   var math = require("math");
  4.   exports.addTen = function(x){
  5.     return math.add(x, 10);
  6.   };
  7. });  

复制代码完整的模块定义
一个完整的模块定义包含了模块名,依赖,以及回调函数。这种形式的优点是模块可以包含在另外的文件中,或者可以用script标记载入的地址中。这是build工具自动生成的规范模式,使得多个依赖可以被打包在同一个文件中,这种格式的例子如下:

  1. define("adder", ["math"], function(math){
  2.   return {
  3.     addTen: function(x){
  4.       return math.add(x, 10);
  5.     }
  6.   };
  7. });

复制代码最后,我们来看有模块id,但没有模块依赖的情况。这种情况用于你想指定模块id,但是这个模块不依赖于其它模块。这时的参数默认是“require”,“exports”和“module”。从而我们可以这样创建adder模块。

  1. define("adder", function(require, exports){
  2.   exports.addTen = function(x){
  3.       return x + 10;
  4.   };
  5. });

复制代码通过这种方式定义的模块可以被RequireJS载入,也可以作为其它模块的依赖被载入,或者直接用require()的形式载入。

综上所述,这种API看似简单,却提供了一种极其灵活的方式来定义模块,适用于各种应用场景,从可被自由移动的匿名模块,到构建后的可被<script>标记载入的模块。当前RequireJS和Dojo实现了这套规范,而JavaScript的Web Server框架NodeJS的Nodules也实现了这个规范。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

作妖 2022-09-15 04:18:57

thx for sharing

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文