RequireJS 使用详解

时间:2024-05-30 17:14:37
require.js 主要解决两个问题 :
    1> 实现js文件的异步加载,避免网页失去响应
  2> 管理模块之间的依赖性,便于代码的编写和维护


require.js的加载
1> 引入 require.js 文件
<script src="js/require.js" async="true" defer></script>
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上
2> 加载自己的 js 文件
<script src="js/require.js" data-main="js/main"></script>
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/ 下载
2> 进入到工程 <root> 目录,并执行压缩指令
$ cd /Users/mew/Desktop/AllMyFile/CompanyReposity/Study/Webstorm/Test1/js    ## 进入到项目根目录
$  node /Users/mew/Desktop/r.js -o build.js    ## 编译项目

build.js 文件中的内容 
({
    appDir: "./", // 应用程序的目录(即<root>),在这个文件夹下的所有文件将会被复制到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"]
        }
    }
})

目录结构截图
RequireJS 使用详解