js 模块化规范的发展历程

模块化的发展历程:

按照诞生时间的先后顺序

  1. CommonJs,2009年1月
  2. AMD Asynchronous Module Definition, 2011年2月
  3. CMD Common Module Definition, 2011年4月后
  4. UMD Universal Module Definition, 2014年9月
  5. ESM EcmaScript Module, 2016年5月

一、CommonJs 模块规范

第一阶段: Modules/1.0, 产物 browserify
第二阶段: Modules/Async, 诞生了 Modules/AsynchronousDefinition 规范
第三阶段: Modules/2.0,基于 CommonJS 规范的实现:BravoJS

  • 运行时加载,一个文件就是一个模块
  • 更适合服务端,node.js 采用了这个规范
  • 同步加载模块,不支持异步
  • 使用 require/exports 关键字

二、AMD 和 CMD

AMDRequireJS 在推广过程中对模块定义的规范化产出。

  • 一种基于模块的异步加载的js代码机制
  • 通过延迟和按需加载来解决各个模块之间的依赖关系,依赖前置、提前执行
  • define 关键字用于定义模块,require 关键字用来引用模块

CMD 是阿里的玉伯(王保平)在借鉴了CommonJs 和 AMD 方案后写出的 SeaJS 在推广过程中对模块定义的规范化产出。

  • 与AMD大体相同,也是异步加载
  • 特点是 依赖就近、延迟执行

AMD 和 CMD 的依赖和执行时机的比较

1、AMD:

// a.js
define(function () {
    console.log('init a.js');
    return {
        getFun: function () {
            return 'I am a';
        }
    };
});
// b.js
define(function () {
    console.log('init b.js');
    return {
        getFun: function () {
            return 'I am b';
        }
    };
});
// index.js
define(function (require) {
    var a = require('./a');
    console.log(a.getFun());
    var b = require('./b');
    console.log(b.getFun());
});
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
<script>
    requirejs(['index']);
</script>
// 输出结果如下 
// init a.js
// init b.js
// I am a
// I am b

2、CMD:

// a.js
define(function (require, exports, module) {
    console.log('init a');
    exports.getFun = function () {
        return 'I am a';
    }
});
// b.js
define(function (require, exports, module) {
    console.log('init b');
    exports.getFun = function () {
        return 'I am b';
    }
});
// index.js
define(function(require, exports, module) {
    var a = require('./a'); //在需要时申明
    console.log(a.getFun());
    var b = require('./b'); //在需要时申明
    console.log(b.getFun());
});
<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
<script>
    seajs.use('./index.js')
</script>
// 输出 
// init a
// I am a
// init b
// I am b

三、UMD

  • 前后端跨平台的解决方案
  • 兼容 AMD 和 CommonJS 的规范,并支持传统的声明全局变量的方式
    1. 先判断是否支持 AMD(define是否存在),存在则使用 AMD 方式加载模块
    2. 再判断是否支持 Node.js 模块格式( exports 是否存在),存在则使用 Node.js 模块格式
    3. 前两个都不存在,则将模块公开到全局( window 或 global )
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, 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
    }
}));

四、ESM

2015年6月,ES6/ES2015 正式通过决议,从语言层面提出的一种模块化标准。

  • 编译时加载,模块加载从入口文件开始,最终生成完整的模块实例关系图,过程包含:
    1. 构建:查找,下载,然后把所有文件解析成 module record。
    2. 实例化:为模块分配内存空间,依照导入,导出语句把模块指向对应内存地址。
    3. 运行:把内存空间填充为真实值。
  • node V8.5.0+ 对其进行了支持
  • 使用 import / export 关键字

导入导出方式

1. 默认导入导出

使用 export default 关键字。

// 文件 a.js
// 导出默认对象
export default {
  // 定义 hello 方法,输出欢迎信息
  hello(name) {
    console.log(`Hello, ${name}!`)
  },
  // 定义 byebye 方法,输出道别信息
  byebye(name) {
    console.log(`byebye, ${name}!`)
  },
  // 定义 userInfo 对象,存储用户信息
  userInfo: {
    name: 'vavid', // 用户名
    age: 18 // 用户年龄
  }
}

// 文件 b.js
// 引入 a.js 中默认导出的模块
import defaultModule from './a.js'

// 调用 defaultModule 中定义的 hello() 方法,输出欢迎信息并传入用户姓名
defaultModule.hello(defaultModule.userInfo.name) // Hello,vavid!

// 调用 defaultModule 中定义的 byebye() 方法,输出道别信息并传入用户姓名
defaultModule.byebye(defaultModule.userInfo.name) // byebye,vavid!

2.具名导入导出

具名导出,使用 export 关键字。

// 文件 a.js
// 定义 hello 方法,输出欢迎信息
export function hello(name) {
  console.log(`Hello, ${name}!`)
}

// 定义 byebye 方法,输出道别信息
export function byebye(name) {
  console.log(`byebye, ${name}!`)
}

// 定义 userInfo 对象,存储用户信息
export const userInfo = {
  name: 'vavid', // 用户名
  age: 18 // 用户年龄
}
// 文件 b.js
// 引入 a.js 中具名导出的模块
import { byebye, hello, userInfo as user } from './a.js'

// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(user.name) 

// 调用 hello() 方法,输出欢迎信息并传入用户姓名
hello(user.name)

Tips: 使用 as 关键字还可以修改导入内容的名称。

3.全部导入导出

可以将另一个模块的内容直接全部导出。

导出同时也可以设置默认导出。

// 文件 b.js

// 从 a.js 中导出所有的模块成员
export * from './a.js'

// 导出一个默认模块,对象包含 goal 属性,初始值为 'learn'
export default {
  goal: 'learn'
}

使用 import * as xx from 'module' 导入所有内容。

// 文件 c.js
// 导入 b.js 中所有被导出的模块成员,并作为 allValues 对象的属性
import * as allValues from './b.js'

// 在控制台输出 allValues 对象
console.log(allValues)

// 从 allValues 对象中解构出 hello、byebye、default 和 userInfo 模块成员
const { hello, byebye, default: data, userInfo } = allValues

// 调用 hello() 方法,输出欢迎信息并传入用户姓名
hello(userInfo.name)

// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(userInfo.name)

// 输出 data 对象的 goal 属性
console.log(data.goal) // learn

4.重新导出

// lib.js
export function hello(name) {
  console.log(`Hello, ${name}!`)
}

export default {
  filename: 'lib.js',
  des: 'lib.js的一些默认导出'
}

// util.js
export function byebye(name) {
  console.log(`ByeBye, ${name}!`)
}

export default {
  filename: 'util.js',
  des: 'util.js的一些默认导出'
}

// index.js
// 从 './lib.js' 中导出 hello 和默认导出并重命名为 libData
export { hello, default as libData } from './lib.js'

// 从 './util.js' 中导出所有命名导出
export * from './util.js'

// 从 './util.js' 中默认导出并重命名为 utilData
export { default as utilData } from './util.js'

此时通过 index.js 统一对外导出。

// usage.js
import { hello, byebye, libData, utilData } from './index.js'

hello(libData.filename) // Hello, lib.js!
byebye(utilData.filename) // ByeBye, util.js!


参考

https://github.com/amdjs/amdjs-api/wiki/AMD
CMD 模块定义规范
Sea.js 与 RequireJS 的异同
https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
编程时间简史

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值