AMD 模式介绍(en)
AMD: The Definitive SourceSo what is AMD?AswebapplicationscontinuetogrowmoreadvancedandmoreheavilyrelyonJavaScript,therehasbeenagrowingmovementtowardsusingmodulestoorganizecodeanddependencies.Modulesgiveusawaytomakeclearlydistinguishedcomponentsandinterfacesthatcaneasilybeloadedandconnectedtodependencies.TheAMDmodulesystemgivesustheperfectpathforusingJavaScriptmodulestobuildwebapplications,withasimpleformat,asynchronousloading,andbroadadoption. The Asynchronous Module Definition (AMD) format is an API for defining reusable modules that can be used across different frameworks. AMD was developed to provide a way to define modules such that they could be loaded asynchronously using the native browser script element-based mechanism. The AMD API grew out ofdiscussions in 2009 in the Dojo communitywhich then moved to discussions with CommonJS on how to better adapt the CommonJS module format (used by NodeJS) for the browser. It has since grown into its own standard with its own community. AMD has taken off in popularity,with numerous module loaders and widespread usage. At SitePen we have worked extensively with AMD in Dojo,adding support and now actively building applications with this format.
Glossary
Why AMD Modules?The basic premise of a module system is to:
AMD satisfies this need,and uses a callback function with dependencies as arguments so that dependencies can be asynchronously loaded before the module code is executed. AMD also provides a plugin system for loading non-AMD resources. While alternate methods can be used to load JavaScript (XHR + eval),using script elements to load JavaScript has an edge in performance,eases debugging (particularly on older browsers),and has cross-domain support. Thus AMD aims to provide an optimal development experience in the browser. The AMD format provides several key benefits. First,it provides a compact declaration of dependencies. Dependencies are defined in a simple array of strings,making it easy to list numerous dependencies with little overhead. AMD helps eliminate the need for globals. Each module defines dependencies and exports by referencing them with local variables or return objects. Consequently,modules can define functionality and interact with other modules without having to introduce any global variables. AMD is also “anonymous”,meaning that the module does not have to hard-code any references to its own path,the module name relies solely on its file name and directory path,greatly easing any refactoring efforts. By coupling dependencies with local variables,AMD encourages high-performance coding practices. Without an AMD module loader,JavaScript code has traditionally relied on the nested objects to “namespace” functionality of a given script or module. With this approach,functions are typically accessed through a set of properties,resulting in a global variable lookup and numerous property lookups,adding extra overhead and slowing down the application. With dependencies matched to local variables,functions are typically accessed from a single local variable,which is extremely fast and can be highly optimized by JavaScript engines. Using AMD The foundational AMD API is the
The To demonstrate basic usage of AMD,here we could define a module that utilizes the
3
4
5
6
7
8
9
10
|
define([
"dojo/query"
,
"dojo/on"
],
(query,on){
return
{
flashHeaderOnClick:
(button){
on(button,monospace; font-size:1em; border:0px; bottom:auto; float:none; height:auto; left:auto; line-height:1.1em; margin:0px; outline:0px; overflow:visible; padding:0px; position:static; right:auto; top:auto; vertical-align:baseline; width:auto; min-height:inherit; color:blue">"click"
(){
query(
".header"
).style(
"color"
"red"
);
});
}
};
Once dojo/query and dojo/on are loaded (which doesn’t happen until their dependencies are loaded,and so on),the callback function is called,with the |
<script src=
"/path/to/dojo/dojo.js"
><!--mce:1--></script>
<script type=
"text/javascript"
><!--mce:2--></script>
|
Dojo provides a shortcut for loading an initial module. The initial module can be loaded by specifying the module in thedeps
configuration option:
This is an excellent way of loading your application because JavaScript code can be completely eliminated in HTML,and only a single script tag is needed to bootstrap the rest of the application. This also makes it very easy to create aggressive builds that combine your application code and dojo.js code in a single file without having to alter the HTML script tags after the build. RequireJS and other module loaders have similar options for loading the top level modules.
The progression of dependency loading from therequire()
call to the modules is illustrated in the diagram above. Therequire()
call kicks off the loading of the first module,and dependencies are loaded as needed. Modules that are not needed (“module-d” in the diagram) are never loaded or executed.
require()function can also be used to configure the module path look-ups,and other options,but these are generally specific to the module loader,and more information on configuration details are available in each loader’s documentation.
Plugins and Dojo Optimizations
AMD also supports plugins for loading alternate resources. This is extremely valuable for loading non-AMD dependencies like HTML snippets and templates,CSS,and internationalized locale-specific resources. Plugins allow us to reference these non-AMD resources in the dependency list. The syntax for this is:
"plugin!resource-name"
A commonly used plugin is thedojo/text
plugin which allows you to directly load a file as text. With this plugin,we list the target file as the resource name. This is frequently used by widgets to load their HTML template. For example,with Dojo we can create our own widget like this:
"dojo/_base/declare"
"dijit/_WidgetBase"
"dijit/_TemplatedMixin"
"dojo/text!./templates/foo.html"
(declare,_WidgetBase,_TemplatedMixin,template){
declare([_WidgetBase,_TemplatedMixin],{
templateString: template
});
This example is instructive on multiple levels for creating Dojo widgets. First,this represents the basic boilerplate for creating a widget using the Dijit infrastructure. You might also note how we created a widget class and returned it. The declare() (class constructor) was used without any namespace or class name. As AMD eliminates the need for namespaces,we no longer need to create global class names withdeclare() . This aligns with a general strategy in AMD modules of writing anonymous modules. Again,an anonymous module is one that does not have any hardcoded references to its own path or name within the module itself and we could easily rename this module or move it to a different path without having to alter any code inside the module. Using this approach is generally recommended,however if you will be using this widget with declarative markup,you will still need to include namespace/class names in order to create a namespaced global for Dojo’s parser to reference in Dojo 1.7. Improvements coming in Dojo 1.8 allow you to use module ids.
There are several other plugins that are included with Dojo that are useful. The
7
|
"dojo/domReady!"
(query){
// DOM is ready,so we can query away
".some-class"
).forEach(
(node){
// do something with these nodes
});
Another valuable plugin is |
"dojo/has!touch?ui/touch:ui/desktop"
(ui){
// ui will be ui/touch if touch is enabled,
//and ui/desktop otherwise
ui.start();
The ternary operators can be nested,and empty strings can be used to indicate no module should be loaded.
The benefit of using Data Modules For modules that do not have any dependencies,and are simply defined as an object (like just data),one can use a single argument define({
foo:
"bar"
This is actually similar to JSONP,enabling script-based transmission of JSON data. But,AMD actually has an advantage over JSONP in that it does not require any URL parameters; the target can be a static file without any need for active code on the server to prefix the data with a parameterized callback function. However,this technique must be used with caution as well. Module loaders always cache the modules,so subsequent |
define(
(require){
var
query = require(
);
on = require(
);
...
When a single argument is provided,require,exports and module are automatically provided to the factory. The AMD module loader will scan the factory function for require calls,and automatically load them prior to running the factory function. Because the require calls are directly inline with the variable assignment,I could easily delete one of the dependency declarations without any further need to synchronize lists.
|
// declare modules that we need up front
"dojo/dom-create"
"require"
return
function
// create container elements for our widget right away,
// these could be styled for the right width and height,
// and even contain a spinner to indicate the widgets are loading
slider = domCreate(
"div"
"slider"
},node);
progress = domCreate(
"progress"
// now load the widgets,we load them independently
// so each one can be rendered as it downloads
"dijit/form/HorizontalSlider"
(Slider){
Slider({},slider);
});
"dijit/Progress"
(Progress){
Progress({},progress);
});
}
This provides an excellent user experience because they interact with components as they become available,rather than having to wait for the entire application to load. Users are also more likely to feel like an application is fast and responsive if they can see the page progressively rendering.
|
main.js:
"component"
"exports"
(component,exports){
// we define our exported values on the exports
// which may be used before this factory is called
exports.config = {
title:
"test"
};
exports.start =
(){
component.Widget();
};
});
component.js:
"main"
// again,we define our exported values on the exports
// which may be used before this factory is called
exports.Widget = declare({
showTitle:
(){
alert(main.config.title);
}
});
This example would not function properly if we simply relied on the return value,because one factory function in the circular loop needs to execute first,and wouldn’t be able to access the return value from the other module.
calls will be scanned,so this example could be written:
(require,exports){
exports.myFunction =
....
};
Looking Forward
The EcmaScript committee has been working on adding native module support in JavaScript. Theproposed additionis based on new syntax in the JavaScript language for defining and referencing modules. The new syntax includes a import {query} from
"dojo/query.js"
;
import {on} from
"dojo/on.js"
;
export
flashHeaderOnClick(button){
(){
);
}
|
The proposed new module system includes support forcustom module loadersthat can interact with the new module system,which may also still be used to retain certain existing AMD features like plugins for non-JavaScript resources.