前端模块化

555 阅读5分钟

背景说明

网络和时代在不断的发展,JavaScript不再仅仅是实现简单的页面交互逻辑,前端JS代码日益增多,如何管理好这些JS代码成为了一个难题。这时候就有了前端代码模块化的概念,在前端代码模块化的过程中,出现了很多模块化规范。例如CommonJS,AMD,CMD,ES6 Module规范,它们有着各自的优缺点。

1.为什么要模块化?

很久很久以前,前端是这样引入各个JS文件的

<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="main.js"></script>
<script src="other1.js"></script>
<script src="other2.js"></script>
<script src="utils.js"></script>

就是这么简单的放在一起 有什么缺点?(难以维护,依赖模糊,请求过多)

  1. 使用这些文件的顺序不能出错。比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。
  2. 污染全局作用域 例如utils.js文件中有each方法,log方法,那么再在别的文件不能再定义each方法,log方法(Yahoo有试过一个解决方案,如下,但仍不理想,其本质上仍然是一个对象)
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};
org.CoolSite.Utils.each = function (arr) {
  // 实现代码
};
org.CoolSite.Utils.log = function (str) {
  // 实现代码
};
  1. 维护成本太高了,要找一个方法太困难。同时依赖关系也非常不明确。

2.模块化的好处有什么?

  1. 便于维护
  2. 便于复用
  3. 避免命名冲突
  4. 更好的分离,按需加载

3.模块化总共出现了几种规范?

CommonJS(Node.js) => AMD(require.js) => CMD(Sea.js) => ES Module(ES6模块化)
听说还有个UMD 它又是啥?
严格上说,umd不能算是一种模块规范,它没有模块定义和调用,这是AMD和CommonJS的结合体,保证模块可以被AMD和CommonJS调用 UMD其实就是AMD和CommonJS的组合,它的实现原理是啥?

  1. 先判断是否支持Node.js模块格式(判断exports是否存在),存在则使用Node.js模块格式
  2. 再判断是否支持AMD(判断define是否存在),存在则使用AMD方式加载模块
  3. 前两个都不存在,则将模块公开到全局(window或global)

4.为什么CommonJS还不错,却还需要AMD,CMD?

CommonJS是用同步的方式加载模块。 服务端,模块文件都存储在本地磁盘,读取很快,所以没问题 但是在浏览器端,限于网络原因,CommonJS并不适合浏览器端模块加载

于是就有了AMD,CMD这两个专门用于浏览器端的规范,他们都是异步加载模块

AMD规范是非同步加载模块,允许指定回调函数, 缺点是不能按需加载、开发成本大。 CMD规范是通过按需加载, 缺点是依赖SPM打包,模块的加载逻辑偏重。

5.同样适用于浏览器端,AMD和CMD有什么区别?

  1. AMD预先加载所有的依赖,直到使用的时候才执行(依赖前置)
    CMD推崇 依赖就近

    // CMD

    define(function(require, exports, module) {  
         var a = require('./a')   
         a.doSomething()
         var b = require('./b') // 依赖可以就近书写   
         b.doSomething()   
     })
    

    // AMD

    define(['./a', './b'], function(a, b) {  
      // 依赖必须一开始就写好
        a.doSomething()
        b.doSomething() 
     })
    
  2. 对于依赖的模块
    AMD是提前执行(不过RequireJS从2.0开始,也改成可以延迟执行)
    CMD是延迟执行(as lazy as possible 懒执行)

  3. AMD的API默认是一个当多个用,CMD推崇职责单一
    例如:AMD里require分全局的和局部的。CMD里面没有全局的require,提供seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

6.CommonJS和ES6 Module有什么区别?

  1. CommonJS模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
  • 值的拷贝: 一旦输出一个值,模块内部的变化就影响不到这个值
  • 值的引用: 原始值变了的话,import加载的值也会跟着变
  1. CommonJS模块是运行时加载,ES6模块是编译时输出接口
  • 运行时加载: CommonJS模块就是对象 输入时先加载整个模块,生成一个对象,再从这个对象上读取方法
  • 编译时加载: ES6模块不是对象 通过export import可以指定加载某个输出值

7.目前开发主流的模块化规范是什么?

服务端,node 主要使用的就是CommonJS
而日常开发非服务端项目 主要使用ES6 Module,但是由于ES6目前无法在浏览器中执行,要通过babel将不被支持的import编译为当前受到广泛支持的require

8.CommonJS语法 和ES6 Module语法

  • CommonJS
module.export = value
require()
  • ES6 Module
export  
import

9.AMD语法

/**
 * 基本思想 通过define方法,将代码定义为模块
 *          通过require方法,实现代码的模块加载
 */
 define(['dataService','jquery'],function(dataService){
     let name = 'Tom'
     function showMsg(){
         alert(dataService.getMsg() + name)
     }
     $('body').css('background','green')
     //暴露模块
     return { showMsg }
 })

 // main.js文件
(function() {
    require.config({
      baseUrl: 'js/', //基本路径 出发点在根目录下
      paths: {
        //自定义模块
        alerter: './modules/alerter', //此处不能写成alerter.js,会报错
        dataService: './modules/dataService',
        // 第三方库模块
        jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
      }
    })
    require(['alerter'], function(alerter) {
      alerter.showMsg()
    })
})()

10.CMD语法

define(function(require,exports){
    exports.each = function(arr){
        //实现代码
    }
    exports.log = function(str){
        console.log(str)
    }
    var testdemo = require('./comment.js')
});
define(function(require,exports){
    var util = require('./util.js');
    exports.init = function(){
        //实现代码
    }
})
//通过require引入
require('./util.js')

参考文章