说说你对模块化的理解(CommonJS、AMD、CMD、ES Module)

模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块规范有common js、AMD、CMD、ES Module规范

Commonjs

Node.js是Commonjs规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块

// 定义模块math.js
const basicNum = 0;
function add(a, b) {
  return a + b;
}
module.exports = { //在这里写上需要向外暴露的函数、变量
  add,
  basicNum
}

// 引用自定义的模块时,参数包含路径,可省略.js
const math = require('./math');
math.add(2, 5);

commonjs出来以后服务端的模块概念基本已经形成,但是commonjs是同步的方式加载模块,在服务端,模块文件都存在本地磁盘,读取可以很快完成同步加载,但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。所以出现了异步模块加载AMD。

AMD

AMD是异步模块定义,采用异步方式加载模块,模块的加载不影响后面语句的运行,所有依赖这个模块的语句都定义在他的一个回调函数中,等加载完成之后再执行回调函数。同期推出了一个有名的库叫requirejs,他实现了AMD规范。AMD通过define函数来定义模块,通过requie函数来引入模块,第一个参数都是一个数组,数组中是依赖模块,第二个参数是一个回调,回调的参数是依赖模块的输出。

requirejs在申明依赖的模块时会在第一时间加载并执行模块内的代码,就会存在即使没用到某个模块还是会加载执行的情况

// utils.js
define([], function() {
    return {
        add: function(a, b) {
            console.log(a + b)        
        }    
    }
})
// main.js 文件
require(['./utils'], function(utils) {
    utils.add(1, 2)    
})
// 多个js文件存在依赖关系,需要提前加载所有的依赖
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
    // 等于在最前面声明并初始化了要用到的所有模块
    a.doSomething();
    if (false) {
      // 即便没用到某个模块 b,但 b 还是提前执行了
      b.foo()
    } 
})

CMD

CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇就近依赖、延迟执行。比如AMD会在申明依赖的第一个参数中列出所有的模块,CMD是在需要该模块的时候再进行require引入,从而实现了按需加载

/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
    // 等于在最前面声明并初始化了要用到的所有模块
    a.doSomething();
    if (false) {
        // 即便没用到某个模块 b,但 b 还是提前执行了
        b.doSomething()
    } 
})

/** CMD写法 **/
define(function(require, exports, module) {
  var a = require('./a'); //在需要时申明
  a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }  
})

ES Modules

ES6 在语言标准的层面上,实现了模块功能,其模块功能主要由两个命令构成:export和import。

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

/** 定义模块 math.js **/
let basicNum = 0;
let add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模块 **/
import { basicNum, add } from './math';
add()
console.log(basicNum)

CommonJs 和 ES Modules的区别

 1. Commonjs模块输出的是一个值的拷贝,ES6模块输出的是值的引用

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

// main.js
let count = 0
function add () {
    count++
}
module.exports = { count, add }
// utils.js
let utils = require('./main')
utils.add() // 此时模块内部的count以发生改变
console.log(utils.count) // 但是模块内部的变化不会影响到值 依旧输出的是0

ES6模块输出的是值的引用。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本执行时,再根据这个只读引用去模块中取值。即如果原始值变了,import加载的值也会变

// main.js
export let count = 0
export function add () {
  count++
}

// index.html
<script type="module">
    import { count, add } from './main.js'
    add()
    console.log(count) // 输出1
</script>

2.Commonjs模块是运行时加载,ES6模块是编译时输出接口

  • Commonjs加载的是一个对象(即module.exports属性),该对象只有在脚本运行完成才会生成,然后从这个对象上面读取方法,这种加载称为“运行时加载”
  • ES6模块不是对象,而是通过export命令显示指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”

总结

模块化 场景 特点 语法
CommonJS node、webpack 同步加载、磁盘读取速度快

导出:通过module.exports或exports导出

引用:require(' xx ')

AMD 浏览器端,不常用 异步加载,依赖前置,预先加载所有的依赖

导出:通过define定义模块

引用:require()

CMD 浏览器端,不常用 异步加载,依赖就近,按需加载

导出:通过define定义模块

引用:require()

ES Module 目前浏览器端的默认标准 静态编译

导出:通过export或export default输出模块

引用:import .. from 'xxx'

猜你喜欢

转载自blog.csdn.net/weixin_45313351/article/details/127449787