详解JavaScript模块化开发require.js

更新日期: 2017-11-13阅读: 3.3k标签: es6
前言

前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了,jquery,Ajax,Node.Js,MVC,MVVM等的助力也使得前端开发得到重视,也使得前端项目越来越复杂,然而,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,所以,进行模块化开发的重要性就不言而喻了。那么什么是模块呢?

一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。所以,今天小编介绍一下js模块化开发的几种方法。

 

一、模块化开发规范

js模块化的开发并不是随心所欲的,为了便于他人的使用和交流,需要遵循一定的规范。目前,通行的js模块规范主要有两种:CommonJS和AMD。

 

1.1AMD

AMD 即Asynchronous Module Definition,中文名是“异步模块定义”的意思。它是一个在浏览器端模块化开发的规范,服务器端的规范是CommonJS;

模块将被异步加载,模块加载不影响后面语句的运行。所有依赖某些模块的语句均放置在回调函数中。

关于AMD有两个非常重要的概念,那就是用于模块定义的define方法和用于处理依赖加载的require方法。

 

(1)作为一个规范,只需定义其语法api,而不关心其实现。define函数定义如下:

define(
     [module-name?] /*可选*/, 
     [array-of-dependencies?] /*可选*/, 
     [module-factory-or-object]
);


其中:
  • module-name: 模块标识,可以省略。如果没有这个属性,则称为匿名模块。
  • array-of-dependencies: 所依赖的模块,可以省略。
  • module-factory-or-object: 模块的实现,或者一个JavaScript对象

举个栗子:

define(
    "myModule",
    ["foo", "bar"],
    // 模块定义函数,依赖(foo,bar)作为参数映射到函数上
    function (foo, bar) {
        // 创建模块
        var myModule = {
            myFun: function () {
                console.log("Jeri");
            }
        }
        // 返回定义的模块
        return myModule;
    }
);


(2)require()方法,有两个参数:

require([module], callback);


 其中:

  • 第一个参数[module],是一个数组,里面的成员就是要加载的模块;
  • 第二个参数callback,则是加载成功之后的回调函数。

 举个实际栗子:

require([‘math‘], function (math) {
   math.add(2, 3);
});


1.2CommonJS规范

 CommonJS是服务器端模块的规范,根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。输出模块变量的最好方法是使用module.exports对象。

不过,与AMD表现形式不同的是,CommonJS模块并不使用define进行定义。CommonJS模块由两部分组成:变量exports和require函数。

(1)exports/require:加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的module.exports对象。

再举个栗子:创建两个js文件:

libJS:

// 新定义的模块方法
 function log(arg) {
     console.log(arg);
 }
 // 把方法暴露给其他模块
exports.log = log;


 

 app.JS:

// ./lib是我们需要的一个依赖
var lib = requrie("./lib"); 
// 新定义的模块方法
function foo() {
     lib.log("jeri");
}
// 把方法暴露给其他模块
 exports.foo = foo;


(2)module.exports

直接举栗子:

var i = 1;
var max = 30; 
module.exports = function () {
   for (i -= 1; i++ < max; ) {
     console.log(i);
   }
  max *= 1.1;
};


 

二、JS模块的常用写法

 在前言中小编也提过,模块:就是一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。

除了接下来要讲的require.js,还有适合于一些简单模块的几种常用写法。

 

2.1字面量写法

要知道,模块就是实现特定功能的一组方法。

只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。这也是最简单的一种写法。也就是说,每一个函数就是一个简单的模块。

function func1 (){
   alert("这是func1");
};
function func2 (){
   alert("这是func2");
}


 把栗子中的两个函数func1()和func2()就组成了一个模块;

不过,这种写法有着一定的缺点:

如果只是实现一些简单的功能还好,如果实现复杂的功能,太多的函数就会出现问题,"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

2.2对象写法

 所以,为了解决上述的问题,把模块写成一个对象,所有的模块成员都放到这个对象里面不失为一个好的方式。

再再举个栗子:

var module = {
      func1 : function(){
        alert(1);      
     }
     func2 : function(){
       alert(2);      
     }
}
module.func1();
module.func2();


 

2.3通过闭包实现

 在JavaScript中,并不能可以直接声明类,但我们可以使用闭包来封装私有的属性和方法,进而模拟类的概念,在JavaScript中实现Module模式;

疯狂的举栗子:

var myModule = (function () {
     // 私有变量
     var privateVar = 0;
     // 私有函数
     var privateFun = function (foo) {
         console.log(foo);
     };
     return {
         // 私有变量
         publicVar: "foo",
            // 公有函数        
            publicFun: function (arg) {
             // 修改私有变量
             privateVar ++; 
             // 传入bar调用私有方法
             privateFun(arg);
         }
     };
}) ()


 

2.4输入全局变量

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

var module1 = (function ($, YAHOO) {
     //...
})(jQuery, YAHOO);

 

三、require.js

 requir.JS本就是为了实现js的模块开发而创建的一个js脚本,其主要有着两大优点:

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

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

 

3.1require.js的实现步骤

 (1)众所周知,要导入js文件,为了避免网页失去响应有两种方法;

第一种,把<script></script>放在文件的最后。

今天,我们主要说第二种:

<script src="js/require.js" defer async="true" ></script>


 async属性表明这个文件需要异步加载,避免网页失去响应。但是,IE不支持这个属性,只支持defer,所以需要把defer也写上。

(2)在html中导入require.JS文件后,就可以编写自己的main.js文件。

首先,导入main.js文件:

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

 

其中:data-main属性的作用是,指定网页程序的主模块。

在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。 

(3)编写main.js;

require([‘moduleA‘, ‘moduleB‘, ‘moduleC‘], function (moduleA, moduleB, moduleC){
    // some code here
});
 

 通过require方法,实现代码的模块加载,传入两个参数:

①第一个参数是一个数组,表示代码所依赖的模块。
②第二个参数是一个回调函数,当前面的模块加载完成后,就会被调用。
加载的模块一参数的形式传入,从而在回调函数内部可以使用这些模块,回调函数就是整个页面的JS代码;

再次疯狂的举栗子:

require([‘jquery‘, ‘underscore‘, ‘backbone‘], function ($, _, Backbone){
     // some code here});

 

3.2模块的加载

在上面的栗子中,我们可以使用require.config()方法,对模块的加载行为进行自定义。

(1)require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。 

 不要命的举栗子:

require.config({
     paths: {
       "jquery": "jquery.min",
       "underscore": "underscore.min",
       "backbone": "backbone.min"
     }
});

 

 (2)上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。

如果这些模块在其他目录,比如js/lib目录,则有两种写法。

一种是逐一指定路径。

再次举栗子:

require.config({
     paths: {
      "jquery": "lib/jquery.min",
       "underscore": "lib/underscore.min",
       "backbone": "lib/backbone.min"
     }
});


另一种则是直接改变基目录(baseUrl)。

再再次举栗子:

require.config({
    baseUrl: "js/lib",
    paths: {
      "jquery": "jquery.min",
      "underscore": "underscore.min",
      "backbone": "backbone.min"
    }
  });


 (3)如果某个模块在另一台主机上,也可以直接指定它的网址;

举栗子:

require.config({
    paths: {
      "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }
});


3.3require的AMD模块写法

require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。

具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:

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


加载方法如下:

// main.js 
require([‘math‘], function (math){
    alert(math.add(1,1));
});


如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

define([‘myLib‘], function(myLib){
    function foo(){
      myLib.doSomething();
     } 
     return {
       foo : foo
    };
});


作者编

 在日常的工作中,js的模块法开发是一个不可或缺的一部分。在这里只是介绍了基于AMD模块的require.js。还有基于commonJS规范的node.js。小编会在下次和大家一起学习。

链接: https://fly63.com/article/detial/91

es6 箭头函数的使用总结,带你深入理解js中的箭头函数

箭头函数是ES6中非常重要的性特性。它最显著的作用就是:更简短的函数,并且不绑定this,arguments等属性,它的this永远指向其上下文的 this。它最适合用于非方法函数,并且它们不能用作构造函数。

js解构赋值,关于es6中的解构赋值的用途总结

ES6中添加了一个新属性解构,允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。用途:交换变量的值、从函数返回多个值、函数参数的定义、提取JSON数据、函数参数的默认值...

ES6中let变量的特点,使用let声明总汇

ES6中let变量的特点:1.let声明变量存在块级作用域,2.let不能先使用再声明3.暂时性死区,在代码块内使用let命令声明变量之前,该变量都是不可用的,4.不允许重复声明

ES6的7个实用技巧

ES6的7个实用技巧包括:1交换元素,2 调试,3 单条语句,4 数组拼接,5 制作副本,6 命名参数,7 Async/Await结合数组解构

ES6 Decorator_js中的装饰器函数

ES6装饰器(Decorator)是一个函数,用来修改类的行为 在设计阶段可以对类和属性进行注释和修改。从本质上上讲,装饰器的最大作用是修改预定义好的逻辑,或者给各种结构添加一些元数据。

基于ES6的tinyJquery

Query作为曾经Web前端的必备利器,随着MVVM框架的兴起,如今已稍显没落。用ES6写了一个基于class简化版的jQuery,包含基础DOM操作,支持链式操作...

ES6 中的一些技巧,使你的代码更清晰,更简短,更易读!

ES6 中的一些技巧:模版字符串、块级作用域、Let、Const、块级作用域函数问题、扩展运算符、函数默认参数、解构、对象字面量和简明参数、动态属性名称、箭头函数、for … of 循环、数字字面量。

Rest/Spread 属性_探索 ES2018 和 ES2019

Rest/Spread 属性:rest操作符在对象解构中的使用。目前,该操作符仅适用于数组解构和参数定义。spread操作符在对象字面量中的使用。目前,这个操作符只能在数组字面量和函数以及方法调用中使用。

使用ES6让你的React代码提升到一个新档次

ES6使您的代码更具表现力和可读性。而且它与React完美配合!现在您已了解更多基础知识:现在是时候将你的ES6技能提升到一个新的水平!嵌套props解构、 传下所有props、props解构、作为参数的函数、列表解构

在使用es6语法class的时候,babel到底做了什么?

自从有了webpack之后,我们这些jscoder似乎得到了前所未有的解放,箭头函数,对象解构,let,const关键字,以及class、extends等等,webpack会帮我们把这些es6代码转换成浏览器能够识别的es5代码,那么有多少人真正的看过,babel转换之后的代码呢?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!