欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

阮一峰 Javascript模块化编程(二):AMD规范 有大用

Javascript模块化编程(二):AMD规范

作者: 阮一峰

日期: 2012年10月30日

这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块。

(接上文

七、模块的规范

先想一想,为什么模块很重要?

因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。

但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。

目前,通行的Javascript模块规范共有两种:CommonJSAMD。我主要介绍AMD,但是要先从CommonJS讲起。

八、CommonJS

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。

这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。

  var math = require('math');

然后,就可以调用模块提供的方法:

  var math = require('math');

  math.add(2,3); // 5

因为这个系列主要针对浏览器编程,不涉及node.js,所以对CommonJS就不多做介绍了。我们在这里只要知道,require()用于加载模块就行了。

九、浏览器环境

有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?

  var math = require('math');

  math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

十、AMD

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

  require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

  require(['math'], function (math) {

    math.add(2, 3);

  });

math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

目前,主要有两个Javascript库实现了AMD规范:require.jscurl.js。本系列的第三部分,将通过介绍require.js,进一步讲解AMD的用法,以及如何将模块化编程投入实战。

(完)

留言(80条)

学习了!

真是简洁明了啊

最近你发文章很频繁啊,支持

引用NinoFocus的发言:

真是简洁明了啊

这样的语法去写一些复杂的应用感觉在做恶梦.

实现AMD规范的加载器其实是挺多的,不过多数人还是用requirejs。另外如果对ES6的模块感兴趣,可以考虑http://github.com/hax/my.js ,是按照ES6草案的module/loader规范实现的。

第二行Math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。

这里写错了Math应该为math吧?

期待下集

建议把seajs也加入介绍

引用redhacker的发言:

这里写错了Math应该为math吧?

谢谢指出,已经更正了。

建议写一写 SeaJS 的 CMD 规范,与 AMD 非常类似,在国内的影响力非常大,但是个人觉得 SeaJS 比 RequireJS 好很多,另外由于是国人开发的,交流也非常方便,可以看到 github 上的更新、互动非常频繁
http://seajs.org
https://github.com/seajs/seajs

这章讲的好少...
希望下一章可以禁得住看~

转载网上总结的实现规范异同:

RequireJS 与 SeaJS 的异同
https://github.com/seajs/seajs/issues/277
AMD 和 CMD 的区别有哪些?
http://www.zhihu.com/question/20351507/answer/14859415

浏览器环境模块加载个人推荐用:SeaJS

特别想听你讲ECMA3和ECMA5的区别啊

这次真的看懂了,大神的行为果然流畅易懂。
期待ECMA3与5的相关主题。

“这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。”
这段解释得太到位了

最近也在学习模块化编程。学习了!

我懂了,谢谢哥的分享~~~

您好!我有一个疑问,如下:
require(['jquery', 'backbone'], function ($, Backbone){

    // some code here

  });
*******************
同事加载了jquery,backbone两个模块,但我要在backbone里面,使用jquery怎么办啊?

我尝试了一下直接使用是不行的。

简单明了,学习了

require.js相对CommonJS太丑陋了

终于看到是人看的了,介绍的很清楚.

引用East神奇的发言:

您好!我有一个疑问,如下:
require(['jquery','backbone'], function ($, Backbone){

    // some code here

  });
*******************
同事加载了jquery,backbone两个模块,但我要在backbone里面,使用jquery怎么办啊?

我尝试了一下直接使用是不行的。


requireJS的shim可以拿来指定依赖

你好,我有个疑问, 我比较菜,不好意思。

如果我用AMD的话:
require(['math'], function (math) {
    math.add(2, 3);
});
math.add这个方法还是要等math这个模块加载完之后才会被调用,这不是跟同步一样的嘛?

阮老师,非常感谢您的分享!学生在这里谢谢您了!祝您工作顺利,身体健康!

引用javascript的菜鸟的发言:

你好,我有个疑问, 我比较菜,不好意思。

如果我用AMD的话:
require(['math'], function (math) {
    math.add(2, 3);
});
math.add这个方法还是要等math这个模块加载完之后才会被调用,这不是跟同步一样的嘛?

菜鸟献丑

这个是回调函数,math加载完后执行math.add(2,3)就不会出错。
而第一种,有可能math还没加载完时,执行math.add(2,3)会抛出错误。

讲到这里,我又发现一个问题了。跟朴灵一样,这里阮老师又没把同步异步的概念说清楚。

这个加载都是异步的。第一种没用AMD的回调函数,加载和执行是异步的,会出现异常。
第二种,加载是异步的,但用了回调,整个执行流程是同步的。

设置script.src的方式都是异步加载。

我在这看到了同步加载的办法。

http://www.cnblogs.com/ecalf/archive/2012/12/12/2813962.html

引用tab3o1的发言:


requireJS的shim可以拿来指定依赖

哦,模块加载之间也要保持顺序

@昂帝梵德·纳尔

我也有同样的问题, 那么这样说其实所有的模块加载都是同步的,只是为了执行流程不抛出异常,才用require去规范模块之间的加载顺序,继而用回调函数处理. 我是不是太菜了......

写的太清楚了!!!

写得简明易懂太适合我这种小白看啦。以后就看你的文章啦~

个人感觉所谓的同步异步的问题,还是看各个代码块的依赖关系,如果代码毫无依赖,从上之下依次执行毫无问题,可以看成同步执行。如果B代码依赖于A代码,A不执行完就执行B的话就会报错,所以只要A先于B执行完成即可,假设A执行所需要花的时间很长,而除了B以外的代码没有任何与AB的依赖关系,那么除了B需要阻塞一下,其他的代码可以直接执行,也就是所谓的异步,这样理解对否?

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

引用小白白的发言:

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

// 异步, 先加载完的先执行,可能b.bar先与a.foo执行
require(['a'], function(a) {
a.foo();
});
required(['b'], function(b) {
b.bar()
});

// 同步,b.bar必须等a加载完再执行
var a = require('a');
a.foo();
var b = require('b');
b.bar();

大概是这个意思吧?

一笔点睛,醍醐灌顶。

通俗易懂,厉害

引用小白白的发言:

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

前一个异步是针对这个模块与其他模块来说,这个模块(这个模块可能要执行很长时间,异步的可以放到最后执行)并不会阻塞其他模块的执行;这里说的同步是这个模块内部的执行是同步的(内部必须按严格执行顺序)---个人见解

引用小白白的发言:

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

这里说的同步异步,不是你理解的那样.
所谓同步,就是脚本按照顺序加载,执行.假如脚本放在页面前,这是页面就要等脚本加载好执行完才能加载页面,浏览器会停留一段时间的空白显示...
异步就不同了,脚本位置和页面位置跟上面的一情况假设一样,这时浏览器不会先加载脚本,而是从上到下先加载整个HTML的字节,加载完再解析得到外部引用的JS脚本,再采用某种算法加载这些外部脚本,加载完成一个,就执行一次事先定义的回调方法.

总体说你的问题就是 观看的角度不同

requirejs是来解决依赖的, 跟同步异步有毛关系 , 是我太菜了?

一步步引入,清晰简明!

但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

=====

请问这里怎么做到浏览器“假死”的等待状态?

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
============

请问这个依赖模块加载完成和上面CMD说浏览器“假死”等待状态有什么区别?

建议作者把文章的主题调整一下,直说AMD和CMD的区别,把文章中的“假死” 异步等相关介绍删除吧 误导初学者

本章中有一些不严谨的地方,就说下面两行代码
var math = require('math');
  math.add(2, 3);

首先两行代码是顺序执行的,如果第一行代码是异步加载的,那么第二行代码就有可能会报错,如果第一行代码是同步加载的,那么第二行就不会报错,无非会等待一段阻塞时间

引用小白白的发言:

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

模块的加载都是异步(异步要和平时生活中反过来理解,异步是指一起做多件事情)的,而执行确实是同步(同步就是个异步相反,事情得一件一件的做,不能一起做多件事)的。所以当我们require多个模块时,这些模块是同时在加载(也就是异步),可能有的模块很小,最快加载完了,那它后面的回调函数就先执行了。

小白飘过问一个弱智的问题,普通写js,通过dev Tools 打断点看JS代码的运行过程,但是采用了amd 模块化的js代码改如何断点进去

老师,我们用Eclipse编写AMD规范的JavaScript代码后,大纲(outline)视图就不能列出我们写的函数了。Ctrl + O也不能快速找到函数了。也不支持require方式的编程。我购买了spket,但是也只能解决部分js库的问题。还有其他什么解决方法吗?

引用ShiT的发言:

小白飘过问一个弱智的问题,普通写js,通过dev Tools 打断点看JS代码的运行过程,但是采用了amd 模块化的js代码改如何断点进去


可以在你需要进入断点的地方输入 debugger;
访问的时候打开开发者调试工具,走到debugger处就能进入断点。windows下按F12可以快速打开开发者调试工具。Mac下是Shift Control I。

文笔好的程序员就是让人仰慕! 一下子就豁然开朗了!!

阮老師 你好,新年快樂。
最近開始從頭看一下Commonjs 與AMD
以前胡裡胡塗的照用,現在有些想法想要釐清,
我的認知如下
Commonjs 與AMD 都只是一種標準(SPEC),
但是是非ES的標準,Commonjs 多用在Server 端的應用(ex: nodejs),
AMD是Web在用(ex: Requirejs),兩者最大目的都在載入模組,
最大的差異是Commonjs是同步 AMD是異步載入。
ES6(ES2015)開始有規範模組載入的標準,
不過我不清楚ES6 在同步或非同步的寫法上有何差異?
隨著瀏覽器的支援,ES6(以後的)標準將會取代Commonjs 與 AMD.

以上的觀念不知道有沒有錯誤的地方,再麻煩老師指正。

你们可以把模块看成是一个.js文件,异步加载是指多个.js文件同时去加载,而这个时候callback还没有执行,等所有的.js文件加载完毕的时候,才开始调用这个callback。

简洁明了,豁然开朗

简洁明了,学习啦

@昂帝梵德·纳尔:

我也想问的是这个,阮大神这个例子举的不太恰当啊。

看完上一篇,马上看完这篇,一步步从0到理解。。。

不仅从阮老师的文章中学习到知识,从网友的评论中学到了很多很多。知识只有分享才有它存在的意义。

感谢老师的分享

好久没有看到这么好的文章了,言简意赅,关键是通俗易懂!好!多多分享!

引用阮少的发言:

个人感觉所谓的同步异步的问题,还是看各个代码块的依赖关系,如果代码毫无依赖,从上之下依次执行毫无问题,可以看成同步执行。如果B代码依赖于A代码,A不执行完就执行B的话就会报错,所以只要A先于B执行完成即可,假设A执行所需要花的时间很长,而除了B以外的代码没有任何与AB的依赖关系,那么除了B需要阻塞一下,其他的代码可以直接执行,也就是所谓的异步,这样理解对否?

这个比较说的通。
引用阮少的发言:

个人感觉所谓的同步异步的问题,还是看各个代码块的依赖关系,如果代码毫无依赖,从上之下依次执行毫无问题,可以看成同步执行。如果B代码依赖于A代码,A不执行完就执行B的话就会报错,所以只要A先于B执行完成即可,假设A执行所需要花的时间很长,而除了B以外的代码没有任何与AB的依赖关系,那么除了B需要阻塞一下,其他的代码可以直接执行,也就是所谓的异步,这样理解对否?

大哥 写的真好,瞬间醍醐灌顶

峰哥的文章都很好,都讲的特别清晰易懂,我很多的知识都是在这里弄明白的

峰哥的文章好棒,我不得不赞你~~~

峰哥写得循序渐进, 系统地介绍了模块化开发这一技术, 小弟受益匪浅, 十分感谢!

不太明白,如果B依赖A,那按下面这样写不就好了:


这样自动就是A.js加载完执行完之后,再加载B.js执行B.js


为什么还要那么麻烦用什么require.js呢?

引用椰汁的发言:

不太明白,如果B依赖A,那按下面这样写不就好了:



这样自动就是A.js加载完执行完之后,再加载B.js执行B.js


为什么还要那么麻烦用什么require.js呢?

这不是依赖关系与执行顺序的问题。两种方法都是被依赖着先被创建,然后被调用。逻辑上没有问题。问题在于执行效率。

var math = require('math');
math.add(2, 3);

在这种情况下math.add(2, 3);一定会等var math = require('math');执行完成之后再执行,因此逻辑不会出错。它的问题在于整个线程会停在var math = require('math');这个比较慢的网络IO操作上。以至于后续的不依赖于math的操作也要等待。再大的内存,再多核的CPU也等着干着急。

require(['math'], function (math) {
    math.add(2, 3);
});

这个好处在于require(['math'],function (math)()};之后的的语句不用等它加载结束就可以执行。从而达到并发的效果。

个人愚见,觉得举例不太恰当,假如有a和b两个模块,两个模块不存在依赖,打包后,a在b前面,如果是模块同步加载的,那么a模块一会等到b模块执行完再执行,会存在阻塞,所以模块是异步的才行,也就是说谁先加载完需要的模块,谁就会先执行,而模块内部采用的是回调函数,其实是一种同步的思想,也就是说你必须加载完你依赖的模块,你才能执行回调函数。 简而言之,模块之间是异步,模块内部回调是同步思想。

@East神奇:

先加载jQuery模块,在callback里加载backbone模块,在callback的callback里使用jQuery

说实话,看评论收获更大。。。这篇文章写的真心一般,同步异步基本等于不提。。

引用redhacker的发言:


这里写错了Math应该为math吧?


引用插件,首字母大写~

大哥 !666!!!谢谢你提供这么好的文章!

引用july的发言:


前一个异步是针对这个模块与其他模块来说,这个模块(这个模块可能要执行很长时间,异步的可以放到最后执行)并不会阻塞其他模块的执行;这里说的同步是这个模块内部的执行是同步的(内部必须按严格执行顺序)---个人见解

引用july的发言:


前一个异步是针对这个模块与其他模块来说,这个模块(这个模块可能要执行很长时间,异步的可以放到最后执行)并不会阻塞其他模块的执行;这里说的同步是这个模块内部的执行是同步的(内部必须按严格执行顺序)---个人见解

关于AMD的模块化的同步异步问题困扰了我很长时间,听完大神的讲解,逐渐清晰了,谢谢!

感谢分享,收益匪浅,看到好多您的入门教材,写的简明扼要,层层深入,也容易理解。

每次我搜索出内容,一看到是你写的,立马首先点开看,你写的通俗易懂,谢谢

希望阮老师出一期sea.js

异步类似多线程,同步类似单线程。win界面程序为了保证界面不会卡死一下,会有个ui线程和其他线程,跟浏览器requirejs异步回调解决的问题是一样。

引用小白白的发言:

"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思

它后面的语句这句话你没看懂,后面的是指require模块外部的语句。

广告太多。

math.add()与math模块加载不是同步的,意思是不必等到math模块加载完,math.add()就可以执行?math.add()不是在回调函数里吗?回调函数不是要等math模块加载完才能执行吗?

我的理解是,commonJS中的require只是webpack打包的一个依据,根据这种require的依赖关系,打包成一个js文件(webpack也可以打包成多个文件),然后浏览器加载这个js文件!所有的模块都会一次性加载到浏览器,既然这样,那还有什么同步阻塞问题?

看了好多关于CommonJS和AMD的文章,最后终于在你这篇文章中读明白了

我的理解是, 同步和异步
首先是相对于这一行代码来讲的,

1 同步 意思是 执行这一行代码, 可以立刻拿到执行结果, 后面的代码可以直接用。
2 异步是 执行这一行代码, 但是这一代码是一很耗时的动作, 后面的代码需要等一会儿才能拿到这个动作的执行结果, 这个叫做异步。
区别就是 动作执行和拿到结果是否有时间 间隔。

其次 对于多行代码来讲,
如果 这些代码都没有耗时操作, 顺序执行 ,那就是同步, 如果下一行需要等上一行执行结果, 就是异步
以上是个人理解, 有错误请指教

我的理解是, 同步和异步
首先是相对于这一行代码来讲的,

1 同步 意思是 执行这一行代码, 可以立刻拿到执行结果, 后面的代码可以直接用。
2 异步是 执行这一行代码, 但是这一代码是一很耗时的动作, 后面的代码需要等一会儿才能拿到这个动作的执行结果, 这个叫做异步。
区别就是 动作执行和拿到结果是否有时间 间隔。

其次 对于多行代码来讲,
如果 这些代码都没有耗时操作, 顺序执行 ,那就是同步, 如果下一行需要等上一行执行结果, 就是异步
以上是个人理解, 有错误请指教

我要发表看法


来自  http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html

普通分类: