模块

在使用 rollup 或者 browserify 时总遇到打包模式选择的问题,这里记录总结一下。

what ?

  • 将一个复杂的程序根据不同的规则(逻辑,分工)分成不同的模块,使之条理的组合在一起工作
  • 模块的内部数据与实现是私有的,外部仅能通过模块暴露的方法与属性与之通信或调用

why ?

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

“原始”的模块化

“原始”指的是时间上的原始,存在于大部分前后未分离的项目中

存在的问题:

  • 全局作用域下容易造成变量冲突
  • 外部可以随意更改内部的数据和方法,维护困难
  • 文件只能按照 script 的书写顺序进行加载,会造成多个请求
  • 开发人员必须主观解决模块和代码库的依赖关系
  • 在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪

全局function模式

将不同的功能封装成不同的全局函数

function foo(){}
function bar(){}

namespace (命名空间)

将数据和功能放在对象中

const module1 = {
  data:'https://qishaoxuan.github.io/blog/',
  foo:function(){},
  bar:function(){}
}

IIFE (立即调用的函数表达式)

匿名函数自调用(闭包)

通过在全局对象 window 上挂载属性或方法来暴露接口

保证模块独立性的同时,也使得模块之间的依赖关系变得明显

(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    console.log(otherFun())
  }
  function bar() {}
  function otherFun() {
    return 'hello, world'
  }
  //暴露行为
  window.module1 = { foo, bar }
})(window, jQuery)
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
  module1.foo()
</script>

模块化规范

为了解决上述“原始”方法的问题,不同的模块规范出现了。

CommonJS

通过 require 方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports 来导出需要暴露的接口

nodejs 采用该规范

// modle1.js
const foo = 1
const bar = () => {return 'hello, world'}

module.exports.foo = foo
module.exports.bar = bar

// 也可以写为

module.exports = {
  foo:foo,
  bar:bar
}
// main.js
const module1 = require('./module1.js')

function aa() {
  console.log(module1.bar())
}

CommonJS 加载模块是同步的,在服务器环境下是没问题的,然而在浏览器中,同步加载会导致性能、可用性、调试和跨域访问等问题。借助 browserify 可以解决,但是也使得下述两种规范的诞生。

AMD

AMD 规范只有一个主要接口 define(id?, dependencies?, factory),它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行,依赖前置。

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue
});
require(["module", "../file"], function(module, file) { /* ... */ })

优点:

  • 适合在浏览器环境中异步加载模块
  • 可以并行加载多个模块

缺点:

  • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
  • 不符合通用的模块化思维方式,是一种妥协的实现

CMD

CMD 与 AMD 的区别

  • 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)
  • CMD推崇依赖就近,AMD推崇依赖前置
// module1.js
//定义没有依赖的模块
define(function(require, exports, module){
  exports.foo = value
  module.exports = value
})
//  module4.js
//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})
// main.js
define(function (require) {
  var m1 = require('./module1')
  var m2 = require('./module4')
})

UMD (通用模块规范)

UMD 兼容了 AMD 和 CommonJS,同时还支持老式的“全局”变量规范:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之类的
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    //    方法
    function a(){}  
    function b(){}
    function c(){} 
 
    //    暴露公共方法
    return {
        b: b,
        c: c
    }
}));

ES6 模块

ECMAScript6 标准增加的 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

// module1.js

export default function foo() {}
// module2.js

export function bar () {}
export const a = 1
// main.js

import jquery as $ from 'jquery'
import foo from './module1'
import {bar,a} from './module2'

ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。