requireJS教程(一)

requireJS下载:https://requirejs.org/

一、requireJS作用

1)实现js文件的异步加载,避免网页失去响应;

2)管理模块之间的依赖性,便于代码的编写和维护。实现代码模块化

什么是模块化?模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块(但这种方式的模块化缺点明显)

有了模块,能够方便地引用别人的代码。nodejs的诞生意味着模块化编程的诞生。nodejs模块系统参照的是CommonJS规范实现。

有了服务端模块化,大家也想要客户端模块化。但CommonJS不适应浏览器,因为它是同步加载的。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。而requireJS就是遵循AMD规范的。

二、requireJS使用

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

  • define()– 该函数用户创建模块。每个模块拥有一个唯一的模块ID,它被用于RequireJS的运行时函数,define函数是一个全局函数,不需要使用requirejs命名空间.
  • require()– 该函数用于读取依赖。同样它是一个全局函数,不需要使用requirejs命名空间.
  • require.config()– 该函数用于配置RequireJS.

另外还可以把 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通常用来做两件事:

  • 配置requirejs。 比如项目中用到哪些模块,文件路径是什么
  • 载入程序主模块

config.js通过require.config配置参数选项:

  • baseUrl——用于加载模块的根路径。
  • paths——用于映射不存在根路径下面的模块路径。
  • shim——配置在脚本/模块外面并没有使用RequireJS的函数依赖并且初始化函数。假设jQuery并没有使用  RequireJS定义,但是你还是想通过RequireJS来使用它,那么你就需要在配置中把它定义为一个shim。只有当require获取不到模块时才会去shims中寻找。
  • deps——加载依赖关系数组
  • map——

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的方式有如下三种:

  • 用requirejs.config显示指定;
  • 如果指定了Entry Point(data-main),则baseUrl为data-main所指的js的目录;
  • 如果上述均未指定,则baseUrl为运行RequireJS的HTML文件所在目录。

一般module ID会根据baseUrl + paths配置去查询对应模块。但是,按照官方描述如果具备以下三种特性之一,则module ID会被当做普通路径处理,相对于运行RequireJS的HTML文件所在目录去查找文件。

  • module ID以.js结尾,比如lib/hello.js、hello.js
  • 以“/”开始(操作系统根目录/);
  • 包含url协议:如"http:"、"https"。
    比如下面的目录结构:
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文件所在的目录:

requireJS教程(一)_第1张图片

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"
    }
  });
  • baseUrl是设置基准目录。
  • paths属性指定各个模块的加载路径,指定模块id和文件路径的映射关系。可以是一个目录,也可以是一个文件。

配置好后,直接根据模块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)区别

  • 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
  • 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。RequireJS会预先加载,并且是异步加载,加载顺序而且执行顺序
  • 推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
  • 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
  • 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
  • 文档、学习难度。RequireJS的文档比较完善,较容易上手

四、参考文章

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/




你可能感兴趣的:(JS)