# js模块化发展

渐渐js开始发展到服务器端,而作为服务端语言,所必备的一个条件就是模块化,于是各个社区开始指定js相关的模块规范。而到es6的时候,es官方也给出了模块机制。

# 模块化演变

全局function模式==>namespace模式==>IIFE模式==>IIFE模式增强==>模块模式

  • 全局function模式
function m1(){
  //...
}

问题:污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系

  • namespace模式
let myModule = {
  data: 'www.baidu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data

问题: 数据不安全(外部可以直接修改模块内部的数据)

  • IIFE模式:匿名函数自调用(闭包)
// module.js文件
(function(window) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar } //ES6写法
})(window)


// index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
    myModule.foo()
    myModule.bar()
    console.log(myModule.data) //undefined 不能访问模块内部数据
    myModule.data = 'xxxx' //不是修改的模块内部的data
    myModule.foo() //没有改变
</script>

问题:无法在该模块引入另外一个模块

  • IIFE模式增强 : 引入依赖
// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)

// index.html文件
<!-- 引入的js必须有一定顺序 -->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
  myModule.foo()
</script>

问题:过多的script标签导致请求过多、依赖模糊(不清楚谁依赖谁)、 难以维护 。所以在此基础上,js便开始了产生了模块化的规范。

# 常见的模块规范

AMD(require.js库使用)非同步

//定义没有依赖的模块
define(function(){
   return 模块
})

//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
   return 模块
})

//引入使用模块
require(['module1', 'module2'], function(m1, m2){
   //使用m1/m2
})

CMD(sea.js库使用)异步

//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})

//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
    //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

//引入使用模块
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

CommonJs (node.js中使用)同步

//一个一个 导出
module.exports.foo = function(){}
module.exports.age = 1
exports.a = 'hello' //使用exports导出后不能修改此变量,作为静态变量
 
//整体导出,不能使用exports导出
module.exports = { age: 1, a: 'hello', foo:function(){} }


//导入
const foo = require('./foo.js');

es6 module (react等框架)异步

//导出
export { name1, name2,, nameN }; // 与之前声明的变量名绑定 命名导出
export default expression; // 默认导出
                                 
//导入                            
import defaultExport from 'module';     
//与commonjs的区别
//1、CommonJs导出的是变量的一份拷贝,ES6 Module导出的是变量的绑定(引用);
//2、CommonJs是单个值导出,ES6 Module可以导出多个;
//3、CommonJs是动态语法可以写在判断里,ES6 Module静态语法只能写在顶层;
//4、CommonJs的 this 是当前模块,ES6 Module的 this 是 undefined。
# 以及组合型的模块规范

UMD通用模块 ( 就是AMD+CommonJs+全局变量的组合规范 )

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.libName = factory());//在原生js情况下,此处的global就是指的window对象
}(this, (function () { 'use strict';})));

在实际的情况中,各个类库基本都是将某个规范细微改动后使用,例如node也是将commonjs的模块机制调动后使用的。react也是将自行编译了es6的模块机制。

语法 commonJS ES6 AMD CMD
导出 module.exports = {}
exports = {}
export default {}
export var a = 10
define(id?: String, dependencies?: String[], factory: Function(Object); define(function(require, exports, module) {});
导入 require(‘module’) import module from ‘module’ require([‘myModule’], function(myModule) {}); var a = require(‘./a’);
require.async(‘./b’, function(b) {});
加载 动态 – 同步 静态 动态-异步 动态-同步或异步
导出的对象 副本 引用
多次导出 同一个对象
模块作用域 文件 文件 文件
循环加载时 加载已执行的部分
浏览器支持
node支持
典型环境 nodejs babel,vue requirejs seajs
引用目录时 找package.json定义的main,或index