小结AMD,CMD,UMD,CommandJS,ES Module

2,329 阅读4分钟

首发于掘金
原文链接
转载请写明掘金链接

前言 🎤

JS中,最为伟大的,也可说是最为可赞的就是劳动人民的双手,他们让JS原本不带有的功能,用各种各样的神奇方式,把这些功能都实现出来。而模块加载,也是JS进化历程上的一部可圈可点的赞歌。

鼻祖CommandJS

CommandJS是服务端JS的标准规范,特点是只能用于服务端,因此才有AMD出现的机会。CommandJS核心为requiremodule.exports。同时加载方式为同步加载。

声明

//math.js
module.exports.add = function(){}

在CommandJS中的声明方式就是对module.exports或者exports进行修改,具体差异不再赘述,可以看我之前文章。

使用

// index.js
const math = require('./math')
math.add()

使用方法也异常的简单,这是因为Node在底层做了很多其他事情来保证开发者能几乎无痛的使用。

循环依赖

Node的循环依赖处理方式是:如果有循环,执行到哪就算哪。简单来说就是,当A模块开始加载时,缓存中会立刻出现A模块的module.exports,当require(b),并且b中require(a)的时候,b只能获取到循环依赖之前的a。

//a.js
module.exports.max = 10
require(b)
module.exports.max = 20
//b.js
const a = require(a)
console.log(a.max) // 10

AMD

AMD是RequireJS使用的规范,是为了浏览器中的模块加载而实现的。并且AMD是异步加载。而AMD的设计思路,也是参考了一部分CommandJS的。
AMD是声明和加载方式非常的简单易懂。

声明

define(['jquery', 'underscore'], function ($, _) {
    //    methods
    function a(){};    //    private because it's not returned (see below)
    function b(){};    //    public because it's returned
    function c(){};    //    public because it's returned

    //    exposed public methods
    return {
        b: b,
        c: c
    }
});

只有在有依赖的情况下,才需要进行定义依赖,否则可以直接传入内容,比如:

// math.js
define(function(){
    return {
        add:function(){}
    }
})

使用

使用则是:

require(['math'], function(math) {
    console.log(math.add());
})

循环依赖

那么AMD是如何解决循环依赖的?其实就是强制忽略了,比如有两个模块A,B。当A依赖B,然后B依赖A的时候,B获取到的A是为未定义的状态。而且总是会把依赖的模块执行完成,也就是说B一定会被先执行完成。

UMD

UMD是一个为了解决跨平台MD导入问题,它的解决方法就是通过揉合CommandJSAMD来解决这个问题,不得不说,思路很清晰。

// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();
  }
}(this, function () {

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

其实它就是自定义了一种包装方式,并且对每种环境下进行不同处理。如果是有依赖的情况,一样会进行特殊情况处理。

CMD

CMD的存在感太低,它的各个方面和AMD都非常像,只不过CMD推崇依赖就近

define(function(require,exports,module){
    const a = require('a')
    a.x()
})

但是也是延迟执行,还是要等加载完。判断方式就是正则啦。

Function.prototype.toString.call(function(){
    console.log('i am Magic')
})

说白了,其实就是让写法更舒服了。

平行世界 ES6 Module

在多维宇宙中,因为一些特殊的,细小的变动,导致在平行宇宙的地球上,ES Module成为了唯一的前后端通用模块加载规范。而2015,在第四次太阳🌞异常活动导致的宇宙波裂变,使得ES Module的灵感传递到了我们所处的地球🌍上来。因为传递时间是2015年,所以这个功能也称为ES2015 Module或者ES6 Module。——我瞎掰的。
ES Module的诞生可以说是真正意义上的解决了前后端模块导入的问题,不过生不逢时,并没有能彻底的取代Node的CommandJS的地位,但是也是一种可喜可贺的划时代创新。

声明

ES6 Module的声明喜闻乐见,这里简单说说

// math.js
export function add(){}

export default {
    add
}

export var name = 'box'

使用

import {add} from './math'
add()

循环依赖

ES6 Module其实并不关心有没有循环依赖,他并不需要产生结果,他只需要给你一个引用即可,至于是否能取到值,那么就需要开发者自己来保证了。

奇点🎆 Webpack

Webpack带来的影响可以说是翻天覆地,你基本不用再关心什么模块导入方式,伟大的Webpack会使用他全知全能的处理方式帮你解决这些问题,你再也不用担心如何在服务端使用ES Module,或者在前端使用CommandJS(Browserify)。

总结 👨‍🏫

如果看不全可以👉滑动

*AMDCommandJSUMDCMDES Module
使用在浏览器
使用在服务端
异步加载✅(允许)✅(允许)

同时,只有ES Module为传递符号引用,其余均为对象。