RequireJS

RequireJS是一个JavaScript文件和模块加载器,可视为模块管理工具。

为什么使用RequireJS呢?

  • 有效防止命名冲突
  • 声明不同JS文件之间的依赖
  • JS代码以模块化的方式组织

RequireJS为帮助解决前端代码库的组织难题,提供了两种解决思路:

  • 模块化组织JS文件
  • 异步加载JS文件

JavaScript模块化编程

JavaScript模块化编程目的是为了让开发者仅需实现核心的业务逻辑,其他都加载他人已写好的模块。但是JavaScript并不是一种模块化的编程语言,虽然ECMAScript6中间正式支持类和模块。但对于之前的版本实际上是不支持类(class),也就更不用说模块(module)。

什么是模块呢?模块是实现特定功能的一组方法。

模块的原始写法

将不同函数以及记录状态的变量放在一起,算是一个模块。

function fn1(){...}
function fn2(){...}

缺点:污染全局变量,无法保证与其他模块发生变量命名冲突,而且模块成员之间看不出直接关系。

模块的对象写法

将模块定义为一个对象,所有模块成员都放在对象里面。

//将属性和操作都封装在对象中
var module = new Object({
  _prop:0,
  fn1:function(){...},
  fn2:function(){...}
});
// 使用时直接调用对象的属性
module.fn1();

缺点:暴露了模块成员,内部状态可被外部改写。

module._prop = 100;

立即执行函数写法

立即执行函数(IIFE, Immediately-Invoked Function Expression)可达到不暴露私有成员的目的。

var module = (function(){
  var _prop = 0;
  var fn1 = function(){...};
  var fn2 = function(){...};
  return {fn1:fn1, fn2:fn2};
})();

放大模式

如果一个模块很大,必须分成几个部分,或者是一个模块需要继承另一个模块,此时就有必要采用放大模式(augmentation)。

var module = (function(mod){
  mod.fn = function(){...};
  return mod;
})(module);

宽放大模式

浏览器环境中模块各部分通常是从网上获取的,有时不知道那个部分会首先加载。采用放大模式,第一个执行的部分可能加载一个不存在的空对象,此时需采用“宽放大模式(Loose Augmentation)”。

var module = (function(mod){
  mod.fn = function(){...};
  return mod;
})(window.module || {});

输入全局变量

独立性是模块的重要特点,模块内部最好不要与程序其他部分直接交互。为了在模块内调用全局变量,必须显式地将其他变量输入模块。

var module = (function($){
  
})(jQuery);

AMD规范

为什么模块很重要呢?如何规范地使用模块呢?

因为有了模块就可很方便地使用别人的代码,想要什么样的功能就可加载什么模块。不过前提是大家必须以同样的方式编写模块。而JS模块目前还没有官方规范,通行的JS模块规范有2种方式:CommonJS和AMD。

CommonJS

老实说在浏览器环境下,没有模块并不是特别大的问题,毕竟网页程序的复杂性有限。但对于服务端,一定要有模块,与操作系统和其他应用程序交互,否则根本无法编程。

2009年,美国程序员Ryan Dahl创建了NodeJS项目,将JS用于服务端编程。由此标志着JS模块化编程的正式诞生。

NodeJS的模块系统是参照CommonJS规范实现的,在CommonJS中有一个全局方法require(),用于加载模块。

var math = require("math");
math.add(1, 2);

自从有了JS服务端模块以后,对于客户端模块,如何做到兼容,使得一个模块不用修改就可以在服务端和客户端浏览器上都能运行呢?由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。问题是对于服务器而言模块都放在本地,可同步加载等待时间只是硬盘读取时间。但是当浏览器中使用服务端的模块时,等待时间取决于网速快慢,长时间的等待会造成浏览器处于“假死”状态。

因此浏览器端的模块不能采用“同步加载(synchronous)”,只能采用“异步加载(asynchronous)”方式,这就是AMD规范诞生的背景。

AMD

AMD(Asynchronous Module Definition)异步模块加载,模块加载不影响后续语句的执行。所有依赖于模块的语句都定义在一个回调函数中,等到加载完成后,回调函数才会执行。

AMD也采用了require()语句加载模块,不同于CommonJS的是,它要求两个参数。

// module参数为一个数组,里面的成员是要加载的模块
// callback参数是模块加载成功后执行的回调函数
require([module], callback)

// math模块与math.add()加载不是同步的,浏览器不会发生假死,因此AMD比较适合浏览器环境。
require(["math"], function(math){
  math.add(1, 2);
});

RequireJS

早期JS代码都会写在一个文件中,仅需加载一个文件即可。后来代码越来越多,必须分割成多个文件,依次加载。问题是这种加载的方式,浏览器会停止页面渲染,加载文件越多,网页失去响应的时间越长。另外JS文件之间存在依赖关系,必须严格保证加载顺序。当依赖关系非常复杂的时候,代码的编写和维护变得异常困难。

RequireJS的诞生是为了解决这两个问题:

  • 实现JS文件的异步加载避免页面失去响应
  • 管理模块之间的依赖关系,便于代码编写和维护。

加载资源文件

<script src="https://cdn.bootcss.com/require.js/2.3.5/require.js"></script>

在引入require.js文件之后,整个windows对象就有require()方法。可通过require()方法来加载其他JS文件。RequireJS的入口是引入时指定的data-main属性,在RequireJS引入后,会自动执行指向data-main属性所指定的入口文件。data-main="js/main"表示让RequireJS去js目录下寻找main.js文件,默认main.js是项目全局配置文件。

由于引入RequireJS文件本身可能会造成页面失去响应,解决的方式可将其放在网页底部加载,或使用延迟加载。

<script src="./assets/scripts/require-2.3.5.js" data-main="js/main" async="true" defer></script>

async="true"的作用和jQuery中AJAX的async=true的目的一样,表示一边加载RequireJS一边执行它。如果设置为false则表示等待RequireJS完全加载完成后才执行,这种方式的缺陷是在网络延迟较大时页面会出现空白。如果script标签不再head中而在页面尾部,则不会出现空白现象。另外,IE并不支持async属性,仅支持defer属性。

主模块

data-main加载的是主模块,意思是页面的入口,类似C语言的main()函数,所有代码从此处开始运行。

RequireJS以一个相对于baseUrl的地址来加载所有代码,页面顶层<script>标签内含有一个特殊的属性data-main,RequireJS使用它来启动脚本加载过程,baseUrl一般设置到该属性相一致的目录。RequireJS目的是鼓励代码模块化,鼓励在使用脚本时以module ID替代 URL 地址。

$ vim index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>app</title>
</head>
<body>
    <script type="text/javascript" src="./js/require-2.3.5.js" data-main="js/main"></script>
</body>
</html>

baseUrl可通过requirejs.config手动设置,若没有显式指定configdata-main,则默认的baseUrl为包含RequireJS的那个HTML页面的所属目录。

$ vim js/main.js
/**
 * RequireJS全局配置文件
 */
requirejs.config({
    //设置项目路径,项目会以baseUrl作为相对路径去查找模块文件
    baseUrl:"./js",
    //预加载JS文件的配置项,默认可不用添加.js后缀
    paths:{
        //RequireJS默认假定所有的依赖资源都是JS脚本,因此无需再module ID上再加上js后缀。
        jquery:"../scripts/jquery-3.3.1"
    }
});

正常情况下,主模块是依赖于其他模块的,此时就要使用AMD规范定义的require()函数。

require([module], function(module){...});

/**
 * RequireJS全局配置文件
 */
requirejs.config({
    //设置项目路径,项目会以baseUrl作为相对路径去查找模块文件
    baseUrl:"./js",
    //预加载JS文件的配置项,默认可不用添加.js后缀
    paths:{
        //RequireJS默认假定所有的依赖资源都是JS脚本,因此无需再module ID上再加上js后缀。
        jquery:"https://cdn.bootcss.com/jquery/3.3.1/jquery",
        bootstrap:"https://cdn.bootcss.com/bootstrap/4.1.1/js/bootstrap"
    }
});

requirejs(['jquery', 'bootstrap'],function($, undefined){

});

RequireJS要求每个模块是一个的单独的JS文件,如果加载多个模块会发出多次HTTP请求,会影响页面的加载速度。

RequireJS加载的模块采用AMD规范,也就是说模块必须按照AMD的规定来书写。具体说来模块必须采用特定的define()函数来定义,如果一个模块不依赖其他模块,可直接定义在define()函数之中。

但是实际上,虽然部分流行的函数库符合AMD规范,但更多的库并不符合。RequireJS如何加载非规范的模块呢?在使用require()之前,需在require.config()函数中定义非规范模块的特征。

require.config()接收一个配置对象,此对象除了paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。每个模块需要定义exports值即输出的变量名,表明这个模块外部调用名称。其次deps数组属性表明该模块的依赖性。

RequireJS常用方法

  • requirejs.config()
  • require()
  • define()

RequireJS源码解析

RequireJS工作流程

  1. 载入模块
  2. 通过模块名解析出模块信息并计算出URL
  3. 通过创建script的形式将模块加载到页面
  4. 判断被加载脚本若存在依赖则加载,若不存在则直接执行factory()
  5. 等待所有脚本都加载完毕后执行回调函数
// 定义全局变量
var requirejs,require,define;
// 自执行函数
(function(global, setTimeout){
  //...
})(this, (typeof setTimeout==='undefined'?undefined:setTimeout));

RequireJS可分为三部分

  1. 定义全局变量和帮助函数
  2. 模块加载核心部分
  3. 定义requiredefine方法以及项目入口
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容

  • 导语: 之前一直有听说RequireJS,但是一直都没机会去了解,只知道它是一个给js做模块化的API。最近在做R...
    wuqke阅读 40,914评论 11 78
  • 为什么会出现这些奇奇怪怪的东西,是不是搞事情? 因为前端代码模块化。 什么又是代码模块化? 这得从JavaScri...
    阿鲁提尔阅读 746评论 0 4
  • 参考资料 RequireJS 中文网Javascript模块化编程(三):require.js的用法——阮一峰 前...
    BeYanJin阅读 7,054评论 2 12
  • 为什么要使用模块化? 最主要的目的: 解决命名冲突 依赖管理 其他价值 提高代码可读性 代码解耦,提高复用性 CM...
    JamHsiao_aaa4阅读 366评论 0 1
  • 借我十年 借我亡命天涯的勇敢 接我说得出口的旦旦誓言 借我生猛与莽撞不问明天 ... 我想你借我那么多那么多 最想...
    初安_6feb阅读 164评论 0 0