require.js 主要解决两个问题 :
1> 实现js文件的异步加载,避免网页失去响应
2> 管理模块之间的依赖性,便于代码的编写和维护
官网 : http://requirejs.org/
GitHub : https://github.com/requirejs/requirejs
require.js的加载
1> 引入 require.js 文件
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上
2> 加载自己的 js 文件
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main
主模块是整个网页的入口代码,它有点像C语言的main()函数,所有代码都从这儿开始运行
主模块的写法
主模块不依赖任何其他模块,那么可以直接写入javascript代码
// main.js
alert("加载成功!");
常见的情况是,主模块依赖于其它模块,这时就要使用AMD规范定义的的require()函数
// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题
注 :
默认情况下,require.js与这三个模块在同一个目录,其对应
moduleA.js,moduleB
.js
和 moduleC
.js,然后自动加载
模块的加载
使用 require.config()方法,可以对模块的加载行为进行自定义。require.config()就写在主模块 (main.js) 的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径
require.config({
paths: {
// baseUrl: "js/lib", 指定基目录
"backbone": "backbone.min",
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min”,
// 直接指定网址
"underscore": "lib/underscore.min",
}
});
require.js要求,每个模块是一个单独的js文件,可以使用 require.js 优化工具将多模块合并
define 函数
模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数 (定义此模块的那个函数) 参数的形式将这些依赖进行注入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其他模块。 RequireJS的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本
在定义一个模块的时候,方法的第一行写一个 “use strict”,用于表示 严格模式,这种模式使得 JavaScript 在更严格的条件下运行,其优点 :
1> 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为
2> 消除代码运行的一些不安全之处,保证代码运行的安全
3> 提高编译器效率,增加运行速度
1> 简单的值对模块 : 把文件名称作为name参数传入,若依赖该组件那么返回的就是一个对
define({
name
:
"hehe"
,
age
:
"18"
});
2> 简单的函数模块 : 和上面一样文件名称作为默认的 name,与上面的差异是,可以提前执行返回值外的代码
define(
function
() {
return
{
name
:
"hehe"
,
age
:
"18"
};
});
3> 依赖函数模块 : 和上面一样,关键是依赖模块是以返回值作为入参的形式传入,如果加载错误或者没有找到对应的模块,那么得到的入参是Undefiend
define([
'angular'
,
'jsUtil'
,
'modules/meet/modules'
,
'modules/meet/services/Meet'
,
'modules/meet/services/MeetRemoteService'
],
function
(require) {
'use strict'
;
var
module
= angular.module(
'meet.services'
);
module
.factory(
'MeetService'
,
function
(Meet, MeetRemoteService) {
var
service
= {
name
:
'hehe'
,
age
:
'18'
};
return
service
;
});
});
4> 返回函数模块 : 和上面一样,这里返回的是函数,在依赖模块中把它作为函数对象调用即可,其实这是一个简单的闭包
define([
'angular'
,
'jsUtil'
,
'modules/meet/modules'
,
'modules/meet/services/Meet'
,
'modules/meet/services/MeetRemoteService'
],
function
(require) {
'use strict'
;
var
module
= angular.module(
'meet.services'
);
module
.factory(
'MeetService'
,
function
(Meet, MeetRemoteService) {
var
service
= {};
service
.
getWeekOfMeet
=
function
(weekFlag, date) {
return
MeetRemoteService.
get
(weekFlag, date).
then
(
function
(data) {
data.
content
= Meet.sortMeet(data.
content
);
return
data;
});
}
return
service
;
});
});
5> 完整定义 : 下面就是完整定义,有名称,有依赖,有回调,内部还有common 的形式引入依赖对象
define(
'sample3'
, [
'sample'
,
'sample1'
],
function
(sample, sample1) {
var
sample4
= require(
'sample4'
);
return function
() {
alert
(sample.
name
+
':'
+ sample.sayhell());
}
});
define函数 name 和 require函数 依赖名称之间的关系
define(name, [], callback) : 这个name可以省掉,默认是文件名称;当然也可以自定义,一旦定义name,根据源代码可以发现 define函数内部其实就是把这个 name以及依赖模块、回调函数作为一个对象存储在全局的数组当中,也就是 defQueue.push([name, deps, callback]);那么这个name就是这个组件注册的的ID
require([name, name2], callback) : 系统首先会在全文检索path中是否对应的路径,如果没有自然把他作为路径拼接在 baseUrl 上去异步加载这个js文件,加载时从源代码中可以看到 ,var data = getScriptData(evt);返回的 data.id 其实就是name,然后执行contex.completeLoad(node.id),其内部就很清楚了,把define中注册的name和这里得到的name进行比较如果相等就执行。所以道理就是:require 和 define 的 name 必须保证一致
AMD模块的写法
require.js加载的模块,采用AMD规范,也就是说,模块必须按照AMD的规定来写。具体来说,模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中,如下代码 :
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return { add: add };
});
加载方法如下 :
// main.js
require(['math'], function (math){
alert(math.add(1,1));
});
如果这个模块还依赖其它模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性 :
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return { foo : foo };
});
当require()函数加载上面这个模块的时候,就会先加载myLib.js文件
注 : 1> jQuery 符合 AMD规范
2> 每个文件只能有一个
define 方法,且
define 方法定义的 Module 与文件名称相同
加载非规范的模块
在加载非规范的模块调用 require() 加载之前,要先用 require.config()方法,定义一些特征,如下代码 :
require.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery/jquery'
},
shim : {
'underscore' : {
exports : '_'
},
'backbone' : {
deps : ['underscore', 'jquery'],
exports : 'Backbone'
},
'jquery.scroll' : {
deps : ['jquery'],
exports : 'jQuery.fn.scroll'
},
'hello' : {
init : function() {
return {
hello: hello,
hello2: hello2
}
}
}
}
});
require(['hello'], function(hello) {
hello.hello1();
hello.hello2();
});
require.config() 接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义
1> exports值 (输出的变量名),表明这个模块外部调用时的名称
2> deps数组,表明该模块的依赖性
使用 shim ,将某个依赖中的某个全局变量暴露给requirejs,当作这个模块本身的引用
注 : 1> require.config 配置 shim 中 exports 的值,一定要与相关文件中暴露出全局变量名称一致。如果暴露出多个全局变量,那么 exports 可以指定其中任何一个,作为模块的返回结果。最好只使用一个全局变量,来减少冲突的可能性
2> 若要暴露多个变量则不能使用
exports,必须换成 init 函数
requirejs一共提供了两个全局变量 :
1> requirejs/require : 用来配置requirejs及载入入口模块,如果其中一个命名被其它库使用了,可以用另一个
2> define : 定义一个模块
注 : 1> 当一个 js 文件已经和一个模块绑定,当该 js 再次绑定一个模块使用的时候会报错
2> 对于经常使用的模块,在
require.config 中设置成有主,这样防止多次引入
如何完全不让jquery污染全局的$
require.js 插件
1> domready插件 : 让回调函数在页面DOM结构加载完成后再运行
require(['domready!'], function (doc) {
// called once the DOM is ready
});
2> text和image插件 : 允许require.js加载文本和图片文件
define([
'text!review.txt’,
'image!cat.jpg’
],
function(review, cat){
console.log(review);
document.body.appendChild(cat);
}
);
合并与压缩
RequireJS 可以将 JavaScript代码轻易的分割成苦干个模块(module)并且保持你的代码模块化与易维护性。这样,将获得一些具有互相依赖关系的 JavaScript文件。仅仅需要在的HTML文档中引用一个基于 RequireJS 的脚本文件,所有必须的文件都将会被自动引用到这个页面上
但是,在生产环境中将所有的 JavaScript文件分离,这是一个不好的做法。这会导致很多次请求(requests),即使这个些文件都很小,也会浪费很多时间。 可以通过合并这些脚本文件,以减少请求的次数达到节省加载时间的目的
另一种节省加载时间的技巧是缩小这些被加载文件的大小,相对小一些的文件会传输的更快一些。这个过程叫作最小化 (minification) ,它是通过小心的改变脚本文件的代码结构并且不改变代码的形为(behavior)和功能(functionality)来实现的。例如这些:去除不必要的空格,缩短(mangling,或都压缩)变量(variables)名与函数(methods,或者叫方法)名,等等。这种合并并压缩文件的过程叫做代码优化( optimization)。这种方法除了用于优化(optimization)JavaScript文件,同样适用于CSS文件的优化
RequireJS有两个主要方法(method): define()和require()。这两个方法基本上拥有相同的定义(declaration) 并且它们都知道如何加载的依赖关系,然后执行一个回调函数(callback function)。与require()不同的是, define()用来存储代码作为一个已命名的模块。 因此define()的回调函数需要有一个返回值作为这个模块定义。这些类似被定义的模块叫作AMD (Asynchronous Module Definition,异步模块定义)
使用 RequireJS Optimizer(RequireJS优化器) 来压缩优化项目
RequireJS Optimizer 依赖于 node.js 可以在 https://nodejs.org/ 下载
1> 去 http://requirejs.org/docs/optimization.html#download 下载 r.js
2> 进入到工程 目录,并执行压缩指令
$ cd /Users/mew/Desktop/AllMyFile/CompanyReposity/Study/Webstorm/Test1/js
## 进入到项目根目录
$ node /Users/mew/Desktop/r.js -o build.js
## 编译项目
build.js 文件中的内容
({
appDir
:
"./"
,
// 应用程序的目录(即),在这个文件夹下的所有文件将会被复制到dir参数标注的文件夹下
baseUrl
:
"./"
,
// 存放模块的根目录,相对于appDir,代表查找文件的锚点(that represents the anchor path for finding files)
dir
:
"./dist"
,
// 输出目录,所有的应用程序文件将会被复制到该文件夹下
modules
: [
// 一个包含多个对象的数组,每个对象代表一个将被优化的模块(module)
{
name
:
"main"
// 模块的入口文件
}
],
// fileExclusionRegExp: /^(r|build)\.js$/, // 任何与此规则匹配的文件或文件夹都将不会被复制到输出目录。由于我们把r.js和build.js放置在应用程序目录下。若希望优化器(optimizer)排除这两个文件,因此可以这样设置/^(r|build)\.js$/
optimizeCss
:
"standard"
,
// RequireJS Optimizer会自动优化应用程序下的CSS文件。这个参数控制CSS最优化设置。允许的值: “none”, “standard”, “standard.keepLines”, “standard.keepComments”, “standard.keepComments.keepLines”
// removeCombined: true, // 如果为true,优化器(optimizer)将从输出目录中删除已合并的文件
paths
: {
// 模块(modules)的相对目录,和
require.config 中的 paths 蕾丝
"angular"
:
"../lib/angular/angular"
,
// 左边的是 module名称,右边的是路径
"angular-animate"
:
"../lib/angular/angular-animate"
,
"angular-touch"
:
"../lib/angular/angular-touch"
,
"angular-cookies"
:
"../lib/angular/angular-cookies"
,
"ui-router"
:
"../lib/angular-ui/angular-ui-router"
,
"ui-bootstrap"
:
"../lib/angular-ui/ui-bootstrap-tpls-2.5.0"
,
"ng-file-upload"
:
"../lib/ng-upload/ng-file-upload.min"
,
"hammer"
:
"../lib/hammer/hammer.min"
,
"ctrls"
:
"../js/ctrls"
,
"services"
:
"../js/services"
,
"directives"
:
"../js/directives"
,
"filters"
:
"../js/filters"
},
shim
: {
// 为那些没有使用define()声名依赖关系及设置模块值的模块,配置依赖关系与“浏览器全局”出口的脚本
,和
require.config 中的 shim 类似
"angular"
: {
exports
:
"angular"
},
"angular-animate"
: {
// 原模块名称
deps
: [
"angular"
],
// 当前模块的依赖性
exports
:
"angular-animate"
// 模块外部调用时的名称
},
"angular-touch"
: {
deps
: [
"angular"
],
exports
:
"angular-touch"
},
"angular-cookies"
: {
deps
: [
"angular"
],
exports
:
"angular-cookies"
},
"ui-router"
: {
deps
: [
"angular"
],
exports
:
"angular-ui"
},
"ui-bootstrap"
: {
deps
: [
"angular"
,
"angular-animate"
],
export
:
"ui-bootstrap"
},
"ng-file-upload"
: {
deps
: [
"angular"
]
}
}
})
目录结构截图