JavaScript模块管理之RequireJS

前言

背景

JavaScript这门脚本语言,相对于Python等确实比较简陋、甚至于丑陋。比如在代码文件管理、和模块管理上,存在很多空白。

www.js8331.com 1

Paste_Image.png

模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数参数的形式将这些依赖进行注入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其他模块。
RequireJS的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本。

JavaScript语言的不足

对于JavaScript文件:

  • www.js8331.com,文件按<script>顺序加载,JavaScript本身缺少对文件依赖关系的管理。更重要的是:js文件的加载会阻塞页面渲染。
  • 在单页面Web应用中,只有一个页面,但许多script不需要在页面初始化的时候一次性加载,只需要在加载了某个子页面时加载——这在Python叫模块动态加载、很自然;而原生JavaScript中是没有任何机制可以实现的。——
    这是一个性能问题。

对于全局命名空间:

  • JavaScript所有文件中的代码都在同一个全局命名空间中,也就是说任何文件定义的全变量和全局函数都在全局命名空间。——这根python之类的语言又大不一样。这条特性有利于文件间共享数据,但全局空间太容易失控而引入Bug。

对于代码的模块化:

  • 即使不出现上述问题,模块化本身也是一个需求。模块化可以实现高内聚、低耦合的良好系统,但JavaScript没有提供模块化工具——我们至少需要“定义模块”和“引用模块”的功能。

在定义一个模块的时候,方法的第一行写一个“use strict”;这是干什么的?

注意到没:JavaScript的第三方库,都是单JS文件!!!

不论多大、不论多少行内!@#¥%……&*()——

use strict –严格模式,这种模式使得Javascript在更严格的条件下运行。

AMD 异步模块加载

AMD的核心思想是,按需加载JS模块。

基于 AMD(Asynchronous Module Definition)的 JavaScript
设计已经在目前较为流行的前端框架中大行其道,jQuery、Dojo、MooTools、EmbedJS
等纷纷在其最新版本中加入了对 AMD 的支持。

RequireJS呢、是AMD规范最好的实现者之一。

www.js8331.com 2

Paste_Image.png

消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度; 为未来新版本的Javascript做好铺垫。

RequireJS概述

RequireJS
是一个JavaScript模块加载器。它聚焦于JavaScript的模块管理和文件管理、不但解决了上诉所有JavaScript的遗留问题,而且提供了某些额外的便利(稍后分解)。

1. 简单的值对模块

RequireJS模块

既然是一个模块加载器,RequireJS肯定会定义一种requireJS风格的模块,以及配套的API。

其实就是把文件名称作为name参数传入,若依赖该组件那么返回的就是一个对象!

模块化脚本

将一个js脚本文件中的所有代码,都定义在一个requireJS模块中,那么这个脚本就是“模块化脚本”。
由此可以看书,约定的思维是:一个js文件对应一个requireJS模块

RequireJS由James Burke创建,他也是AMD规范的创始人。

www.js8331.com 3

define({ name: "hehe", age: "18"});

RequireJS的使用

RequireJS会让你以不同于往常的方式去写JavaScript。你将不再使用script标签在HTML中引入JS文件,以及不用通过script标签顺序去管理依赖关系。
当然也不会有阻塞(blocking)的情况发生

2. 简单的函数模块

RequireJS引入三个全局变量

require会定义三个变量:define,require,requirejs

  • define 用来定义一个模块
  • require 加载依赖模块,并执行加载完后的回调函数
    其中,require即使函数,也是一个对象。它上面挂了一些我们常用的方法如require.config()
  • require === requirejs

和上面一样文件名称作为默认的name,与上面的差异是,可以提前执行返回值外的代码

1、引入RequireJS

使用requirejs很简单,只需要在head中通过script标签引入它 ——
实际上除了require.js,其它文件模块都不再使用script标签引入。

<script data-main="script/main" src="require.js"></script>
define { return { name: "hehe", age: "18" };});

data-main指定主模块

细心的同学会发现script标签上了多了一个自定义属性:data-main=”script/main”,等号右边的main指的main.js。当然可以使用任意的名称。这个main指主模块或入口模块,好比c或java的主函数main。
通常在主模块中,进行RequireJS全局配置:

require.config({
    paths: {
        jquery: 'jquery-1.7.2'
    }
});

3. 依赖函数模块

副作用:data-main同时指定了默认的js路径

上述main.js位于script/,所以之后本地文件的查找、使用相对路径的化、就会相对于这个路径去查找。

define([ 'angular', 'jsUtil', 'modules/meet/modules', 'modules/meet/services/Meet', 'modules/meet/services/MeetRemoteService'],function { 'use strict'; var module = angular.module; module.factory('MeetService', function(Meet, MeetRemoteService) { var service = { name: 'hehe', age: '18' }; return service; });});

2、require.config:全局配置

require方法本身也是一个对象,它带有一个config方法,用来配置require.js运行参数。

应用中所有的js模块都应该在这里配置相关的信息。这些信息包含了每个模块的位置啥的,解决了到哪里去找到模块的问题。这块配置是全局的。

config方法接受一个对象作为参数。一般配置项都在页面data-main所指的js中 ——
这儿是js代码的总入口。
常用的配置有baseUrl,paths等。

require.config({
    baseUrl: "javascript",
    paths: {jquery: ['http//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js','lib/jquery.min'],
            bootstrap: 'lib/bootstrap'
    }
});

和上面一样,关键是依赖模块是以返回值作为入参的形式传入,如果加载错误或者没有找到对应的模块,那么得到的入参是Undefiend,需要注意!

baseUrl 配置本地路径的基准路径

baseUrl参数指定本地模块位置的基准目录,即本地模块的路径是相对于哪个目录的。该属性通常由require.js加载时的data-main属性指定,此时可以省略baseUrl

4. 返回函数模块

paths 配置每个模块的位置

paths参数指定各个模块的位置。这个位置可以是同一个服务器上的相对位置,也可以是外部网址。
可以为每个模块定义多个位置,如果第一个位置加载失败,则加载第二个位置,上面的示例就表示如果CDN加载失败,则加载服务器上的备用脚本。

需要注意的2点:

  • 关于后缀名 — 指定本地文件路径时,可以省略文件最后的js后缀名。
  • 关于baseUrl — 本地模块的路径是相对于baseUrl去定义的。
define([ 'angular', 'jsUtil', 'modules/meet/modules', 'modules/meet/services/Meet', 'modules/meet/services/MeetRemoteService'],function { 'use strict'; var module = angular.module; module.factory('MeetService', function(Meet, MeetRemoteService) { var service = {}; service.getWeekOfMeet = function { return MeetRemoteService.get.then { data.content = Meet.sortMeet; return data; }); } return service; });});

什么样的模块可以不配置path?

如果模块moudleA.js跟引用它的文件是同一路径下的,那么就可以直接用require(["moduleA"], function(ma){})来引用该模块,不需要配置path
—— 这种写法我也不建议。

和上面一样,这里返回的是函数,在依赖模块中把他作为函数对象调用即可,其实这是一个简单的闭包!

shim 加载非AMD规范的js

通过require加载的模块一般都需要符合AMD规范,即使用define来申明模块
但是部分时候需要加载非AMD规范的js,这时候就需要用到另一个功能:shim,shim解释起来也比较难理解,shim直接翻译为”垫”,其实也是有这层意思的。
非AMD模块输出,将非标准的AMD模块”垫”成可用的模块,例如:在老版本的jquery中,是没有继承AMD规范的,所以不能直接require[“jquery”],
这时候就需要shim,那我们可以这样配置

require.config({
  baseUrl: "javascript",
  paths: {
    jquery: 'lib/jquery.1.6',
    bootstrap: 'lib/bootstrap'
  },
  shim: {
    "jquery" : {
      exports : "$"
    }
  }
});

这样配置后,我们就可以在其他模块中引用jquery模块:

require(['jquery'], function ($) {
    //do something....
});

5. 完整定义

jQuery与RequireJS ???

我们知道jQuery从1.7后开始支持AMD规范,即如果jQuery作为一个AMD模块运行时,它的模块名是“jquery”。注意“jquery”是固定的,不能写“jQuery”或其它。

注:如果文件名“jquery-1.7.2.js”改为“jquery.js”就不必配置paths参数了。

我们知道jQuery最终向外暴露的是全局的jQuery和 $。如下

window.jQuery = window.$ = jQuery;

如果将jQuery应用在模块化开发时,其实可以不使用全局的,即可以不暴露出来。需要用到jQuery时使用require函数即可

define('sample3' ,['sample','sample1'],function  { var sample4 = require; return function(){ alert(sample.name+':'+sample.sayhell;

3、define方法: 定义自己的模块

define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。
按照是否依赖其他模块,可以分成两种情况讨论。

  • 第一种情况是定义独立模块,即所定义的模块不依赖其他模块;
  • 第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。

这就是完整定义,有名称,有依赖,有回调,内部还有common的形式引入依赖对象!

定义没有依赖的独立模块

如果被定义的模块是一个独立模块,不需要依赖任何其他模块,可以直接用define方法生成。

  • define的参数可以是一个对象,此时该对象就是输出的模块。

define({
    method1: function() {},
    method2: function() {},
});
  • define的参数也可以是一个函数,此时该函数的返回值就是输出的模块。——
    显然这种写法更具有通用性,建议统一这样写。

// math.js
  define(function (){
    var add = function (x,y){
      return x+y;
    };
    return {
      add: add
    };
  });

关于define函数的name和require函数的依赖名称之间的关系

定义有依赖模块的模块

非独立模块如果被定义的模块需要依赖其他模块,则define方法必须采用下面的格式:

define(['module1', 'module2'], function(m1, m2) {
   ...
});
  • define方法的第一个参数是一个数组,它的成员是当前模块所依赖的模块。
    比如,[‘module1’,
    ‘module2’]表示我们定义的这个新模块依赖于module1模块和module2模块,只有先加载这两个模块,新模块才能正常运行。
    一般情况下,module1模块和module2模块指的是,当前目录下的module1.js文件和module2.js文件,等同于写成[‘./module1’,
    ‘./module2’]。

思考? 如果新模块同属于module文件下,如何定义引入文件路径?

  • define方法的第二个参数是一个函数,当前面数组的所有成员加载成功后,它将被调用。
    它的参数与数组的成员一一对应,比如function(m1,
    m2)就表示,这个函数的第一个参数m1对应module1模块,第二个参数m2对应module2模块。这个函数必须返回一个对象,供其他模块调用。

define(['module1', 'module2'], function(m1, m2) {
  return {
    method: function() {
      m1.methodA();
      m2.methodB();
    }
  };
});

define;这个name可以省掉,默认是文件名称;当然也可以自定义,一旦我们定义了name,根据源代码我们可以发现define函数内部其实就是把这个name以及依赖模块、回调函数作为一个对象存储在全局的数组当中,也就是
defQueue.push;那么这个name就是这个组件注册的的ID!

define回调必须返回一个对象??

需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。
如果依赖的模块很多,参数与模块一一对应的写法非常麻烦。

define(
    [       'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'],
    function(dep1,   dep2,   dep3,   dep4,   dep5,   dep6,   dep7,   dep8){
        ...
    }
);

对于依赖模块特别多的情况,RequireJS有另一种等价写法: ——
还是竖版的好吧。。。!

define(
  function (require) {
    var dep1 = require('dep1'),
      dep2 = require('dep2'),
      dep3 = require('dep3'),
      dep4 = require('dep4'),
      ……
  }
});

require([name , name2],callback);系统首先会在全文检索path中是否对应的路径,如果没有自然把他作为路径拼接在baseUrl上去异步加载这个js文件,加载时从源代码中可以看到
,var data = getScriptData;返回的
data.id其实就是name,然后执行contex.completeLoad,其内部就很清楚了,把define中注册的name和这里得到的name进行比较如果相等就执行。所以道理就是:require
和 define 的 name 必须保证一致!

符合AMD规范的模块

想象一下,如果你想把jquery、通过RequireJS、当做一个模块的方式引入进来,就像这样:

require.config({
    paths: {
        jquery: 'jquery-1.7.2'
    }
});

那么jquery必须首先就是一个requirejs模块,也就是说 ——
jquery必须把所有代码包装在require.define的回调函数里、并且返回$变量,对不对?

所有这样的第三方库,就叫做“符合AMD规范的模块”

总结

4. require方法:在JS文件头部、声明依赖模块

require方法用于调用模块。它的参数与define方法类似。
1.普通加载方式

require(['jquery','bootstrap'], function ($) {
    $('.carousel').carousel({
        interval: 3000
    });
});

上面方法表示加载jquery和bootstrap两个模块,当这两个模块都加载成功后,执行一个回调函数。
该回调函数就用来完成具体的任务。require方法的第一个参数,是一个表示依赖关系的数组。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

有的模块会返回个对象,有的没有返回

如上,bootstrap就没有返回值。jquery有返回值,所以在function中声明$代表jquery模块返回的值。
注意前后是按顺序一一对应的
模块输出变量,如果某个模块不输出变量值,则没有,所以尽量将输出的模块写在前面,防止位置错乱引发误解

可以看出JavaScript这风格跟我们从前的人生、多么的不同!!同样是在文件头部声明依赖模块,人家横着写。。。而且不干不净。。

www.js8331.com 4

小结:RequireJS的风格

  • 一个文件定义一个模块,用文件路径定义模块名,通过模块名声明模块加载
  • 在文件头部声明依赖模块,人家是横着写的哦
  • .js常常可以省略
    比如main-data中指定的js文件,可以省略路径;path中指定的位置、如果是本地文件,可以省略后缀
  • 用别名代替全路径

—————— 华丽丽的分隔线,以下是草稿 —————–

Require更多高级特性

包模块加载

使用config的package参数

require方法的第一个参数,是一个表示依赖关系的数组。
但是页面往往不会只有一个交互效果,如果多了就会展示这样

require(['jquery','bootstrap'], function ($) {
  $('.carousel').carousel({
    interval: 3000
  });
});
//收藏方法
require(['jquery'], function ($) {
  var collect=$('.goodsOffer-collect');

});
//出价控制
require(['jquery','sp-bid-control'], function ($) {

这只是作为参考,目的是为了展示当交互多了的时候会感觉比较乱。为了解决这个问题,可以引入了包加载模块。

require合并

运行命令node r.js -o build.js

实例列表(未完成)

参考文档

本文参考:
RequireJS入门(一)

更多参考:

  1. require官网
    API文档

  2. require.js中文网
    —— 好像挂掉了。。

  3. JS模块化工具requirejs教程(一):初识requirejs
    JS模块化工具requirejs教程(二):基本知识
    这两篇是runoob.com网站的教程,很规范。

  4. RequireJS和AMD规范
    Javascript模块化编程(三):require.js的用法
    这两篇是阮一峰的博客,质量很高。

发表评论

电子邮件地址不会被公开。 必填项已用*标注