js模块化发展历程 总结

3,053 阅读4分钟

前端技术变化太快了,新入坑的同学往往会一脸懵逼,特此总结自己在学习过程中对js模块化这一方面的理解

一、模块化规范种类

image.png

二、发展历史趋势

image.png

step1、2002 - 命名空间模式

命名空间模式

  • 大型项目还是不好维护
  • 没有解决模块间依赖问题

step2、2009 - CommonJS

号召规范服务端的js接口,形成了ServerJs规范(即CommonJs)CommonJS 内的模块规范成为了 Node.js 的标准实现规范

  • require (同步下载的)
  • 随着 Node.js 以及 Browserify 的流行,越来越多的开发者也接受了 CommonJS 规范

step3、2009年 - AMD 规范

关注js模块的异步加载 提出 AMD 规范,require.js、curl是其对应实现

  • 源自CommonJS,但是 异步的加载的
  • 模块下载完后,立即执行加载,所有模块加载完毕进入回调
  • 随着以 npm (遵循CommonJS规范)为主导的依赖管理机制的统一,越来越多的开发者放弃了使用 AMD 模式。

什么是异步加载? 蓝色是 下载文件  红色是 加载js 绿色是解析html

image.png

step4、2011年 - UMD规范

为了支持一个模块同时兼容AMD和CommonJs规范,适用于 同时支持浏览器端和服务端引用的第三方库,提出了 UMD规范

step5、2011年 - CMD规范

require.js 需要提前声明 所依赖的库,为了做到 看起来"使用时才加载"(就近依赖),创造了sea.js,同时其对应CMD规范

  • 下载完后,并不立即执行,回调函数中遇到require才执行加载模块

step6、2015年 - ES2015 Modules

爸爸(ECMAScript 标准的起草者 TC39 委员会)不能坐视不理,推出了ES2015 Modules(import、export),最后有了ES6

  • 导入的值也是只读不可变对象(丧失了CommonJS的修改特性,但也是一个优点,保证了 ES6 Modules 的依赖关系是确定(Deterministic)的,和运行时的状态无关,从而也就保证了 ES6 Modules 是可以进行可靠的静态分析的。),不像CommonJS是一个内存的拷贝

step7、终极奥义 - 现代工具webpack

webpack 自己实现了一套模块机制,无论是 CommonJS 模块的 require 语法还是 ES6 模块的 import 语法,都能够被解析并转换成指定环境的可运行代码。随着webpack打包工具的流行,ES6语法广泛手中,后来的开发者对于 AMD CMD的感知越来越少

三、使用姿势

CommonJS

// file greeting.js 定义一个模块
var helloInLang = {
    en: 'Hello world!',
    es: '¡Hola mundo!',
    ru: 'Привет мир!'
};

var sayHello = function (lang) {
    return helloInLang[lang];
}
// 对外输出
module.exports.sayHello = sayHello;

// file hello.js  引入一个模块
var sayHello = require('./lib/greeting').sayHello;
var phrase = sayHello('en');
console.log(phrase);

AMD

// file lib/greeting.js  定义一个模块
define(function() {
    var helloInLang = {
        en: 'Hello world!',
        es: '¡Hola mundo!',
        ru: 'Привет мир!'
    };

    return {
        sayHello: function (lang) {
            return helloInLang[lang];
        }
    };
});

// file hello.js  引入一个模块
define(['./lib/greeting'], function(greeting) {
    var phrase = greeting.sayHello('en');
    document.write(phrase);
});

UMD

// 定义一个模块
(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
    }
}));

CMD

// 定义一个模块

define(function(require, exports, module) {
   ....
})


//sea.js:
define(function(require, exports, module) {

    var mod_A = require("dep_A");
    var mod_B = require("dep_B");
    var mod_C = require("dep_C");
});

ES2015 Modules

// file lib/greeting.js 定义一个模块
const helloInLang = {
    en: 'Hello world!',
    es: '¡Hola mundo!',
    ru: 'Привет мир!'
};
// 对外输出
export const greeting = {
    sayHello: function (lang) {
        return helloInLang[lang];
    }
};

// file hello.js 引入一个模块
import { greeting } from "./lib/greeting";
const phrase = greeting.sayHello("en");
document.write(phrase);

四、对比

AMD vs CMD

AMD CMD
依赖前置
下载完后,执行加载,所有模块加载完毕进入回调
就近依赖
下载完后,并不执行加载,回调函数中遇到require才执行加载

requre.js vs sea.js

require.js sea.js
RequireJS在实现AMD的同时,还提供了一个CommonJS包裹 专注于web

五、参考

javascript基础修炼(4)——UMD规范的代码推演
www.cnblogs.com/dashnowords…

JavaScript 模块演化简史
mp.weixin.qq.com/s?__biz=MjM…

前端模块化
www.cnblogs.com/dolphinX/p/…

webpack 模块加载机制
blog.csdn.net/weixin_3419…

前端模块化(CommonJs,AMD和CMD)
www.jianshu.com/p/d67bc7997…

让我们再聊聊浏览器资源加载优化
www.infoq.cn/article/bro…

前端模块化开发那点历史
www.zhihu.com/question/20…