requirejs入门到精通

更多内容查看API:http://www.requirejs.cn/

使用了块作用域来申明function防止污染全局变量

声明不同js文件之间的依赖

可以按需、并行、延时载入js

可以让我们的代码以模块化的方式组织

通常使用requirejs的话,我们只需要导入requirejs即可,不需要显式导入其它的js库,因为这个工作会交给requirejs来做。

data-main 是告诉requirejs:你下载完以后,马上去载入真正的入口文件。它一般用来对requirejs进行配置,并且载入真正的程序模块。

<script src="/path/to/require.js" data-main="/path/to/app/config.js">script>

requirejs下。其主要API主要是下面三个函数:

  • define– 该函数用户创建模块。每个模块拥有一个唯一的模块ID,它被用于RequireJS的运行时函数,define函数是一个全局函数,不需要使用requirejs命名空间.
  • require– 该函数用于读取依赖。同样它是一个全局函数,不需要使用requirejs命名空间.
  • config– 该函数用于配置RequireJS.
require.config配置参数选项
  • baseUrl——用于加载模块的根路径。
  • paths——用于映射不存在根路径下面的模块路径。
  • shims——配置在脚本/模块外面并没有使用RequireJS的函数依赖并且初始化函数。假设underscore并没有使用  RequireJS定义,但是你还是想通过RequireJS来使用它,那么你就需要在配置中把它定义为一个shim。
  • deps——加载依赖关系数组
config.js 中通常用来做两件事:
  1. 配置requirejs 比如项目中用到哪些模块,文件路径是什么
  2. 载入程序主模块

requirejs一共提供了两个全局变量:

  1. requirejs/require: 用来配置requirejs及载入入口模块。如果其中一个命名被其它库使用了,我们可以用另一个
  2. define: 定义一个模块

另外还可以把 require 当作依赖的模块,然后调用它的方法:

define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});

依赖一个不使用requirejs方式的库

如果没用define(...) 定义模块比如这个 hello.js按普通方式定义了一个函数,能在requirejs里使用它吗?

function hello() {
  alert("hello, world~");
}

先看下面不能正确工作的代码:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    hello: 'hello'
  }
});

requirejs(['hello'], function(hello) {
  hello();
});

这段代码会报错,提示:   Uncaught TypeError: undefinedisnot a function

hello 是个undefined. 这说明,虽然我们依赖了一个js库(它会被载入),但requirejs无法从中拿到代表它的对象注入进来供我们使用。

在这种情况下,我们要使用shim ,将某个依赖中的某个全局变量暴露给requirejs,当作这个模块本身的引用。

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    hello: 'hello'
  },
  shim: {
    hello: { exports: 'hello' }
  }
});

requirejs(['hello'], function(hello) {
  hello();
});

再运行就正常了。

上面代码 exports: 'hello' 中的 hello ,是我们在 hello.js 中定义的 hello 函数。当我们使用function hello() {} 的方式定义一个函数的时候,它就是全局可用的。如果我们选择了把它 export 给requirejs,那当我们的代码依赖于 hello 模块的时候,就可以拿到这个hello 函数的引用了。

所以: exports 可以把某个非requirejs方式的代码中的某一个全局变量暴露出去,当作该模块以引用。

暴露多个变量:init

如果我要同时暴露多个全局变量呢?比如, hello.js 的定义其实是这样的:

function hello() {
  alert("hello, world~");
}
function hello2() {
  alert("hello, world, again~");
}

它定义了两个函数,而我两个都想要。这时就不能再用exports了,必须换成 init 函数:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    hello: 'hello'
  },
  shim: {
    hello: {
      init: function() {
        return {
          hello: hello,
          hello2: hello2
        }
      }
    }
  }
});

requirejs(['hello'], function(hello) {
  hello.hello1();
  hello.hello2();
});

当 exports 与 init 同时存在的时候, exports 将被忽略。

无主的与有主的模块

为什么我只能使用 jquery 来依赖jquery, 而不能用其它的名字?比如下面这段代码:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    myjquery: 'lib/jquery/jquery'
  }
});

requirejs(['myjquery'], function(jq) {
  alert(jq);
});

它会提示我:jqisundefined

但我仅仅改个名字:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    jquery: 'lib/jquery/jquery'
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery", "js/jquery"],   /// 这样配置后,当百度的jquery没有加载成功后,会加载本地js目录下的jquery
}}); requirejs([ 'jquery'], function(jq) { alert(jq);});

就一切正常了,能打印出jq相应的对象了。为什么?

有主的模块

经常研究,发现原来在jquery中已经定义了:

define('jquery', [], function() { ... });

它这里的 define 跟我们前面看到的 app.js 不同,在于它多了第一个参数 'jquery' ,表示给当前这个模块起了名字 jquery ,它已经是有主的了,只能属于 jquery .

所以当我们使用另一个名字:

myjquery: 'lib/jquery/jquery'

去引用这个库的时候,它会发现,在 jquery.js 里声明的模块名 jquery 与我自己使用的模块名 myjquery 不同,便不会把它赋给 myjquery ,所以 myjquery 的值是 undefined 。

所以我们在使用一个第三方的时候,一定要注意它是否声明了一个确定的模块名。

无主的模块

如果我们不指明模块名,就像这样:

define([...], function() {
  ...
});

那么它就是无主的模块。我们可以在 requirejs.config 里,使用任意一个模块名来引用它。这样的话,就让我们的命名非常自由,大部分的模块就是无主的。

为什么有的有主,有的无主

可以看到,无主的模块使用起来非常自由,为什么某些库(jquery, underscore)要把自己声明为有主的呢?

按某些说法,这么做是出于性能的考虑。因为像 jquery , underscore 这样的基础库,经常被其它的库依赖。如果声明为无主的,那么其它的库很可能起不同的模块名,这样当我们使用它们时,就可能会多次载入jquery/underscore。

而把它们声明为有主的,那么所有的模块只能使用同一个名字引用它们,这样系统就只会载入它们一次。

挖墙角

对于有主的模块,我们还有一种方式可以挖墙角:不把它们当作满足requirejs规范的模块,而当作普通js库,然后在 shim 中导出它们定义的全局变量。

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    myjquery: 'lib/jquery/jquery'
  },
  shim: {
    myjquery: { exports: 'jQuery' }
  }
});

requirejs(['myjquery'], function(jq) {
  alert(jq);
});

这样通过暴露 jQuery 这个全局变量给 myjquery ,我们就能正常的使用它了。

不过我们完全没有必要这么挖墙角,因为对于我们来说,似乎没有任何好处。

如何完全不让jquery污染全局的$

在前面引用jquery的这几种方式中,我们虽然可以以模块的方式拿到jquery模块的引用,但是还是可以在任何地方使用全局变量 jQuery 和 $ 。有没有办法让jquery完全不污染这两个变量?

在init中调用noConflict (无效)

首先尝试一种最简单但是不工作的方式:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    jquery: 'lib/jquery/jquery'
  },
  shim: {
    jquery: {
      init: function() {
        return jQuery.noConflict(true);
      }
    }
  }
});

requirejs(['jquery'], function(jq) {
  alert($);
});

这样是不工作的,还是会弹出来一个非 undefined 的值。其原因是,一旦requirejs为模块名jquery 找到了属于它的模块,它就会忽略 shim 中相应的内容。也就是说,下面这段代码完全没有执行:

jquery: {
  init: function() {
    return jQuery.noConflict(true);
  }
}

使用另一个名字

如果我们使用挖墙角的方式来使用jquery,如下:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    myjquery: 'lib/jquery/jquery'
  },
  shim: {
    myjquery: {
      init: function() {
        return jQuery.noConflict(true);
      }
    }
  }
});

requirejs(['myjquery'], function(jq) {
  alert($);
});

这样的确有效,这时弹出来的就是一个 undefined 。但是这样做的问题是,如果我们引用的某个第三方库还是使用 jquery 来引用jquery,那么就会报“找不到模块”的错了。

我们要么得手动修改第三方模块的代码,要么再为它们提供一个 jquery 模块。但是使用后者的话,全局变量 $ 可能又重新被污染了。

但是如果我不使用shim这个参数的话,在最新版的requirejs2.1.15中(以前的版本我不太清楚),也可以通过require([‘XX’])来解决。

使用map

如果我们有办法能让在继续使用 jquery 这个模块名的同时,有机会调用jQuery.noConflict(true)就好了。

我们可以再定义一个模块,仅仅为了执行这句代码:

jquery-private.js

define(['jquery'], function(jq) {
  return jQuery.noConflict(true);
});

然后在入口处先调用它:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    jquery: 'lib/jquery/jquery',
    'jquery-private': 'jquery-private'
  }
});

requirejs(['jquery-private', 'jquery'], function() {
  alert($);
});

这样的确可行,但是还是会有问题: 我们必须小心的确保 jquery-private 永远是第一个被依赖,这样它才有机会尽早调用 jQuery.noConflict(true) 清除全局变量 $ 和 jQuery 。这种保证只能靠人,非常不可靠。

我们这时可以引入 map 配置,一劳永逸地解决这样问题:

requirejs.config({
  baseUrl: '/public/js',
  paths: {
    jquery: 'lib/jquery/jquery',
    'jquery-private': 'jquery-private'
  },
  map: {
    '*': { 'jquery': 'jquery-private'},
    'jquery-private': { 'jquery': 'jquery'}
  }
});

requirejs(['jquery'], function(jq) {
  alert($);
});

这样做,就解决了前面的问题:在除了jquery-private之外的任何依赖中,还可以直接使用jqurey这个模块名,并且总是被替换为对 jquery-private 的依赖,使得它最先被执行。

Map参数: Map参数是用来解决同一个模块不同版本的问题,比如在项目开发中,开发初期使用了jquery1.7版本,但是由于业务的需求需要引入jquery1.9以上的版本时候,但是又担心有些是依赖于jquery1.7的代码升级到1.9以上的时候会有问题,因此可以让一部分代码还是依赖于jquery1.7,薪增的代码依赖于jquery1.9.

现在我在入口文件app.js添加如下代码:

requirejs.config({

map: {

'app/a': {

'jquery': 'js/lib/jquery1.7.js'

},

'app/b': {

'jquery': 'js/lib/jquery1.9.1.js'

}

}

});

require(['app/a'],function(jq){   

});

require(['app/b'],function(jq){  

});

然后在app/a.js添加如下代码:

// a.js

define(function (require, exports, module) {

var a = require(['jquery']);

});

在app/b.js添加如下代码:

// b.js

define(function (require, exports, module) {

var b = require(['jquery']);

});

在app.js中

require(['app/a'],function(jq){  

});时候,在加载app/a.js的时候会加载jquery1.7.js文件,在加载app/b.js的时候会加载jquery1.9.1.js.如下截图所示:

requirejs入门到精通_第1张图片

如果在app.js中把下面这行b.js代码初始化注释掉

require(['app/b'],function(jq){   

});

那么就只会加载app/a.js及对应的jquery1.7.js,截图如下:

requirejs入门到精通_第2张图片

相应的 如果把app/a.js初始化代码注释掉,把app/b.js代码初始化打开,那么只会加载jquery1.9.1,可以看到如果我想app/b.js中使用jquery1.9的话,那么可以这样使用了。

config参数:  config是指需要将配置信息传给一个模块,这些配置往往是application级别的信息,需要一个手段将他们向下传递给模块。在requireJS中,基于requirejs.config()的config配置项来实现。要获取这些信息的模块可以加载特殊的依赖 ”moudle” ,并调用module.config().

首先我们可以还是试着做demo来理解下上面话的意思吧,我现在在项目requirejs下js/app文件下新建一个d.js. 然后在app.js初始化文件加入如下代码:

requirejs.config({

config: {

'app/c': {

size: 'large'

},

'app/d': {

color: 'blue'

}

}

});

require(['app/c'],function(c){

console.log(c);

});

require(['app/d'],function(dss){

console.log(d);

});

在c.js里面这样写代码:

define(function (require, exports, module) {

//其值是'large'

var size = module.config().size;

return size;

});

在控制台下运行可以看到能打印出 large值出来,这说明我们可以通过config配置项来给app/c.js传递一个模块信息,比如如上面的一个对象{size:large},而在c.js里面直接可以通过module.config()方法来获取size的值。

下面我们可以使用一个依赖数组来做同样的事情,如下d.js代码:

define(['module'], function (module) {

//Will be the value 'blue'

var color = module.config().color;

return color;

});

在控制台看 也一样可以打印出color值出来。


如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:

  // 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));

  });

jQuery的插件可以这样定义:

  shim: {

    'jquery.scroll': {

      deps: ['jquery'],           ////   deps数组,表明该模块的依赖性。

      exports: 'jQuery.fn.scroll'

    }

  }

require.js还提供一系列插件,实现一些特定的功能。

domready插件,可以让回调函数在页面DOM结构加载完成后再运行。

  require(['domready!'], function (doc){

    // called once the DOM is ready

  });

text和image插件,则是允许require.js加载文本和图片文件。

  define([

    'text!review.txt',

    'image!cat.jpg'

    ],

    function(review,cat){

      console.log(review);

      document.body.appendChild(cat);

    }

  );

类似的插件还有json和mdown,用于加载json文件和markdown文件。

  1. 插件形式的非AMD模块,我们经常会用到jquery插件,而且这些插件基本都不符合AMD规范,比如jquery.form插件,这时候就需要将form插件"垫"到jquery中:
require.config({
    shim: {
        "underscore" : {
            exports : "_";
        },
        "jquery.form" : {
            deps : ["jquery"]
        }
    }
})

也可以简写为:

require.config({
    shim: {
        "underscore" : {
            exports : "_";
        },
        "jquery.form" : ["jquery"]
    }
})

这样配置之后我们就可以使用加载插件后的jquery了

require.config(["jquery", "jquery.form"], function($){
    $(function(){
        $("#form").ajaxSubmit({...});
    })
})



define 从名字就可以看出这个api是用来定义一个模块

require 加载依赖模块,并执行加载完后的回调函数(注意require中的依赖是一个数组,即使只有一个依赖,你也必须使用数组来定义)

paths还有一个重要的功能,就是可以配置多个路径,如果远程cdn库没有加载成功,可以加载本地的库

之前的例子中加载模块都是本地js,但是大部分情况下网页需要加载的JS可能来自本地服务器、其他网站或CDN,这样就不能通过这种方式来加载了

require.config({

    paths : {

        "jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]   

    }

})

callback函数中有$参数,这就是依赖的jquery模块的输出变量,如果你依赖多个模块,可以依次写入多个参数来使用:

require(["jquery","underscore"],function($, _){

    $(function(){

        _.each([1,2,3],alert);

    })

})

如果某个模块不输出变量值,则没有,所以尽量将输出的模块写在前面,防止位置错乱引发误解

require会默认的将data-main指定的js为根路径,



requirejs入门到精通_第3张图片


AMD模块规范

第一种写法:

define(function() {

return {

mix: function(source, target) { }

};

});

第二种写法 有依赖项如下:

define(['data', 'ui'], function(data, ui) {

// init here

});

第三种写法 直接一个对象

define({

data: [],

ui: []

});

第四种写法 有名模块如下:

define('index', ['data','base'], function(data, base) {

// todo

});  

有名模块要移动到其他目录时,JS也要跟着改,所以代码维护方面不好

第五种写法 包装模块如下:

define(function(require, exports, module) {

var base = require('base');

exports.show = function() {

// todo with module base

}  

});

:书写格式和nodeJS比较像,可以使用require获取模块,使用exports或者module.exports导出API

书写requireJS遵循一个文件一个模块。

内部机制:

RequireJS加载的每个模块作为script Tag,使用head.appendChild()方法。

在模块的定义时,requireJS等到所有的依赖都加载完毕,会为函数的调用计算出正确的顺序,然后在函数中通过正确的顺序进行调用。

requireJS函数增加了第三个参数errbacks

 requirejs入门到精通_第4张图片

在模块载入失败回调中可以使用undef函数移除模块的注册。

 requirejs入门到精通_第5张图片

 转载:http://www.tuicool.com/articles/jam2Anv

更多内容查看API:http://www.requirejs.cn/

你可能感兴趣的:(js库)