前端模块化

CommonJs 同步加载模块

  • 使用 require 方法导入所需要的模块,可定义变量赋值,此变量即可执行模块内部的方法(同步引入
1
2
3
4
5
require('module_name')
const cmd = require('module_name')
cmd.method()
cmd.prop
....
  • 使用 module.exports 导出所需要的模块
1
2
3
4
5
6
7
8
const a = 1
const addA = () => {
  a++
}
module.exports {
  a,
  addA
}

需要注意的是,一旦我们拿到了a的值,不管我们如何执行addA,暴露出得a的值还是为1,引用类型除外 看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// cmd.js
let count = 1
let obj = {
  size: 1
}
const addCount = () => {
  count ++
  console.log('addCount', count)
}

const addSize = () => {
  obj.size ++
  console.log('addSize', obj.size)
}

module.exports = {
  count,
  obj,
  addSize,
  addCount
}
1
2
3
4
5
6
7
8
9
// index.js
LogUtils.logInfo(cmd.count, 'CMD.count')    // CMD.count 1
cmd.addCount()    // addCount 2
LogUtils.logInfo(cmd.count, 'CMD.count')    // CMD.count 1


LogUtils.logInfo(cmd.obj.size, 'CMD.obj.size')    // CMD.obj.size 1
cmd.addSize()   // addSize 2
LogUtils.logInfo(cmd.obj.size, 'CMD.obj.size')    // CMD.obj.size 2

特点

  • 简单易用
  • 模块复用(服务器端) 实际上浏览器端也可以用,通过模拟require, exports实现
  • 同步加载的特点让他不适于在浏览器端使用,会阻塞页面加载和代码执行

加载原理

CommonJS的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象

1
2
3
4
5
6
{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。 以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。

循环加载

http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

AMD 异步加载模块

通过回调函数的执行方式,执行异步操作,等到引入的模块加载完成之后才会执行回调函数的代码

  • 通过 define 定义函数模块
  • 通过 require 引入函数模块

在定义模块的过程中,如果定义的模块有需要引入的依赖,需要在 define 内部的回调函数之前添加模块路径地址,可以引入多个模块,使用过程中就是在回调中传入对应顺序的依赖,并为其命名,之后在回调函数内部即可访问到该模块的属性方法 格式如下

1
2
3
4
5
6
7
define(
  '模块id',
  ['依赖数组']function([依赖数组]){     //工厂函数
      ...
  }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// amd.js
define('add', ['./../node_modules/d-utils/lib/index.js'], function(Dutils) {
  let a = 1
  function getA () {
    Dutils.LogUtils.logInfo(a, 'getA')
  }

  function addA () {
    a ++
  }

  return {
    a,
    addA,
    getA
  }
})

同样在使用模块的时候,和define类似

1
2
3
4
5
6
7
8
9
// index.js
require(['./amd'], function(AMD) {
  console.log(AMD.a)
  AMD.addA()
  console.log(AMD.a)

  // getA
  AMD.getA()
})

特点

  • require会执行代码请求,引入需要的模块
  • 异步加载,回调函数内部可以拿到引入的所有模块信息

UMD 通用模块规范

产生的原因是为了整合 CMD 和 AMD 的规范 使用工厂模式定义不同规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function (global, factory) {
  // 判断是否支持cmd
  if (typeof exports === 'object' && typeof module !== undefined) {
    module.exports = factory()
  } else if (typeof define === 'function' && define.amd) {    // 设置amd
    define('add', [], factory)
  } else {
    global.add = factory()      // 添加至全局环境
  }
})(this, function() {
  let a = 1
  function addA () {
    a++
  }
  return {
    a,
    addA
  }
})

特点

  • 兼容cmd amd 模块

ES6模块

  • 通过 import 导入模块
  • 通过 export 导出模块 详细语法可以看阮老师es6的
1
2
3
4
5
6
7
8
9
// add.js
export let a = 1
export const addA = function () {
  a ++
}
export default {
  a,
  addA
}
1
2
3
4
5
6
7
8
9
10
import ADD, {
  a,
  addA as changeAddName
} from './module_name'

LogUtils.logInfo(a, 'Es6')    // 1
ADD.addA()/
LogUtils.logInfo(a, 'Es6')    // 2
changeAddName()
LogUtils.logInfo(a, 'Es6')    // 3

这个和之前的cmd 的例子不一样,es6 在引入之后,修改模块的值,会使模块自身的值也发生变化 看阮老师说的 JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

循环加载

http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

参考于:

谈谈Js前端模块化规范: https://segmentfault.com/a/1190000015991869

commmjs规范: https://www.jianshu.com/p/dd08f4095a49

JavaScript 模块的循环加载: http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

ECMAScript 6 入门: https://es6.ruanyifeng.com/

上一篇 : 如何减少webpack4打包时长下一篇 : react-hooks的学习和使用