这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块。
(接上文)
七、模块的规范
先想一想,为什么模块很重要?
因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。
目前,通行的Javascript模块规范共有两种:CommonJS和AMD。我主要介绍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.js和curl.js。本系列的第三部分,将通过介绍require.js,进一步讲解AMD的用法,以及如何将模块化编程投入实战。
(完)
宁静 说:
学习了!
2012年10月30日 17:59 | # | 引用
NinoFocus 说:
真是简洁明了啊
2012年10月30日 18:38 | # | 引用
邓安良 说:
最近你发文章很频繁啊,支持
2012年10月30日 20:21 | # | 引用
sundr 说:
这样的语法去写一些复杂的应用感觉在做恶梦.
2012年10月30日 22:32 | # | 引用
hax 说:
实现AMD规范的加载器其实是挺多的,不过多数人还是用requirejs。另外如果对ES6的模块感兴趣,可以考虑http://github.com/hax/my.js ,是按照ES6草案的module/loader规范实现的。
2012年10月31日 02:47 | # | 引用
redhacker 说:
这里写错了Math应该为math吧?
2012年10月31日 08:28 | # | 引用
大名 说:
期待下集
2012年10月31日 16:30 | # | 引用
w 说:
建议把seajs也加入介绍
2012年10月31日 17:33 | # | 引用
阮一峰 说:
谢谢指出,已经更正了。
2012年10月31日 21:15 | # | 引用
忆秋年 说:
建议写一写 SeaJS 的 CMD 规范,与 AMD 非常类似,在国内的影响力非常大,但是个人觉得 SeaJS 比 RequireJS 好很多,另外由于是国人开发的,交流也非常方便,可以看到 github 上的更新、互动非常频繁
http://seajs.org
https://github.com/seajs/seajs
2012年11月 1日 13:12 | # | 引用
HACK21 说:
这章讲的好少...
希望下一章可以禁得住看~
2012年11月 2日 15:32 | # | 引用
BillLi 说:
转载网上总结的实现规范异同:
RequireJS 与 SeaJS 的异同
https://github.com/seajs/seajs/issues/277
AMD 和 CMD 的区别有哪些?
http://www.zhihu.com/question/20351507/answer/14859415
浏览器环境模块加载个人推荐用:SeaJS
2012年11月 6日 15:55 | # | 引用
桃桃 说:
特别想听你讲ECMA3和ECMA5的区别啊
2012年11月11日 14:18 | # | 引用
亚丁 说:
这次真的看懂了,大神的行为果然流畅易懂。
期待ECMA3与5的相关主题。
2012年12月13日 14:28 | # | 引用
VVG 说:
“这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。”
这段解释得太到位了
2013年1月 5日 10:20 | # | 引用
撒子都学 说:
最近也在学习模块化编程。学习了!
2013年1月 7日 10:30 | # | 引用
钱运来 说:
我懂了,谢谢哥的分享~~~
2013年1月15日 10:15 | # | 引用
East神奇 说:
您好!我有一个疑问,如下:
require(['jquery', 'backbone'], function ($, Backbone){
// some code here
});
*******************
同事加载了jquery,backbone两个模块,但我要在backbone里面,使用jquery怎么办啊?
我尝试了一下直接使用是不行的。
2013年2月 4日 11:04 | # | 引用
lee 说:
简单明了,学习了
2013年3月16日 16:24 | # | 引用
j60017268 说:
require.js相对CommonJS太丑陋了
2013年8月 5日 16:36 | # | 引用
鬼舞 说:
终于看到是人看的了,介绍的很清楚.
2014年7月 2日 14:58 | # | 引用
tab3o1 说:
requireJS的shim可以拿来指定依赖
2014年9月 5日 17:48 | # | 引用
javascript的菜鸟 说:
你好,我有个疑问, 我比较菜,不好意思。
如果我用AMD的话:
require(['math'], function (math) {
math.add(2, 3);
});
math.add这个方法还是要等math这个模块加载完之后才会被调用,这不是跟同步一样的嘛?
2014年10月13日 15:18 | # | 引用
简陋的艺术家 说:
阮老师,非常感谢您的分享!学生在这里谢谢您了!祝您工作顺利,身体健康!
2014年10月20日 15:45 | # | 引用
昂帝梵德·纳尔 说:
这个是回调函数,math加载完后执行math.add(2,3)就不会出错。
而第一种,有可能math还没加载完时,执行math.add(2,3)会抛出错误。
讲到这里,我又发现一个问题了。跟朴灵一样,这里阮老师又没把同步异步的概念说清楚。
这个加载都是异步的。第一种没用AMD的回调函数,加载和执行是异步的,会出现异常。
第二种,加载是异步的,但用了回调,整个执行流程是同步的。
设置script.src的方式都是异步加载。
我在这看到了同步加载的办法。
http://www.cnblogs.com/ecalf/archive/2012/12/12/2813962.html
2014年11月 6日 00:18 | # | 引用
xinwendashibaike 说:
2014年12月17日 14:34 | # | 引用
amanda 说:
@昂帝梵德·纳尔
我也有同样的问题, 那么这样说其实所有的模块加载都是同步的,只是为了执行流程不抛出异常,才用require去规范模块之间的加载顺序,继而用回调函数处理. 我是不是太菜了......
2014年12月25日 10:09 | # | 引用
anyedage 说:
写的太清楚了!!!
2015年1月 8日 10:04 | # | 引用
王金有 说:
写得简明易懂太适合我这种小白看啦。以后就看你的文章啦~
2015年1月 8日 11:15 | # | 引用
阮少 说:
个人感觉所谓的同步异步的问题,还是看各个代码块的依赖关系,如果代码毫无依赖,从上之下依次执行毫无问题,可以看成同步执行。如果B代码依赖于A代码,A不执行完就执行B的话就会报错,所以只要A先于B执行完成即可,假设A执行所需要花的时间很长,而除了B以外的代码没有任何与AB的依赖关系,那么除了B需要阻塞一下,其他的代码可以直接执行,也就是所谓的异步,这样理解对否?
2015年1月18日 14:24 | # | 引用
小白白 说:
"它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行"
这句话的意思,实质上不还是同步的吗。等模块加载完才会执行回调啊,不解,到底异步是什么意思
2015年2月 4日 14:53 | # | 引用
ethanchen 说:
// 异步, 先加载完的先执行,可能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();
大概是这个意思吧?
2015年3月10日 16:12 | # | 引用
聂君 说:
一笔点睛,醍醐灌顶。
2015年3月17日 11:56 | # | 引用
古老二 说:
通俗易懂,厉害
2015年4月18日 04:01 | # | 引用
july 说:
前一个异步是针对这个模块与其他模块来说,这个模块(这个模块可能要执行很长时间,异步的可以放到最后执行)并不会阻塞其他模块的执行;这里说的同步是这个模块内部的执行是同步的(内部必须按严格执行顺序)---个人见解
2015年5月18日 23:16 | # | 引用
Cocos 说:
这里说的同步异步,不是你理解的那样.
所谓同步,就是脚本按照顺序加载,执行.假如脚本放在页面前,这是页面就要等脚本加载好执行完才能加载页面,浏览器会停留一段时间的空白显示...
异步就不同了,脚本位置和页面位置跟上面的一情况假设一样,这时浏览器不会先加载脚本,而是从上到下先加载整个HTML的字节,加载完再解析得到外部引用的JS脚本,再采用某种算法加载这些外部脚本,加载完成一个,就执行一次事先定义的回调方法.
总体说你的问题就是 观看的角度不同
2015年6月25日 18:11 | # | 引用
heipacker 说:
requirejs是来解决依赖的, 跟同步异步有毛关系 , 是我太菜了?
2015年6月27日 14:45 | # | 引用
连续 说:
一步步引入,清晰简明!
2015年7月 7日 23:46 | # | 引用
Jim.Yan 说:
但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
=====
请问这里怎么做到浏览器“假死”的等待状态?
2015年8月22日 15:38 | # | 引用
Jim.Yan 说:
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
============
请问这个依赖模块加载完成和上面CMD说浏览器“假死”等待状态有什么区别?
2015年8月22日 15:41 | # | 引用
Jim.Yan 说:
建议作者把文章的主题调整一下,直说AMD和CMD的区别,把文章中的“假死” 异步等相关介绍删除吧 误导初学者
2015年8月22日 15:44 | # | 引用
Fred 说:
本章中有一些不严谨的地方,就说下面两行代码
var math = require('math');
math.add(2, 3);
首先两行代码是顺序执行的,如果第一行代码是异步加载的,那么第二行代码就有可能会报错,如果第一行代码是同步加载的,那么第二行就不会报错,无非会等待一段阻塞时间
2015年10月14日 11:07 | # | 引用
小菜 说:
模块的加载都是异步(异步要和平时生活中反过来理解,异步是指一起做多件事情)的,而执行确实是同步(同步就是个异步相反,事情得一件一件的做,不能一起做多件事)的。所以当我们require多个模块时,这些模块是同时在加载(也就是异步),可能有的模块很小,最快加载完了,那它后面的回调函数就先执行了。
2015年10月24日 14:59 | # | 引用
ShiT 说:
小白飘过问一个弱智的问题,普通写js,通过dev Tools 打断点看JS代码的运行过程,但是采用了amd 模块化的js代码改如何断点进去
2015年11月18日 18:29 | # | 引用
cbtpro 说:
老师,我们用Eclipse编写AMD规范的JavaScript代码后,大纲(outline)视图就不能列出我们写的函数了。Ctrl + O也不能快速找到函数了。也不支持require方式的编程。我购买了spket,但是也只能解决部分js库的问题。还有其他什么解决方法吗?
2015年11月24日 15:33 | # | 引用
cbtpro 说:
可以在你需要进入断点的地方输入 debugger;
访问的时候打开开发者调试工具,走到debugger处就能进入断点。windows下按F12可以快速打开开发者调试工具。Mac下是Shift Control I。
2015年11月24日 15:36 | # | 引用
sungo 说:
文笔好的程序员就是让人仰慕! 一下子就豁然开朗了!!
2015年12月30日 19:02 | # | 引用
Mark 说:
阮老師 你好,新年快樂。
最近開始從頭看一下Commonjs 與AMD
以前胡裡胡塗的照用,現在有些想法想要釐清,
我的認知如下
Commonjs 與AMD 都只是一種標準(SPEC),
但是是非ES的標準,Commonjs 多用在Server 端的應用(ex: nodejs),
AMD是Web在用(ex: Requirejs),兩者最大目的都在載入模組,
最大的差異是Commonjs是同步 AMD是異步載入。
ES6(ES2015)開始有規範模組載入的標準,
不過我不清楚ES6 在同步或非同步的寫法上有何差異?
隨著瀏覽器的支援,ES6(以後的)標準將會取代Commonjs 與 AMD.
以上的觀念不知道有沒有錯誤的地方,再麻煩老師指正。
2016年1月 6日 02:53 | # | 引用
小牛也蒙圈 说:
你们可以把模块看成是一个.js文件,异步加载是指多个.js文件同时去加载,而这个时候callback还没有执行,等所有的.js文件加载完毕的时候,才开始调用这个callback。
2016年1月11日 18:13 | # | 引用
Bruce.Lee 说:
简洁明了,豁然开朗
2016年1月11日 19:56 | # | 引用
godcat 说:
简洁明了,学习啦
2016年7月12日 19:33 | # | 引用
xxq 说:
@昂帝梵德·纳尔:
我也想问的是这个,阮大神这个例子举的不太恰当啊。
2016年9月28日 10:41 | # | 引用
只爱叶子的夏天 说:
看完上一篇,马上看完这篇,一步步从0到理解。。。
2016年10月11日 01:26 | # | 引用
unclekeith 说:
不仅从阮老师的文章中学习到知识,从网友的评论中学到了很多很多。知识只有分享才有它存在的意义。
2016年10月20日 23:51 | # | 引用
李星阳 说:
感谢老师的分享
2016年11月18日 15:21 | # | 引用
meilideyiqie 说:
好久没有看到这么好的文章了,言简意赅,关键是通俗易懂!好!多多分享!
2016年12月 8日 13:44 | # | 引用
轰轰火火 说:
2017年2月10日 17:52 | # | 引用
陈帅 说:
大哥 写的真好,瞬间醍醐灌顶
2017年2月15日 11:44 | # | 引用
唐三藏 说:
峰哥的文章都很好,都讲的特别清晰易懂,我很多的知识都是在这里弄明白的
2017年2月15日 22:44 | # | 引用
王凯杰 说:
峰哥的文章好棒,我不得不赞你~~~
2017年2月23日 09:32 | # | 引用
林朝 说:
峰哥写得循序渐进, 系统地介绍了模块化开发这一技术, 小弟受益匪浅, 十分感谢!
2017年3月 8日 15:34 | # | 引用
椰汁 说:
不太明白,如果B依赖A,那按下面这样写不就好了:
这样自动就是A.js加载完执行完之后,再加载B.js执行B.js
为什么还要那么麻烦用什么require.js呢?
2017年4月 2日 13:48 | # | 引用
how to 说:
这不是依赖关系与执行顺序的问题。两种方法都是被依赖着先被创建,然后被调用。逻辑上没有问题。问题在于执行效率。
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)()};之后的的语句不用等它加载结束就可以执行。从而达到并发的效果。
2017年4月 5日 06:21 | # | 引用
刘静怡 说:
个人愚见,觉得举例不太恰当,假如有a和b两个模块,两个模块不存在依赖,打包后,a在b前面,如果是模块同步加载的,那么a模块一会等到b模块执行完再执行,会存在阻塞,所以模块是异步的才行,也就是说谁先加载完需要的模块,谁就会先执行,而模块内部采用的是回调函数,其实是一种同步的思想,也就是说你必须加载完你依赖的模块,你才能执行回调函数。 简而言之,模块之间是异步,模块内部回调是同步思想。
2017年4月21日 16:16 | # | 引用
匿名 说:
@East神奇:
先加载jQuery模块,在callback里加载backbone模块,在callback的callback里使用jQuery
2017年5月 4日 20:08 | # | 引用
老李 说:
说实话,看评论收获更大。。。这篇文章写的真心一般,同步异步基本等于不提。。
2017年5月 9日 23:02 | # | 引用
李帅武 说:
引用插件,首字母大写~
2017年5月31日 18:03 | # | 引用
leon 说:
大哥 !666!!!谢谢你提供这么好的文章!
2017年7月27日 10:36 | # | 引用
牛仔很忙 说:
关于AMD的模块化的同步异步问题困扰了我很长时间,听完大神的讲解,逐渐清晰了,谢谢!
2017年7月28日 10:53 | # | 引用
amy 说:
感谢分享,收益匪浅,看到好多您的入门教材,写的简明扼要,层层深入,也容易理解。
2017年8月10日 18:08 | # | 引用
追求梦想 说:
每次我搜索出内容,一看到是你写的,立马首先点开看,你写的通俗易懂,谢谢
2017年8月30日 12:42 | # | 引用
Neisun 说:
希望阮老师出一期sea.js
2017年9月27日 16:34 | # | 引用
小芦 说:
异步类似多线程,同步类似单线程。win界面程序为了保证界面不会卡死一下,会有个ui线程和其他线程,跟浏览器requirejs异步回调解决的问题是一样。
2017年10月19日 19:42 | # | 引用
Catu 说:
它后面的语句这句话你没看懂,后面的是指require模块外部的语句。
2017年10月30日 11:06 | # | 引用
无名 说:
广告太多。
2017年10月30日 14:58 | # | 引用
伊宇落 说:
math.add()与math模块加载不是同步的,意思是不必等到math模块加载完,math.add()就可以执行?math.add()不是在回调函数里吗?回调函数不是要等math模块加载完才能执行吗?
2017年11月 6日 15:56 | # | 引用
前端攻城狮 说:
我的理解是,commonJS中的require只是webpack打包的一个依据,根据这种require的依赖关系,打包成一个js文件(webpack也可以打包成多个文件),然后浏览器加载这个js文件!所有的模块都会一次性加载到浏览器,既然这样,那还有什么同步阻塞问题?
2017年11月 8日 16:49 | # | 引用
Andy 说:
看了好多关于CommonJS和AMD的文章,最后终于在你这篇文章中读明白了
2018年1月30日 18:37 | # | 引用
dinghao 说:
我的理解是, 同步和异步
首先是相对于这一行代码来讲的,
1 同步 意思是 执行这一行代码, 可以立刻拿到执行结果, 后面的代码可以直接用。
2 异步是 执行这一行代码, 但是这一代码是一很耗时的动作, 后面的代码需要等一会儿才能拿到这个动作的执行结果, 这个叫做异步。
区别就是 动作执行和拿到结果是否有时间 间隔。
其次 对于多行代码来讲,
如果 这些代码都没有耗时操作, 顺序执行 ,那就是同步, 如果下一行需要等上一行执行结果, 就是异步
以上是个人理解, 有错误请指教
2019年7月17日 10:56 | # | 引用
dinghao 说:
我的理解是, 同步和异步
首先是相对于这一行代码来讲的,
1 同步 意思是 执行这一行代码, 可以立刻拿到执行结果, 后面的代码可以直接用。
2 异步是 执行这一行代码, 但是这一代码是一很耗时的动作, 后面的代码需要等一会儿才能拿到这个动作的执行结果, 这个叫做异步。
区别就是 动作执行和拿到结果是否有时间 间隔。
其次 对于多行代码来讲,
如果 这些代码都没有耗时操作, 顺序执行 ,那就是同步, 如果下一行需要等上一行执行结果, 就是异步
以上是个人理解, 有错误请指教
2019年7月17日 12:32 | # | 引用