前端模块化编程

备注:此文章为多文章参考后整理
 
前端模块化规范有三种:
  • CommonJs
  • AMD
  • CMD
CommonJs用在服务器端,AMD和CMD用在浏览器环境。
 
浏览器环境模块化对比:
  • AMD(RequireJS):提前执行(异步加载:依赖先执行)+ 延迟执行;即依赖关系前置,在定义模块的时候就要声明它所依赖的模块。
  • CMD(SeaJS):延迟执行(运行到需加载,根据顺序执行);即按需加载,依赖就近,在需要某个模块的时候,再去require。
 
 
1.CommonJS服务器端模块的规范,由nodejs推广使用
 
根据CommonJS规范:
    a.  一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被            其他模块读取,除非定义为global对象的属性。
    b. 输出模块变量的最好方法是使用module.exports对象。
    c. 加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象。
 
 
用一套标准模板封装模块定义:
define(function(require, exports, module) {

  // The module code goes here

});
  • require()用来引入外部模块;
  • exports对象用于导出当前模块的方法或变量,唯一的导出口;
  • module对象就代表模块本身。
 
这套代码为模块加载器提供了机会,使其在模块代码执行之前,对模块代码进行静态分析,并动态生成依赖列表。
 
math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});

increment.js

define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});

index.js

define(function(require, exports, module) {
  var inc = require('increment').increment;
  inc(1); // 2
});

Browserify 是目前最常用的 CommonJS 格式转换的工具。

 
2.AMD
 
        AMD是 "Asynchronous Module Definition" 的缩写,意思就是"异步模块定义"。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
 
RequireJS 主要解决两个问题
  • 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
  • js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
RequireJs也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
第一个参数是一个数组,表示所依赖的模块,eg:['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;
第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
语法:
require([module], callback);

代码示例:

require([‘moduleA’, ’moduleB’, ’moduleC'], function(moduleA, moduleB, moduleC) {

    //do something

});
require([‘jquery’, ‘underscore’, ‘backbone’], function($, _, backbone){
    
    //do something

});

require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

 

define()函数

RequireJS定义了一个函数 define,它是全局变量,用来定义模块:
define(id?, dependencies?, factory);

参数说明:

  • id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
  • 依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
    依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择 以函数的长度属性指定的参数个数 调用工厂方法。
  • 工厂方法factory,模块初始化要执行的函数或对象。 如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
示例代码:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
      exports.verb = function() {
          return beta.verb();
          //Or:
          return require("beta").verb();
      }
});

 

RequireJS使用示例:

require.config是用来定义别名的,在paths属性下配置别名。然后通过requirejs(参数一,参数二);参数一是数组,传入我们需要引用的模块名,第二个参数是个回调函数,回调函数传入一个变量,代替刚才所引入的模块。

main.js
//别名配置
requirejs.config({
    paths: {
        jquery: 'jquery.min' //可以省略.js
    },   
    shim: {//适用于非AMD规范编写的库
        ‘underscore’:{
            exports:’_'
        },
        ‘backbone’:{
            deps:[‘underscore’,’jquery'],
            exports:’backbone'
        },
        ‘jquery.scroll’:{
            deps:[‘jquery’],
            exports:’jQuery.fn.scroll'
        }
    }
});

 

//引入模块,用变量$表示jquery模块
requirejs(['jquery'], function ($) {
    $('body').css('background-color','red');
});

引入模块也可以只写require()requirejs通过define()定义模块,定义的参数上同。在此模块内的方法和变量外部是无法访问的,只有通过return返回才行。

math.js
define('math',['jquery'], function ($) {//引入jQuery模块
    return {
        add: function(x,y){
            return x + y;
        }
    };
});

将该模块命名为math.js保存。

require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));//110
});

main.js引入模块方法。

RequireJS减少http请求个数的优化方法: http://requirejs.org/docs/optimization.html
 
 
3.CMD
 
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
在 CMD 规范中, 一个模块就是一个文件。代码的书写格式如下:
define(function(require, exports, module) {

  // 模块代码

});

require是可以把其他模块导入进来的一个参数;

exports 是可以把模块内的一些属性和方法导出;
module  是一个对象,上面存储了与当前模块相关联的一些属性和方法。
AMD是 依赖关系前置 , 在定义模块的时候就要声明其依赖的模块;
CMD是 按需加载依赖就近 ,只有在用到某个模块的时候再去require。
代码示例:
// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 依赖可以就近书写
  b.doSomething()
  // ...
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  ...
})

seaJS使用示例:

// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

// 加载模块
seajs.use(['myModule.js'], function(my){
    var star= my.data;
    console.log(star);  //1
});

 

4.UMD(跨平台模块化,项目常用)
 
      上面介绍的CommonJS和AMD等模块化方案都是针对特定的平台,如果想要实现跨平台的模块化,就得引入UMD的模块化方式。UMD是通用模块定义(Universal Module Definition)的缩写,使用该中模块化方案,可以很好地兼容AMD, CommonJS等模块化语法。
      接下来,让我们通过一个简单地例子看一下如何使用和定义UMD模块:
 
加载jquery依赖:
(function(root, factory) { 
    if(typeof define === 'function' && define.amd) { 
        //AMD
        define(['jquery'], factory); 
    } else if(typeof module === 'object' && typeof module.exports === ‘object’) { 
        //commonJS, node etc.
        module.exports = factory(require('jquery')); 
    } else { 
        //Browser globals. Root is window
        root.UmdModule = factory(root.jQuery); 
    } 
}(this, function(jquery) { 
    // 现在你可以利用jquery做你想做的事了 
    // methods
    function myFunc(){};

    // exposed public method 
    return myFunc;
}));

加载jquery和underscore依赖:

(Function(this, factory){
    if(typeof define === ‘function’ && define.amd){
        //AMD
        define([‘jquery’,’underscore'], factory);
    }else if(typeof module === ‘object’ && typeof module.exports === ‘object'){
        //commonJS node
        module.exports = factory(require(‘jquery’), require(‘underscore’));
    }else{ 
        //browser globals(root is window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function($, _){
    function a(){};//private
    function b(){};//public
    function c(){};//public
    return {
        b:b,
        c:c
    }
}));

地图数据初始化之前加载echarts依赖:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports', 'echarts'], factory);
    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
        // CommonJS, node
        factory(exports, require('echarts'));
    } else {
        // Browser globals
        factory({}, root.echarts);
    }
}(this, function (exports, echarts) {
    // do something
}));

      这种模块定义方法,可以看做是IIFE的变体。不同的是它倒置了代码的运行顺序,需要你将所需运行的函数作为第二个参数传入。由于这种通用模块的适用性强,很多JS框架和类库都会打包成这种形式的代码。

 
 
5.ES6 Modules
 
      对于ES6来说,不必再使用闭包和封装函数等方式进行模块化支持了。在ES6中,从语法层面就提供了模块化的功能。然而受限于浏览器的实现程度,如果想要在浏览器中运行,还是需要通过Babel等转译工具进行编译。ES6提供了importexport命令,分别对应模块的导入和导出功能。具体实例如下:
// demo-export.js 模块定义
var name = "scq000" 
var sayHello = (name) => { console.log("Hi," + name); } 
export {name, sayHello}; 

// demo-import.js 使用模块 
import {sayHello} from "./demo-export"; sayHello("scq000");
      对于具体的语法细节,想必大家在日常使用过程中都已经轻车熟路了。但对于ES6模块化来说,有以下几点特性是需要记住的:
  • ES6使用的是基于文件的模块。所以必须一个文件一个模块,不能将多个模块合并到单个文件中去。
  • ES6模块API是静态的,一旦导入模块后,无法再在程序运行过程中增添方法。
  • ES6模块采用引用绑定(可以理解为指针)。这点和CommonJS中的值绑定不同,如果你的模块在运行过程中修改了导出的变量值,就会反映到使用模块的代码中去。所以,不推荐在模块中修改导出值,导出的变量应该是静态的。
  • ES6模块采用的是单例模式,每次对同一个模块的导入其实都指向同一个实例。
 
 
6.webpack中的模块化解决方案
 
      作为现代化的前端构建工具,Webpack还提供了丰富的功能能够使我们更加轻易地实现模块化。利用Webpack,你不仅可以将Javascript文件进行模块化,同时还能针对图片,css等静态资源进行模块化。你可以在代码里使用CommonJS, ES6等模块化语法,打包的时候你也可以根据需求选择打包类型,如UMD, AMD等:
module.exports = { 
    //... output: { library: 'librayName', libraryTarget: 'umd', // 配置输出格式 filename: 'bundle.js' } 
};

       另外,ES6模块好处很多,但是并不支持按需加载的功能, 而按需加载又是Web性能优化中重要的一个环节。好在我们可以借助Webpack来弥补这一缺陷。Webpack v1版本提供了require.ensureAPI, 而2.x之后使用了import()函数来实现异步加载。具体的代码示例可以查看 前端性能优化之加载技术 这篇文章。

 
总结
 
      模块化方案解决了代码之间错综复杂的依赖关系,不仅降低了开发难度同时也让开发者将精力更多地集中在业务开发中。随着ES6标准的推出,模块化直接成为了Javascript语言规范中的一部分。这也意味着CommonJS, AMD, CMD等模块化方案终将退出历史的舞台。当然,要实现完全ES6模块化的使用,还需要一段长时间的等待。那么,在这段过渡的时间里,我们可能仍然需要维护旧有的代码,使用传统的模块化方案来构建应用。对于前端工程师来说,系统地了解主流的模块化方案就显得十分必要了。最后,让我们再一次回顾一下各种模块化方式的特点:
模块化方案
加载
同步/异步
浏览器
服务端
模块定义
模块引入
IFEE
取决于代码
取决于代码
支持
支持
IFEE
命名空间
AMD
提前预加载
异步
支持
构建工具r.js
define
require
CMD
按需加载
延迟执行
支持
构建工具spm
define
define
Common
值拷贝,
运行时加载
同步
原生不支持,需要使用browserify提前打包编译
原生支持
module.exports
require
UMD
取决于代码
取决于代码
支持
支持
IFEE
命名空间
ES Modules
(ES6)
实时绑定,
动态绑定,
编译时输出
同步
需用babel转译
需用babel转译
export
import
 
 
 
 
 
 
 
 

转载于:https://my.oschina.net/jingyao/blog/3084533

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值