AMD - Learning JavaScript Design Patterns [Book] - O'Reilly

The overall goal for the Asynchronous Module Definition (AMD) format is to provide a solution for modular JavaScript that developers can use today. It was born out of Dojo’s real world experience using XHR+eval, and proponents of this format wanted to avoid any future solutions suffering from the weaknesses of those in the past.

The AMD module format itself is a proposal for defining modules in which both the module and dependencies can be asynchronously loaded. It has a number of distinct advantages, including being both asynchronous and highly flexible by nature, which removes the tight coupling one might commonly find between code and module identity. Many developers enjoy using it, and one could consider it a reliable stepping stone toward the module system proposed for ES Harmony.

AMD began as a draft specification for a module format on the CommonJS list, but as it wasn’t able to reach full consensus, further development of the format moved to the amdjs group.

Today, it’s embraced by projects including Dojo, MooTools, Firebug, and even jQuery . Although the term CommonJS AMD format has been seen in the wild on occasion, it’s best to refer to it as just AMD or Async Module support, as not all participants on the CommonJS list wished to pursue it.

Note

There was a time when the proposal was referred to as Modules Transport/C, however as the spec wasn’t geared toward transporting existing CommonJS modules, but rather—for defining modules—it made more sense to opt for the AMD naming convention.

Getting Started with Modules

The first two concepts worth noting about AMD are the idea of a define method for facilitating module definition and a require method for handling dependency loading. define is used to define named or unnamed modules based using the following signature:

define(
module_id /*optional*/,
[dependencies] /*optional*/,
definition function /*function for instantiating the module or object*/
);

As we can tell by the inline comments, the module_id is an optional argument that is typically only required when non-AMD concatenation tools are being used (there may be some other edge cases where it’s useful, too). When this argument is left out, we refer to the module as anonymous.

When working with anonymous modules, the idea of a module’s identity is DRY, making it trivial to avoid duplication of filenames and code. Because the code is more portable, it can be easily moved to other locations (or around the filesystem) without needing to alter the code itself or change its module ID. Consider the module_id similar to the concept of folder paths.

Note

Developers can run this same code on multiple environments just by using an AMD optimizer that works with a CommonJS environment such as r.js.    r.js Runs RequireJS in Node and Rhino, and used to run the RequireJS optimizer.

Back to the define signature, the dependencies argument represents an array of dependencies required by the module we are defining, and the third argument (definition function or factory function) is a function that’s executed to instantiate our module. A bare bone module could be defined as follows (Example 11-1):

Example 11-1. Understanding AMD: define()

// A module_id (myModule) is used here for demonstration purposes only
define( "myModule", ["foo", "bar"], // module definition function
// dependencies (foo and bar) are mapped to function parameters
function ( foo, bar ) {
// return a value that defines the module export
// (i.e the functionality we want to expose for consumption) // create your module here
var myModule = {
doStuff:function () {
console.log( "Yay! Stuff" );
}
}; return myModule;
}); // An alternative version could be..
define( "myModule", ["math", "graph"], function ( math, graph ) { // Note that this is a slightly different pattern
// With AMD, it's possible to define modules in a few
// different ways due to it's flexibility with
// certain aspects of the syntax
return {
plot: function( x, y ){
return graph.drawPie( math.randomGrid( x, y ) );
}
};
});

require, on the other hand, is typically used to load code in a top-level JavaScript file or within a module, should we wish to dynamically fetch dependencies. Here is an example of its usage (Example 11-2):

Example 11-2. Understanding AMD: require()

// Consider "foo" and "bar" are two external modules
// In this example, the "exports" from the two modules
// loaded are passed as function arguments to the
// callback (foo and bar) so that they can similarly be accessed require(["foo", "bar"], function ( foo, bar ) {
// rest of your code here
foo.doSomething();
});

The following is an example of a dynamically-loaded dependency (Example 11-3):

Example 11-3. Dynamically-loaded dependencies

define(function ( require ) {
var isReady = false, foobar; // note the inline require within our module definition
require(["foo", "bar"], function ( foo, bar ) {
isReady = true;
foobar = foo() + bar();
}); // we can still return a module
return {
isReady: isReady,
foobar: foobar
};
});

The following is an example of defining an AMD-compatible plug-in (Example 11-4):

Example 11-4. Understanding AMD: plug-ins

// With AMD, it's possible to load in assets of almost any kind
// including text-files and HTML. This enables us to have template
// dependencies which can be used to skin components either on
// page-load or dynamically. define( ["./templates", "text!./template.md","css!./template.css" ], function( templates, template ){
console.log( templates );
// do something with our templates here
} });

Note

Although css! is included for loading CSS dependencies in the above example, it’s important to remember that this approach has some caveats, such as it not being fully possible to establish when the CSS is fully loaded. Depending on how we approach our build process, it may also result in CSS being included as a dependency in the optimized file, so use CSS as a loaded dependency in such cases with caution. If interested in doing the above, we can also explore @VIISON’s RequireJS CSS plug-in further here.

This example could simply be looked at as requirejs(["app/myModule"], function(){}), which indicates the loader’s top level globals are being used.

This is how to kick off top-level loading of modules with different AMD loaders; however, with a define() function, if it’s passed a local require all require([]) examples apply to both types of loader, curl.js and RequireJS (Examples 11-5 and 11-6).

Example 11-5. Loading AMD modules using RequireJS

require(["app/myModule"], 

    function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff();
});

Example 11-6. Loading AMD modules using curl.js

curl(["app/myModule.js"], 

    function( myModule ){
// start the main module which in-turn
// loads other modules
var module = new myModule();
module.doStuff(); });

Here is the code for modules with deferred dependencies:

// This could be compatible with jQuery's Deferred implementation,
// futures.js (slightly different syntax) or any one of a number
// of other implementations define(["lib/Deferred"], function( Deferred ){
var defer = new Deferred(); require(["lib/templates/?index.html","lib/data/?stats"],
function( template, data ){
defer.resolve( { template: template, data:data } );
}
);
return defer.promise();
});

AMD Modules with jQuery

Unlike Dojo, jQuery really only comes with one file; however, given the plug-in-based nature of the library, we can demonstrate how straightforward it is to define an AMD module that uses it below.

define(["js/jquery.js","js/jquery.color.js","js/underscore.js"],

    function( $, colorPlugin, _ ){
// Here we've passed in jQuery, the color plugin and Underscore
// None of these will be accessible in the global scope, but we
// can easily reference them below. // Pseudo-randomize an array of colors, selecting the first
// item in the shuffled array
var shuffleColor = _.first( _.shuffle( "#666","#333","#111"] ) ); // Animate the background-color of any elements with the class
// "item" on the page using the shuffled color
$( ".item" ).animate( {"backgroundColor": shuffleColor } ); // What we return can be used by other modules
return {};
});

There is, however, something missing from this example: the concept of registration.

Registering jQuery as an Async-compatible module

One of the key features that landed in jQuery 1.7 was support for registering jQuery as an asynchronous module. There are a number of compatible script loaders (including RequireJS and curl) that are capable of loading modules using an asynchronous module format, and this means fewer hacks are required to get things working.

If a developer wants to use AMD and does not want her jQuery version leaking into the global space, she should call noConflict in their top level module that uses jQuery. In addition, since multiple versions of jQuery can be on a page, there are special considerations that an AMD loader must account for, and so jQuery only registers with AMD loaders that have recognized these concerns, which are indicated by the loader specifying define.amd.jQuery. RequireJS and curl are two loaders that do so.

The named AMD provides a safety blanket of being both robust and safe for most use cases.

// Account for the existence of more than one global
// instances of jQuery in the document, cater for testing
// .noConflict() var jQuery = this.jQuery || "jQuery",
$ = this.$ || "$",
originaljQuery = jQuery,
original$ = $; define(["jquery"] , function ( $ ) {
$( ".items" ).css( "background","green" );
return function () {};
});

Why AMD is a better choice for writing modular JavaScript

We have now reviewed several code samples taking us through what AMD is capable of. It certainly appears to give us more than just a typical Module pattern, but why is it a better choice for modular application development?

  • Provides a clear proposal for how to approach defining flexible modules.

  • Significantly cleaner than the present global namespace and <script> tag solutions many of us rely on. There’s a clean way to declare standalone modules and dependencies they may have.

  • Module definitions are encapsulated, helping us to avoid pollution of the global namespace.

  • Arguably works better than some alternative solutions (e.g., CommonJS, which we’ll be looking at shortly). It doesn’t have issues with cross-domain, local, or debugging and doesn’t rely on server-side tools to be used. Most AMD loaders support loading modules in the browser without a build process.

  • Provides a “transport” approach for including multiple modules in a single file. Other approaches like CommonJS have yet to agree on a transport format.

  • It’s possible to lazy load scripts if this is needed.

Note

Many of the above could be said about YUI’s module loading strategy as well.

AMD - Learning JavaScript Design Patterns [Book] - O'Reilly的更多相关文章

  1. Learning JavaScript Design Patterns The Module Pattern

    The Module Pattern Modules Modules are an integral piece of any robust application's architecture an ...

  2. Learning JavaScript Design Patterns The Observer Pattern

    The Observer Pattern The Observer is a design pattern where an object (known as a subject) maintains ...

  3. Learning JavaScript Design Patterns The Singleton Pattern

    The Singleton Pattern The Singleton pattern is thus known because it restricts instantiation of a cl ...

  4. Learning JavaScript Design Patterns The Constructor Pattern

    In classical object-oriented programming languages, a constructor is a special method used to initia ...

  5. use getters and setters Learning PHP Design Patterns

    w Learning PHP Design Patterns Much of what passes as OOP misuses getters and setters, and making ac ...

  6. Learning PHP Design Patterns

    Learning PHP Design Patterns CHAPTER 1 Algorithms handle speed of operations, and design patterns ha ...

  7. JavaScript Design Patterns: Mediator

    The Mediator Design Pattern The Mediator is a behavioral design pattern in which objects, instead of ...

  8. Javascript Design Patterns - Js Class

    JavaScript is a class-less language, however classes can be simulated using functions. eg: // A car ...

  9. .Learning.Python.Design.Patterns.2nd.Edition之单实例模式

    可以慢慢理解.. 对照JAVA class Singleton(object): def __new__(cls): if not hasattr(cls, 'instance'): cls.inst ...

随机推荐

  1. 在git bash 中配置git用户名和邮箱及查看配置信息

    Administrator@LuoTong- MINGW32 ~ $ git config --global user.name "mrluotong" Administrator ...

  2. tz汇报

    不爽,不满意,存在太多Bug,汇报前的了解不充分,了解到了有那些领导参加,但是没有具体了解领导的时间安排,没有按照领导的时间调整汇报提纲及思路,汇报到1个半小时,领导需要参加会议,提前离开,没能够与领 ...

  3. 太原fpxt招标

    5.31号13点多赶到太原,到yy公司,准备参加6.1号的jzfpxt投标,一起到yy山西分公司的还有北京yy总公司D工,Y工,W工等, D工负责标书及系统演示,我主要是根据D工的演示思路调整系统,演 ...

  4. webpack初体验之模块化开发

    写在前面的话 上次写过一篇关于webpack入门的博客,当时只是说借助node来完成开发,并用webpack打包以让浏览器识别.其实其主要思想就是实现前端模块化开发. 众所周知,历史上,JavaScr ...

  5. poj1236-Tarjan算法

    题目大意: 一些学校连成了网络, 在学校之间存在某个协议:每个学校都维护一张传送表,表明他们要负责将收到的软件传送到表中的所有学校.如果A在B的表中,那么B不一定在A的表中. 现在的任务就是,给出所有 ...

  6. POJ 3743 LL’s cake(圆+PSLG)

    题意是给你一块在原点半径为10的圆,然后告诉你一条直线在圆弧上的极角,相当于用这条直线把这个圆分成两半,然后一共是n条直线切圆,就好比切蛋糕,问你其中最大一块的面积是多少. 如果我们将圆弧转化成直线边 ...

  7. 6-1 如何读写csv数据

    >>> from urllib import urlretrieve >>> urlretrieve('http://table.finance.yahoo.com ...

  8. letsencrypt 免费SSL证书申请, 自动更新

    Let's Encrypt 泛域名 证书申请 及自动更新 关键字:SSL证书.HTTPS 初次申请 1. 下载certbot wget https://dl.eff.org/certbot-auto ...

  9. 45. Jump Game II (JAVA)

    Given an array of non-negative integers, you are initially positioned at the first index of the arra ...

  10. 封装自己的framework静态库

    ios中我们写的代码有时不愿意让别人看到,可能对它进行封装,生成一个静态库如典型的.a,还有一种就是和 苹果自带的库一样的后缀名为.framework的库,个人推荐使用.framework,因为.a不 ...