Web前端 之 RequireJS

来源:互联网 发布:深入理解linux内核pdf 编辑:程序博客网 时间:2024/05/01 06:31

引言

随着互联网的发展,浏览器端的应用越来越多,单个应用规模越来越大,逻辑也越来越复杂,从最初的页面动态效果,到与服务器异步通信,以AJAX为代表的通信技术,允许在不刷新页面的情况下动态请求服务器的服务,再到复杂的交互,使得JavaScript代码里剧增,因此,代码如何有效组织容易维护,如何应对规模效应和复杂逻辑,以及需求多变的要求,是前端急需解决的问题。


问题是这样,解决方法有什么呢?从其他比较成熟的桌面端编程语言或者服务器端编程语言,我们可以找出解决方法,因为这些语言或者技术已经经过了这个阶段。比如,面向对象技术,但是直到ES5,JavaScript中并没有面向对象的直接支持,一般的做法是利用JavaScript中的函数,原型链实现;又比如模块化组织代码,直到ES5,JavaScript中也是没有直接支持,这时候出现了很多规范和方法来实现模块化,比如CommonJS,AMD(不是CPU)等。今天我们要说的就是AMD的一个实现:Require.js。


注:当然,模块化并不能代表前端工程化,前端工程化囊括了很多内容:例如,因为网络应用的特殊性,既成的代码压缩后会有更小的体积,这样便更利于网络传输,所以前端工程化还包括流程的自动化,各种优化等问题。本文只介绍模块化中的一个解决方案。


没有Resquire.js时我们遇到的问题

AMD时异步模块定义的缩写,从名字中我们可以看出,其主要特定是模块异步加载,为什么要异步呢?同步有什么缺陷吗?请看下面的例子:


<!DOCTYPE html><html>  <head>    <meta charset="utf-8">    <title>不使用RequireJS</title>    <link rel="stylesheet" href="css/main.css" media="screen" title="no title" charset="utf-8">  </head>  <body>    <div id="map" class="map"></div>    <script src="scripts/1.js" charset="utf-8"></script>    <script src="scripts/2.js" charset="utf-8"></script>    <script src="scripts/3.js" charset="utf-8"></script>    <script src="scripts/4.js" charset="utf-8"></script>    <script src="scripts/5.js" charset="utf-8"></script>    <script src="scripts/6.js" charset="utf-8"></script>    <script type="text/javascript">      // Do Something    </script>  </body></html>

这个例子中两点需要指出,1,引入JavaScript都放置于页面底端,这是因为JavaScript执行和UI渲染是使用的一个线程,如果将JavaScript放于传统的`<head></head>`标签中,如果项目比较复杂,JavaScript文件将会引入很多,而加载JavaScript文件时会阻塞UI的渲染,这个时候用户会看到一片空白;2,JavaScript载入是`one by one`,这就意味着,1加载完,2才会加载,这样同步依次加载,显然没有异步加载快速,且考虑到JavaScript模块文件之间的相互依赖,以上例子只能按照一定的顺序执行。


那么我们看看Require.js是如何解决这些问题的。


Require.js模块异步加载

异步加载是指几个JavaScript模块文件共同加载,要使用RequireJS提供的异步加载功能,我们需要将RequireJS引入,我们在html引入的格式如下:

<script data-main="your_script" src="require.js"></script>


data-main属性指定的自己写的JavaScript脚本,该文件不必带.js文件类型后缀,因为RequireJS默认就是认为文件都是JavaScript,这样就可以利用RequireJS提供的模块异步加载功能了。但是共同加载就有一个问题,比如2.js依赖于1.js,怎么确定2.js要执行时,1.js已经加载完了呢?在Requirejs中可以通过'requirejs.config({})'来进行配置,这个配置是写在your_script.js中的,比如我们配置2.js依赖于1.js,如下:


requirejs.config({  shim: {    2: ['1']  }});

这里虽然1.js 与 2.js 同时加载,但是经过这样的配置,一定保证2.js在1.js之前加载完成,这样既保证了加载的速度,又保证了类库之间的依赖关系。requirejs还有很多配置参数,例如下面的例子:


requirejs.config({    urlArgs: 'bust='+(new Date()).getTime(),    waitSeconds: 20,    baseUrl: 'scripts/libs',    paths: {      jquery: 'jquery.min',      bootstrap: 'bootstrap.min'    },    shim: {      bootstrap: ['jquery']    }});

默认情况下,Require.js会缓存请求的JavaScript文件,这种情况下,如果对引入的JavaScript代码做了修改,那么你刷新网页,浏览器还是调取缓存的版本,urlArgs会将其值附加到每个请求的JavaScript文件,这样浏览器就会认为JavaScript发生了改变,取最新的版本,而不是缓存的版本。或者你如果你使用firefox,可以crtl+F5可以清空缓存,重新请求,使用chorme,打开控制台,在其刷新按钮处右键鼠标,会弹出选项,是清空缓存重新请求还是从缓存中获取,但这两种方式都不如添加urlArgs方便。


waitSeconds表示加载脚本等待的最长时间,超出指定时间将会报错并停止加载;baseUrl表示JavaScript脚本库的存放路径;paths可以配置脚本独自的特殊路径,例如某个脚本不在规定的baseUrl目录中,可以使用该选项配置;shim参数则配置各个脚本之间的依赖,比如例子中bootstrap的脚本插件依赖jquery,即可这样配置。


Require.js管理与定义模块

RequireJS另外一个重要的功能是它提供了模块化的管理思想,我们从其他的语言或者技术中可以知道,模块一般就是一个文件,一个文件一般只写一个有关的类或者命名空间,JavaScript的命名空间可以使用对象字面量来进行模拟,这里不再延伸。在JavaScript中,我们也要尽量遵循这些思想,那么,每个模块如何引入呢?如何书写呢?AMD都有自己的规定,比如引入,其规定使用require函数,如下:

require(['jquery','bootstrap'], function($){    // here you can use $ for jquery and bootstrap});

require才会真正引发相应的JavaScript载入,requirejs.config({})只是做相关的配置,并不会引发文件载入。require函数接收两个参数,第一个是一个数组,包含所有需要载入的依赖,第二个是相应文件载入后需要调用的回调函数,参数是每个依赖导出的局部变量,比如jquery使用$。这样我们就可以在回调函数的函数体中书写我们的逻辑业务代码了。但是,并不是所有的JavaScript文件都可以通过require([],callback);载入,需要载入的类或者模块必须遵循AMD的模块定义规范。在RequireJS中,定义模块需要使用define函数,如下:


define(['jquery'],function($){    function SomeClass(){        // some code    }    SomeClass.prototype.subfunction = function(){        // do Something    };    SomeClass.prototype.subfunction2 = function(){        // do Something    };    return SomeClass;});

define函数的原型是define([],callback),接收两个参数,第一个是可能的依赖库,比如jquery,是一个数组,可以指定多个依赖,使用逗号分开,第二个参数是回调函数,参数与依赖数组对应,表示依赖导出的局部变量,和require函数基本一致,这样增加了一致性,减少了学习成本。你可以在你的模块里,写一个类,或者一个命名空间,或者仅仅是写一些函数的集合,都可以,最后将模块导出时,你可以直接return一个对象或者命名空间,或者一个对象字面量,或者一个函数,如例子中,我们导出了这个类。我们在调用的代码处,便可以直接new来初始化SomeClass类:"var example = new SomeClass();"。


总结

以上就是使用RequireJS在前端模块化给出的解决方案:模块的定义和导出,模块的导入及并行加载。需要指出的是,还有其他的规范对JavaScript的模块化做出的努力,比如CommonJS,Node就遵循这个规范。


好的,就写到这里,有问题欢迎文章下留言或者给我发邮件。

1 0