zoukankan      html  css  js  c++  java
  • 使用RequireJS优化Web应用前端

    摘要:RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一。最新版本的RequireJS压缩后只有14K,堪称非常轻量。它还同时可以和其他的框架协同工作,使用RequireJS必将使您的前端代码质量得以提升。

    AMD 简介

    前端开发在近一两年发展的非常快,JavaScript作为主流的开发语言得到了前所未有的热捧。大量的前端框架出现了,这些框架都在尝试着解决一些前端开发中的共性问题,但是实现又不尽相同。在这个背景下,CommonJS社区诞生了,为了让前端框架发展的更加成熟,CommonJS鼓励开发人员一起在社区里为一些完成特定功能的框架制定规范。AMD(Asynchronous Module Definition)就是其中的一个规范。

    传统JavaScript代码的问题

    让我们来看看一般情况下JavaScript代码是如何开发的:通过<script>标签来载入JavaScript文件,用全局变量来区分不同的功能代码,全局变量之间的依赖关系需要显式的通过指定其加载顺序来解决,发布应用时要通过工具来压缩所有的JavaScript代码到一个文件。当Web项目变得非常庞大,前端模块非常多的时候,手动管理这些全局变量间的依赖关系就变得很困难,这种做法显得非常的低效。

    AMD的引入

    AMD提出了一种基于模块的异步加载JavaScript代码的机制,它推荐开发人员将JavaScript代码封装进一个个模块,对全局对象的依赖变成了对其他模块的依赖,无须再声明一大堆的全局变量。通过延迟和按需加载来解决各个模块的依赖关系。模块化的JavaScript代码好处很明显,各个功能组件的松耦合性可以极大的提升代码的复用性、可维护性。这种非阻塞式的并发式快速加载JavaScript代码,使Web页面上其他不依赖JavaScript代码的UI元素,如图片、CSS以及其他DOM节点得以先加载完毕,Web页面加载速度更快,用户也得到更好的体验。

    CommonJS的AMD规范中只定义了一个全局的方法,如清单1所示。

    清单1.AMD规范

    1. define(id?, dependencies?, factory);  

    该方法用来定义一个JavaScript模块,开发人员可以用这个方法来将部分功能模块封装在这个define方法体内。

    id表示该模块的标识,为可选参数。

    dependencies是一个字符串Array,表示该模块依赖的其他所有模块标识,模块依赖必须在真正执行具体的factory方法前解决,这些依赖对象加载执行以后的返回值,可以以默认的顺序作为factory方法的参数。dependencies也是可选参数,当用户不提供该参数时,实现AMD的框架应提供默认值为[“require”,”exports”,“module”]。

    factory是一个用于执行改模块的方法,它可以使用前面dependencies里声明的其他依赖模块的返回值作为参数,若该方法有返回值,当该模块被其他模块依赖时,返回值就是该模块的输出。

    CommonJS在规范中并没有详细规定其他的方法,一些主要的AMD框架如RequireJS、curl、bdload等都实现了define方法,同时各个框架都有自己的补充使得其API更实用。

    RequireJS简介

    RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一。最新版本的RequireJS压缩后只有14K,堪称非常轻量。它还同时可以和其他的框架协同工作,使用RequireJS必将使您的前端代码质量得以提升。

    目前最新版本的RequireJS 1.0.8在IE 6+、Firefox 2+、Safari 3.2+、Chrome 3+、Opera 10+上都工作的很好。

    实战RequireJS

    在Web应用中使用RequireJS

    RequireJS官方主页上下载最新的版本的require.js,放到Web页面中。示例的Web项目结构图1所示。

    图 1. Web 项目结构

    图1.Web项目结构

    通过在index.html中加入一个<script>标签直接引入require.js即可。

    RequireJS功能初探

    RequireJS使用起来非常的简单,但是这些功能确是在Web前端开发中非常重要的,下面我们来逐一看看如何使用RequireJS的各项功能。

    使用RequireJS加载JavaScript文件

    即使您的Web前端由于种种历史原因还没有打算进行模块化的设计,RequireJS作为JavaScript文件的加载器,还是可以帮助您来完成异步非阻塞的文件加载。

    如清单2所示,有两个JavaScript文件a.js和b.js,里面各自定义了myFunctionA和myFunctionB两个方法,通过下面这个方式可以用RequireJS来加载这两个文件,在function部分的代码可以引用这两个文件里的方法。

    清单2.加载JavaScript文件

    1. <script src="./js/require.js"></script>   
    2.  
    3. <script>   
    4.  
    5. require(["./js/a.js", "./js/b.js"], function() {   
    6.  
    7. myFunctionA();   
    8.  
    9. myFunctionB();   
    10.  
    11. });   
    12.  
    13. </script> 

    require方法里的这个字符串数组参数可以允许不同的值,当字符串是以”.js”结尾,或者以”/”开头,或者就是一个URL时,RequireJS会认为用户是在直接加载一个JavaScript文件,否则,当字符串是类似”my/module”的时候,它会认为这是一个模块,并且会以用户配置的baseUrl和paths来加载相应的模块所在的JavaScript文件。配置的部分会在稍后详细介绍。

    这里要指出的是,RequireJS默认情况下并没有保证myFunctionA和myFunctionB一定是在页面加载完成以后执行的,在有需要保证页面加载以后执行脚本时,RequireJS提供了一个独立的domReady模块,需要去RequireJS官方网站下载这个模块,它并没有包含在RequireJS中。有了domReady模块,清单2的代码稍做修改加上对domReady的依赖就可以了。

    清单3.页面加载后执行JavaScript

    1. <script src="./js/require.js"></script>   
    2.  
    3. <script>   
    4.  
    5. require(["domReady!", "./js/a.js", "./js/b.js"], function() {   
    6.  
    7. myFunctionA();   
    8.  
    9. myFunctionB();   
    10.  
    11. });   
    12.  
    13. </script> 

    执行清单3的代码后,通过Firebug可以看到RequireJS会在当前的页面上插入为a.js和b.js分别声明了一个<script>标签,用于异步方式下载JavaScript文件。async属性目前绝大部分浏览器已经支持,它表明了这个<script>标签中的js文件不会阻塞其他页面内容的下载。

    清单4.RequireJS插入的<script>

    1. <script type="text/javascript" charset="utf-8" async="" data-requirecontext="_"   
    2.  
    3. data-requiremodule="js/a.js" src="js/a.js"></script> 

    使用RequireJS来定义JavaScript模块

    这里的JavaScript模块与传统的JavaScript代码不一样的地方在于它无须访问全局的变量。模块化的设计使得JavaScript代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者函数,有效的避免大量而且复杂的命名空间管理。

    如同CommonJS的AMD规范所述,定义JavaScript模块是通过define方法来实现的。

    下面我们先来看一个简单的例子,这个例子通过定义一个student模块和一个class模块,在主程序中实现创建student对象并将student对象放到class中去。

    清单5.student模块,student.js

    1. define(function(){  
    2.  
    3. return {  
    4.  
    5. createStudent: function(name, gender){  
    6.  
    7. return {  
    8.  
    9. name: name,  
    10.  
    11. gender: gender  
    12.  
    13. };  
    14.  
    15. }  
    16.  
    17. };  
    18.  
    19. }); 

    清单6.class模块,class.js

    1. define(function() {  
    2.  
    3. var allStudents = [];  
    4.  
    5. return {  
    6.  
    7. classID: "001",  
    8.  
    9. department: "computer",  
    10.  
    11. addToClass: function(student) {  
    12.  
    13. allStudents.push(student);  
    14.  
    15. },  
    16.  
    17. getClassSize: function() {  
    18.  
    19. return allStudents.length;  
    20.  
    21. }  
    22.  
    23. };  
    24.  
    25. }  
    26.  
    27. ); 

    清单7.主程序

    1. require(["js/student", "js/class"], function(student, clz) {  
    2.  
    3. clz.addToClass(student.createStudent("Jack", "male"));  
    4.  
    5. clz.addToClass(student.createStudent("Rose", "female"));  
    6.  
    7. console.log(clz.getClassSize()); // 输出 2  
    8.  
    9. }); 

    student模块和class模块都是独立的模块,下面我们再定义一个新的模块,这个模块依赖student和class模块,这样主程序部分的逻辑也可以包装进去了。

    清单8.依赖student和class模块的manager模块,manager.js

    1. define(["js/student", "js/class"], function(student, clz){  
    2.  
    3. return {  
    4.  
    5. addNewStudent: function(name, gender){  
    6.  
    7. clz.addToClass(student.createStudent(name, gender));  
    8.  
    9. },  
    10.  
    11. getMyClassSize: function(){  
    12.  
    13. return clz.getClassSize();  
    14.  
    15. }  
    16.  
    17. };  
    18.  
    19. }); 

    清单9.新的主程序

    1. require(["js/manager"], function(manager) {  
    2.  
    3. manager.addNewStudent("Jack", "male");  
    4.  
    5. manager.addNewStudent("Rose", "female");  
    6.  
    7. console.log(manager.getMyClassSize());// 输出 2  
    8.  
    9. }); 

    通过上面的代码示例,我们已经清楚的了解了如何写一个模块,这个模块如何被使用,模块间的依赖关系如何定义。还是有一些使用技巧需要提示一下:

    尽量不要提供模块的ID,如AMD规范所述,这个ID是可选项,如果提供了,在RequireJS的实现中会影响模块的可迁移性,文件位置变化会导致需要手动修改该ID。

    每个JavaScript文件只定义一个模块,模块名称和文件路径的查找算法决定了这种方式是最优的,多个的模块和文件会被优化器进行优化。

    避免模块的循环依赖,如果实在避免不了,可以模块中加上对”require”模块的依赖,在代码中直接用require(”dependencyModuleName”)。

    配置RequireJS

    前面的介绍中,我们似乎忽略了一个基本问题,模块名字是怎么来的?当我在require一个模块时,这个模块是如何映射到具体的JavaScript文件上去?这就涉及到如何配置RequireJS。

    最简化的加载RequireJS的方式如清单2所示,在这种情况下,我们没有指定一个baseUrl和paths给RequireJS,如果通过如清单10所示方式,则data-main指定了一个在当前index.html目录并行的文件夹下的/js/main.js作为程序入口,而/js目录也将作为baseUrl在其他模块定义时候使用。

    清单10.载入require.js

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

    因此,我们前面示例中的所有模块依赖,都可以去掉”js/”,直接写 ”student”, ”class”,”manager”等。

    一种更为直接的方式显示指定baseUrl和paths就是利用require.config来设置这些参数。如清单11所示。

    清单11.配置RequireJS

    1. <script type="text/javascript" src="./js/require.js"></script> 
    2.  
    3. <script type="text/javascript"> 
    4.  
    5. require.config({  
    6.  
    7. baseUrl: "./js",  
    8.  
    9. paths: {  
    10.  
    11. "some": "some/v1"  
    12.  
    13. },  
    14.  
    15. waitSeconds: 10  
    16.  
    17. });  
    18.  
    19. require( ["some/module", "my/module", "./js/a.js"],  
    20.  
    21. function(someModule, myModule) {}  
    22.  
    23. );  
    24.  
    25. </script> 

    baseUrl指明的是所有模块的base URL,比如”my/module”所加载的script实际上就是/js/my/module.js。注意,以.js结尾的文件加载时不会使用该baseUrl,它们仍然会使用当前index.html所在的相对路径来加载,所以仍然要加上”./js/”。如果baseUrl没有指定,那么就会使用data-main中指定的路径。

    paths中定义的路径是用于替换模块中的路径,如上例中的some/module具体的JavaScript文件路径是/js/some/v1/module.js。

    waitSeconds是指定最多花多长等待时间来加载一个JavaScript文件,用户不指定的情况下默认为7秒。

    另外一个重要的配置是packages,它可以指定其他符合CommonJS AMD规范的目录结构,由此带来了丰富的扩展性。如Dojo、jQuery等的模块也可以通过该配置来让RequireJS加载。

    其他可配置的选项还包括locale、context、deps、callback等,有兴趣的读者可以在RequireJS的官方网站查阅相关文档。

    综合运用RequireJS

    当RequireJS与其他框架一起工作的时候,显然它是可以作为统一的加载器来加载其他框架。鉴于jQuery、Dojo等已经支持了AMD,那么就有可能在定义自己的模块的时候,通过适当配置,直接把其他框架的模块作为依赖对象。

    RequireJS和Dojo

    清单11是一个基本的RequireJS和Dojo集成配置方法,这里关键是将Dojo和Dijit都在packages里面注册一下。

    清单12.配置RequireJS和Dojo

    1. <script> 
    2.  
    3. require = {  
    4.  
    5. packages: [  
    6.  
    7. {  
    8.  
    9. name: “dojo”,  
    10.  
    11. location: “dojo”,  
    12.  
    13. main:”lib/main-browser”,  
    14.  
    15. lib: “.”  
    16.  
    17. },  
    18.  
    19. {  
    20.  
    21. name: “dijit”,  
    22.  
    23. location: “dijit”,  
    24.  
    25. main:”lib/main”,  
    26.  
    27. lib: “.”  
    28.  
    29. }  
    30.  
    31. ],  
    32.  
    33. paths: {  
    34.  
    35. require: “./js”  
    36.  
    37. },  
    38.  
    39. ready: function () {  
    40.  
    41. require([“my/module”], function (module) {  
    42.  
    43. });  
    44.  
    45. }  
    46.  
    47. };  
    48.  
    49. </script> 

    在定义module这个模块时就可以直接将Dojo和Dijit里的模块作为依赖对象了。

    RequireJS和jQuery

    把jQuery作为一个依赖模块来使用也非常简单,只需要在RequireJS里的config里做相应的配置就可以了。

    清单13.配置RequireJS和jQuery

    1. require.config({  
    2.  
    3. paths: {  
    4.  
    5. "jquery": "./js/jquery-1.7"  
    6.  
    7. }  
    8.  
    9. });  
    10.  
    11. require(["jquery"],function(jQ){  
    12.  
    13. console.log(jQ);  
    14.  
    15. }); 

    RequireJS和Web Workers

    Web Workers是多线程的JavaScript,每个worker里面的脚本都会启动一个新的线程来执行,通过在worker里面用importsScript来加载require.js可以直接在worker里面使用 RequireJS 。

    清单14.RequireJS和Web Workers

    1. importScripts('./js/require.js');  
    2.  
    3. require(["require"],  
    4.  
    5. function(require) {  
    6.  
    7. postMessage("test");  
    8.  
    9. }  
    10.  
    11. ); 

    小结

    本文简单介绍了AMD的概念和它带来的好处,深入介绍了如何使用RequireJS来定义自己的JavaScript模块,管理这些模块的依赖关系,异步按需加载这些模块。并进一步讨论了如何用RequireJS和其他框架协同工作。

    来自:IBM开发者社区

     

  • 相关阅读:
    170619、springboot编程之HelloWorld
    170616、解决 java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.ArrayList
    170615、spring不同数据库数据源动态切换
    pytest文档10-命令行传参
    pytest文档9-参数化parametrize
    pytest文档8-html报告报错截图+失败重跑
    pytest文档7-pytest-html生成html报告
    定位对应关系
    ADB 无线连接
    command failed shell "ps 'uiautomator'"的解决方式
  • 原文地址:https://www.cnblogs.com/mz121star/p/2706348.html
Copyright © 2011-2022 走看看