requireJS下载:https://requirejs.org/
一、requireJS作用
1)实现js文件的异步加载,避免网页失去响应;
2)管理模块之间的依赖性,便于代码的编写和维护。实现代码模块化
什么是模块化?模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块(但这种方式的模块化缺点明显)
有了模块,能够方便地引用别人的代码。nodejs的诞生意味着模块化编程的诞生。nodejs模块系统参照的是CommonJS规范实现。
有了服务端模块化,大家也想要客户端模块化。但CommonJS不适应浏览器,因为它是同步加载的。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。而requireJS就是遵循AMD规范的。
二、requireJS使用
requirejs主要API是下面三个函数:
另外还可以把 require 当作依赖的模块,然后调用它的方法:
define(["require"], function(require) {
var cssUrl = require.toUrl("./style.css"); //将文件路径转为绝对路径
});
1、加载requirejs并设置主模块
加载requirejs后要设置网页程序的主模块。比如:
data-main属性的作用是,指定网页程序的主模块。在上例中,就是根目录下的config.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把config.js简写成config。
config.js通常用来做两件事:
config.js通过require.config配置参数选项:
2、主模块的写法
如果主模块不依赖其他模块。可以直接写入js代码,但这样的话其实没必要使用requirejs
//main.js
alert("加载成功");
如果依赖其他模块,就要写成:
// main.js
require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
// some code here
});
require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是['jquery', 'underscore', 'backbone'],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
require()异步加载jquery, underscore,backbone,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
3、配置模块加载路径
在RequireJS中,设置baseURl的方式有如下三种:
一般module ID会根据baseUrl + paths配置去查询对应模块。但是,按照官方描述如果具备以下三种特性之一,则module ID会被当做普通路径处理,相对于运行RequireJS的HTML文件所在目录去查找文件。
js
--hello.js
--config.js
--main.js
index.html
index.html:
config.js:
requirejs.config({
baseUrl:'js/'
});
require(['main']); //根据baseUrl + paths + require值来查找文件
//上面这句可替换为require('js/main.js'),根据index.html所在目录 + require值来查找文件
main.js:
define(['hello'], function(hello){ //根据baseUrl + paths + require值来查找文件
//上面这句可替换为require('js/hello.js'),根据index.html所在目录 + require值来查找文件
//some code
});
相对路径./:define()如果相对路径./,是相对于module ID的值。比如设置了require('js/main.js'),main.js文件依赖hello.js:define(['./hello']), function(){})。此时加载的是js/hello.js。
路径处理流程总结如下,图中主目录指的运行requireJS的HTML文件所在的目录:
1)设置模块路径上面加载的jquery、underscore、backbone都是默认和main.js在同一目录。如果需要加载不同路径的js,则要在加载时写好路径,比如:
// main.js
require(['lib/jquery', 'lib/underscore', 'lib/backbone'], function ($, _, Backbone){ //js文件可加.js后缀,也可不加
// some code here
});
或者也可以require.config()方法设置加载的路径:
require.config({
baseUrl: "js",
paths: {
"jquery": "lib/jquery.min", //会自动加.js后缀。所以不要加.js后缀,否则找不到文件
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
配置好后,直接根据模块id加载即可:
// main.js
require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
// some code here
});
2)设置目录路径
当项目文件较多时,为每个js文件加配置比较麻烦。这时候可以设置加载目录。比如下面的目录:
lib
--cores
--cores1.js
--core2.js
--utils
--util1.js
--util2.js
可配置如下:
require.config({
baseUrl: "js",
paths: {
"cores": "lib/cores",
"utils": "lib/utils"
}
});
配置好后,根据目录+文件名自动查找:
// main.js
require(['cores/cores1', 'cores/cores2', 'utils/utils1', 'utils/utils2'], function(cores1, cores2, utils1, utils2){
//some code
});
4、AMD模块的写法
require.js加载的模块,采用AMD规范。
模块必须采用特定的define()函数来定义。一个文件只能定义一个模块。
1)模块为简单键值对的集合
这种情况直接把对象传给define()函数。比如翻译文件,就可以采用这种形式。
// lib/zh-cn.js
define({
account: "账号",
password: "密码"
});
加载:
// main.js
require(['lib/zh-cn'], function(lang){
//some code
});
2)函数式定义
define()可传入函数,这个函数的返回值可以是对象、函数等。如果没有依赖,直接传入函数:
// lib/zh-cn.js
define(function(){
return {
account: "账号",
password: "密码"
} //返回值对象、函数等
});
如果有依赖模块,第一个参数可以传入依赖模块数组;第二个参数传入函数,参数为获取到的依赖值。如下:
// lib/zh-cn.js
define(['jquery'], function($){
//some code
return {
//some code
} //返回值可以是对象、函数等
});
对模块的返回值类型并没有强制为一定是个object,任何函数的返回值都是允许的。此处是一个返回了函数的模块定义:
// lib/zh-cn.js
define(['jquery'], function($){
// some code
return function(){
// some code
}
});
3)定义简化CommonJS格式的模块
上面几种方式都不能在CommonJS被使用。如果CommonJS也能使用,用下面这种方式。
define(function(require, exports, module) {
var a = require('a'),
b = require('b');
//Return the module value
return function () {};
}
);
4)定义命名模块
你可能会看到有些模块命名了。
define("foo/title",
["my/cart", "my/inventory"],
function(cart, inventory) {
//Define foo/title object in here.
}
);
虽然可以显示指定模块名称,但是这种方式很不方便。如果文件目录变化了,名称就要相应改变。
假定现在有一个math.js文件,它定义了一个math模块。那么math.js就要这样写:
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
5、依赖非AMD规范的模块
如果没用define(...) 定义模块,比如hello.js:
function sayHello() {
alert("hello");
}
我们要使用shim ,将某个依赖中的某个全局变量暴露给requirejs,当作这个模块本身的引用。
requirejs.config({
baseUrl: '/js',
paths: {
hello: 'hello' //设置模块路径
},
shim: {
hello: {
//deps:[], deps表示当前模块的依赖模块
exports: 'sayHello'
} //设置模块要暴露的变量
}
});
requirejs(['hello'], function(sayHello) {
sayHello();
});
如果要同时暴露多个变量,要用init函数。exports和init如果同时存在,会忽略exports。
比如hello.js改成如下:
function sayHello1() {
alert("hello");
}
function sayHello2() {
alert("hello");
}
requirejs.config({
baseUrl: '/js',
paths: {
hello: 'hello' //设置模块路径
},
shim: {
hello: {
init: function() {
return {
sayHello1: sayHello1,
sayHello2: sayHello2
}
}
}
}
});
requirejs(['hello'], function(hello) {
hello.sayHello1();
});
6、加载有主、无主的模块
模块定义时如果指定了模块名称,这个模块就是有主的;否则就是无主的。
1)有命名的模块
require.config设置paths时jquery模块名必须设置jquery,如果设置成其他的,加载时会提示jquery没有定义。
这是因为jquery是有主模块。如下:
define('jquery', [], function() { ... });
对于有主模块,模块名称必须和命名一样:
require.config({
baseUrl:'./',
paths:{
jquery:'lib/jquery.min', //名字必须是jquery,改成其他无效
}
})
引用如下:
define(['jquery'],function(jQuery){
//some code
});
2)无主模块
如果定义模块时不指定模块名称:
define(['dependMoudle1', 'dependModule2'], function() {
//comdeCode
})
加载时可以在require.config设置任意一个模块名来引用它。
可以看到,无主的模块使用起来非常自由,为什么某些库(jquery, underscore)要把自己声明为有主的呢?
按某些说法,这么做是出于性能的考虑。因为像 jquery 这样的基础库,经常被其它的库依赖。如果声明为无主的,那么其它的库很可能起不同的模块名,这样当我们使用它们时,就可能会多次载入jquery/underscore。
而把它们声明为有主的,那么所有的模块只能使用同一个名字引用它们,这样系统就只会载入它们一次。
3)将有主模块改为其他名字(不推荐)
可以通过把有主模块当成不符合AMD规范的文件,然后在shim中导出全局变量。比如下面这个例子,为了加上让渡$变量的处理,如下:
requirejs.config({
baseUrl: '/lib',
paths: {
myjquery: 'jquery.min.js'
},
shim: {
myjquery: {
init: function() {
return jQuery.noConflict(true); //让渡$变量
}
}
}
});
requirejs(['myjquery'], function(jq) {
alert($);
});
但是这种方式不推荐使用。
7、map使用
map的作用:对于给定的模块前缀,使用一个不同的模块ID来加载该模块。
该手段对于某些大型项目很重要:如有两类模块需要使用不同版本的"foo",但它们之间仍需要一定的协同。 在那些基于上下文的多版本实现中很难做到这一点。而且,paths配置仅用于为模块ID设置root paths,而不是为了将一个模块ID映射到另一个。
假设项目目录如下:
js
--config.js
--old.js
--new.js
--foo1.js
--foo2.js
config.js如下:
requirejs.config({
map: { //读取不同版本的js。测试的时候发现foo1.js、foo2.js不能设置模块名称,一旦设置将无法获取。这是什么原因
"js/old": {
"foo": "js/foo1.js"
},
"js/new": {
"foo": "js/foo2.js"
}
}
});
//测试map属性
require(['js/old', 'js/new'], function(oldFile, newFile){
});
old.js、new.js代码一样,如下:
define(['foo'], function(foo){
console.log(foo);
});
foo1.js如下:
//不要设置模块名字
define({
name :'foo1'
});
foo2.js如下:
//不要设置模块名字
define({
name :'foo2'
});
运行时,old.js会加载了foo1.js,new.js会加载foo2.js。
foo1.js、foo2.js如果设置了模块名称,old.js、new.js获取到的foo为undefined,这是什么原因暂时还不清楚。三、requireJS和SeaJS区别
1)相同之处
RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。
2)区别
四、参考文章
1、(阮一峰)Javascript模块化编程(一):模块的写法http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
2、(阮一峰)Javascript模块化编程(二):AMD规范http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html
3、(阮一峰)Javascript模块化编程(三):require.js的用法
http://www.ruanyifeng.com/blog/2012/11/require_js.html
4、RequireJS路径深入详解:https://www.jianshu.com/p/99321f292776
5、requirejs入门到精通:https://blog.csdn.net/bluesky1215/article/details/71079667
6、requirejs官网:http://www.requirejs.cn/