• JS模块化:CommonJS和AMD(Require.js)


    早期的JS中,是没有模块化的概念的,这一情况直到09年的Node.js横空出世时有了好转,Node.js将JS作为服务端的编程语言,使得JS不得不寻求模块化的解决方案。

    模块化概念

    在JS中的模块是针对单个文件的,即一个文件是一个模块,要使用这个模块就加载该文件即可。

    CommonJS

    node.js的模块系统,是参照CommonJS规范实现的。

    定义模块和加载模块

    在CommonJS中,有一个全局性方法require(),用于加载模块,而module.exports用于导出当前文件的模块。

    假定有一个外部模块Utils.js,那么该模块需要这么写:

     1 // 定义类
     2 function Utils(name) {
     3     this._name = name;
     4 }
     5 
     6 // 定义类方法
     7 Utils.prototype.sayHi = function() {
     8     console.log("Hi, I am " + this._name);
     9 };
    10 
    11 // 定义静态方法
    12 Utils.add = function(a, b) {
    13     return a + b;
    14 }
    15 
    16 // 将类 Utils 作为当前文件的模块导出
    17 module.exports = Utils;

    加载模块并使用的方法如下:

    1 // 加载外部模块文件,位于当前文件夹下的 Utils.js,导入后该模块放入变量 Utils 中
    2 var Utils = require('./Utils.js');
    3 
    4 var obj = new Utils("Li Lei");
    5 obj.sayHi(); // Hi, I am Li Lei
    6 
    7 console.log(Utils.add(10, 20)); // 30

    可以导出为任意类型

    我们就上面的例子稍加改动,Utils.js如下:

     1 function Utils(name) {
     2     this._name = name;
     3 }
     4 Utils.prototype.sayHi = function() {
     5     console.log("Hi, I am " + this._name);
     6 };
     7 Utils.add = function(a, b) {
     8     return a + b;
     9 }
    10 module.exports = {version: 0.1, utils: Utils};

    执行的js代码如下:

    1 var ex = require('./Utils.js');
    2 
    3 console.log(ex.version); // 0.1
    4 
    5 var obj = new ex.utils("Li Lei");
    6 obj.sayHi(); // Hi, I am Li Lei
    7 
    8 console.log(ex.utils.add(10, 20)); // 30

    我们可以发现可以导出任意的一个对象,而使用require导入的就是导出的那个对象。

    不适用于浏览器

    在Node.js中,require方法是同步执行的,即只有加载并解析之后,代码才会向下执行,但是在浏览器中,加载脚本是异步执行的。

    AMD

    有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

    但是浏览器并不适用CommonJS规范,因为服务器端同步加载不是一个问题,所有的模块都存放在本地硬盘,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,如果采用同步加载的策略,可能要等很长时间才能加载好文件,这段时间里浏览器会处于"假死"状态。

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

    概念

    AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

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

    require([module], callback);

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

    目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。下面我们主要就require.js来看看如何在浏览器里实现模块化。

    Require.js

    官网地址:http://www.requirejs.cn/

    这个是非常流行的一个实现AMD规范的JS库,我们下载其最新版本(目前是2.1.11),我们下面用Require.js来实现上面CommonJS里的例子。

    定义模块和加载模块

    AMD规范里,定义模块和加载模块都和CommonJS规范不同,使用require加载模块但是带有回调函数,而定义模块则使用define函数。

    我们先看看Utils模块的写法:

     1 define(function (){
     2     // 定义类
     3     function Utils(name) {
     4         this._name = name;
     5     }
     6 
     7     // 定义类方法
     8     Utils.prototype.sayHi = function() {
     9         console.log("Hi, I am " + this._name);
    10     };
    11 
    12     // 定义静态方法
    13     Utils.add = function(a, b) {
    14         return a + b;
    15     }
    16     
    17     // 将类 Utils 作为当前文件的模块返回
    18     return Utils;
    19 });

    加载和使用的方法如下:

     1 <html>
     2 <head>
     3     <title>Test</title>
     4     <!-- 引入require.js文件 -->
     5     <script src="require.js"></script>
     6     <script>
     7     // 配置各个模块地址
     8     require.config({
     9     paths: {
    10       "Utils": "./js/lib/Utils"
    11     }
    12   });
    13 
    14     // 如果模块地址都放在同一个文件夹中,可以用下面的简写方式
    15     // require.config({
    16     //     baseUrl: "./js/lib",
    17   //   paths: {
    18   //     "Utils": "Utils"
    19   //   }
    20   // });
    21 
    22     // 加载指定模块
    23     require(["Utils"], function(Utils) {
    24         // 模块加载完毕之后,模块中导出的对象会作为参数传入,再回调中直接使用即可
    25         
    26         var obj = new Utils("Li Lei");
    27         obj.sayHi(); // Hi, I am Li Lei
    28 
    29         console.log(Utils.add(10, 20)); // 30
    30     });
    31     </script>
    32 </head>
    33 <body>
    34 </body>
    35 </html>

    多个模块的情况

    我们定义的模块如果还依赖其它的模块,可以这么写:

    1 // 当前定义的模块依赖 Socket 模块
    2 define(["Socket"], function (){
    3 // 当前定义的模块依赖 Socket 及 Game 模块
    4 define(["Socket", "Game"], function (){

    加载模块时也可以加载多个模块:

    1 // 加载 Utils 和 Game 模块,加载好之后,对应模块的导出对象会在回调中作为参数分别传入
    2 require(["Utils", "Game"], function(Utils, Game) {

    更多地用法,可以参考官方文档。

    总结一下

    我们发现,CommonJS和AMD的写法无论是定义模块还是加载模块,都是存在差异的,但是模块内部的写法是基本一致的,所以通过一定的技巧,可以写出兼容两种标准的模块,如下:

     1 (function(golbal, factory){
     2     // AMD
     3     if(typeof define === "function" && define.amd)
     4         define(factory);
     5     // CommonJS
     6     else if(typeof require === "function" && typeof module === "object" && module && module.exports)
     7         module.exports = factory();
     8 })(this, function(){
     9     // 定义类
    10     function Utils(name) {
    11         this._name = name;
    12     }
    13 
    14     // 定义类方法
    15     Utils.prototype.sayHi = function() {
    16         console.log("Hi, I am " + this._name);
    17     };
    18 
    19     // 定义静态方法
    20     Utils.add = function(a, b) {
    21         return a + b;
    22     }
    23     
    24     // 将类 Utils 作为当前文件的模块返回
    25     return Utils;
    26 });

    当然,知道了两种标准的差异之后,我们在使用时,也可以自己修改一个标准的模块为另一个标准可以支持的写法了。

    另外,大部分类库都会提供多种模块格式的代码,比如AMD和CommonJS等格式,选择我们需要的格式即可。

  • 相关阅读:
    day50——前端简介、标签分类、常用标签
    day46——约束条件、表与表建 关系、修改表的完整语法
    day45——存储引擎、数据类型、约束条件
    day44——存储数据的发展、数据库分类、mysql
    Ⅰ:计算机核心基础
    Ⅶ:基本数据类型及内置方法
    Ⅶ:作业
    Ⅵ:深浅copy
    Ⅵ:流程控制
    Ⅳ:运算符
  • 原文地址:https://www.cnblogs.com/hammerc/p/6898707.html
Copyright © 2020-2023  润新知