require.js 介绍
//-------------------------------------------------------------
AMD规范
前端开发在近一两年发展的非常快,JavaScript作为主流的开发语言得到了前所未有的热捧。大量的前端框架出现了,这些框架都在尝试着解决一些前端开发中的共性问题,但是实现又不尽相同。在这个背景下,CommonJS社区诞生了,为了让前端框架发展的更加成熟,CommonJS鼓励开发人员一起在社区里为一些完成特定功能的框架制定规范。AMD(Asynchronous Module Definition)就是其中的一个规范。
传统JavaScript代码的问题
让我们来看看一般情况下JavaScript代码是如何开发的:通过<script>标签来载入JavaScript文件,用全局变量来区分不同的功能代码,全局变量之间的依赖关系需要显式的通过指定其加载顺序来解决,发布应用时要通过工具来压缩所有的JavaScript代码到一个文件。当Web项目变得非常庞大,前端模块非常多的时候,手动管理这些全局变量间的依赖关系就变得很困难,这种做法显得非常的低效。
AMD的引入
AMD提出了一种基于模块的异步加载JavaScript代码的机制,它推荐开发人员将JavaScript代码封装进一个个模块,对全局对象的依赖变成了对其他模块的依赖,无须再声明一大堆的全局变量。通过延迟和按需加载来解决各个模块的依赖关系。模块化的JavaScript代码好处很明显,各个功能组件的松耦合性可以极大的提升代码的复用性、可维护性。这种非阻塞式的并发式快速加载JavaScript代码,使Web页面上其他不依赖JavaScript代码的UI元素,如图片、CSS以及其他DOM节点得以先加载完毕,Web页面加载速度更快,用户也得到更好的体验。
CommonJS的AMD规范中只定义了一个全局的方法,如清单1所示。
清单1.AMD规范
define(id?, dependencies?, factory);
该方法用来定义一个JavaScript模块,开发人员可以用这个方法来将部分功能模块封装在这个define方法体内。
id表示该模块的标识,为可选参数。
dependencies是一个字符串Array,表示该模块依赖的其他所有模块标识,模块依赖必须在真正执行具体的factory方法前解决,这些依赖对象加载执行以后的返回值,可以以默认的顺序作为factory方法的参数。dependencies也是可选参数,当用户不提供该参数时,实现AMD的框架应提供默认值为[“require”,”exports”,“module”]。
factory是一个用于执行改模块的方法,它可以使用前面dependencies里声明的其他依赖模块的返回值作为参数,若该方法有返回值,当该模块被其他模块依赖时,返回值就是该模块的输出。
CommonJS在规范中并没有详细规定其他的方法,一些主要的AMD框架如RequireJS、curl、bdload等都实现了define方法,同时各个框架都有自己的补充使得其API更实用。
require.js意义
//-------------------------------------------------------------
模块化
其实在计算机领域,模块化的概念被推崇了近四十年。软件总体结构体现模块化思想,即把软件划分为一些独立命名的部件,每个部件称为一个模块,当把所有模块组装在一起的时候,便可获得问题的一个解。
模块化以分治法为依据,但是否意味着我们把软件无限制的细分下去。事实上当分割过细,模块总数增多,每个模块的成本确实减少了,但模块接口所需代价随之增加。要确保模块的合理分割则须了解信息隐藏,内聚度及耦合度。
这里的JavaScript模块与传统的JavaScript代码不一样的地方在于它无须访问全局的变量。模块化的设计使得JavaScript代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者函数,有效的避免大量而且复杂的命名空间管理。
如同CommonJS的AMD规范所述,定义JavaScript模块是通过define方法来实现的。
命名空间
Javascript因为天生的缺点,语言本身没有集成命名空间的概念,所以变量名、函数名很容易发生冲突。这些年,大家想尽了各种办法,给 js 添加命名空间的概念,其中最成熟的套路,就是 RequireJS 这种。
如果你对 c# 或 java 语言的命名空间有一点点了解,那么,RequireJS 的用法和作用,可以用 c# 中的两行代码来类比说明:
using xx=wojilu.Core;
namespace MyApp {
public class MyClass {
public void MyTest() {}
}
}
require.js主要的功能,就是实现c#代码的第一行和第二行的功能:1)引入需要使用的命名空间(顺便加个别名也行);2)将自己的代码放到命名空间中,避免全局污染。
define( ['wojilu.Core'], function(xx) {
return {
MyTest : function() {alert( 'wojilu1 module' );}
};
});
和上面的c#代码对比一下,require.js 同样也做了两件事情:
1、引入命名空间 wojilu.Core,同时给它取了一个别名 xx;
2、将自己的所有代码放在 define 中,避免全局化的污染冲突。
总之,require.js定义了(define)一个命名空间,在定义的时候,顺便引用了需要使用其他命名空间。我们注意到,按照 require.js 的术语,它把命名空间叫做“ 模块”。注意,在这里, require.js 定义的模块(命名空间)是匿名的,没有取名,这是和c#不同的地方。
require.js实战
//-------------------------------------------------------------引入require.js
<script src="require.js"></script>
有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:
<script src="js/require.js" defer async="true" ></script>
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。
这种写法虽然简单,但其实并不推荐,一般的写法还要再加个属性:
<script data-main="js/main" src="js/require-jquery.js"></script>
就像一个c程序总有一个 main 方法作为入口一样,上面这种写法,做了几件事情:
1、加载了 require-jquery.js 文件。注意,官方提供了 require.js和 jquery 的打包版本,推荐。
2、在加载之后,加载入口文件 js/main.js ,注意,main.js 写进去的时候,不需要后缀名。
你的所有其他 js 模块文件,都可以写在 main.js 里面,通过 main.js 加载。
require.js用法(一):define定义模块
define( ['wojilu.test1'], function( t1 ) {
return {
NewTest : function() { t1.MyTest();}
};
});
通过文件名(不需要后缀名),引入了 wojilu.test1 命名空间,并给它取了别名 t1,然后在代码中使用 t1.MyTest() 方法。
按照 RequireJS 的规范,所有的模块定义,都必须放在 return {} 对象中。也就是说,你的代码都要写在 return 返回的 {} 对象里面。这会不会导致代码臃肿难看?当然不会。你可以重构一下,比如这样做:
define(['wojilu.test1'], function (t1) {
function someFunc1() {
// 实际主要代码
}
function someFunc2() {
// 实际主要代码
}
// 通过 return 方式,将需要公开的函数暴露出来,供其他 js 调用
return {
NewTest: function () {
t1.MyTest();
},
fun1: someFunc1,
fun2: someFunc2
};
});
require.js用法(二):require加载js文件
到此为止,我们遇到了两个关键词,一个是 define ,可以用来定义模块(命名空间),第一部分我们讲了;还有一个是 require,可以直接加载其他 js。它除了简单的用法:
<script>
require( ["some" ] );
</script>
之外,还有和 define 类似的复杂用法:
<script>
require(["aModule", "bModule"], function() {
myFunctionA(); // 使用 aModule.js 中的函数 myFunctionA
myFunctionB(); // 使用 bModule.js 中的函数 myFunctionB
});
</script>
总结一下,define 是你定义自己的模块的时候使用,可以顺便加载其他js;require 直截了当,供你加载用的,它就是一个加载方法,加载的时候,可以定义别名。
require.js用法(三):require.config()对模块加载行为自定义
require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。
require.config({
paths: {
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
另一种则是直接改变基目录(baseUrl)。
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
如果某个模块在另一台主机上,也可以直接指定它的网址,比如:
require.config({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
}
});
require.js用法(四):define模块返回函数个数问题
1、define 的return只有一个函数,require的回调函数可以直接用别名代替该函数名。
2、define 的return当有多个函数,require的回调函数必须用别名调用所有的函数。
require(['selector','book'], function(query,book) {
var els = query('.wrapper');
book.fun1();
book.fun2();
});
此处query 函数是1的情况,book 函数是2的情况。
require.js用法(五):加载的js文件不是AMD规范定义
当调用的js文件不是使用define定义,直接使用其全局变量或函数就可以了,没有影响。
这个时候只是控制了js 文件的加载顺序。
@@部分资料来自网络,感谢伟大的乐于分享的程序猿们!
require.js:http://www.requirejs.org
require.js入门教程:http://www.verydemo.com/demo_c110_i2031.html
使用RequireJS优化Web应用前端:http://www.csdn.net/article/2012-09-27/2810404
require.js链接汇总:http://webeginner.diandian.com/post/2013-12-04/40060318517