Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端模块化:CommonJS & AMD & CMD & UMD & ES6 Module #11

Open
xchunzhao opened this issue Sep 25, 2019 · 0 comments
Open

前端模块化:CommonJS & AMD & CMD & UMD & ES6 Module #11

xchunzhao opened this issue Sep 25, 2019 · 0 comments

Comments

@xchunzhao
Copy link
Owner

前言

模块化的开发方式可以更高效的复用代码,以及更优雅的组织代码。目前流行的模块化规范有CommonJSAMDCMDUMDES6 Module。本篇旨在系统性的对比这几种模块化规范。

CommonJS

Node是CommonJS规范的主要实践者,它有四个重要的环境变量作为模块化的实现提供支持:moduleexportsrequireglobal。使用module.exports导出模块,使用require加载模块

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


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

    • 同步加载模块。(Node端没有问题,浏览器端由于网络原因性能较差)
    • 所有代码都运行在模块作用域,不会污染全局作用域。
    • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
    • 运行时执行
  • CommonJS循环加载

如果发生模块的循环加载,即A加载B,B又加载A,则B将加载A的不完整版本。

// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';

// b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';

// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);

上述三个文件中,a.js加载了b.js,而b.js又加载了a.js。此时返回a.js不完整的版本。执行结果如下:

b.js  a1
a.js  b2
main.js  a2
main.js  b2

如果此时将main.js修改如下:

// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);

此时的执行结果是:

b.js  a1
a.js  b2
main.js  a2
main.js  b2
main.js  a2
main.js  b2

有上述执行结果的原因为由于CommonJS在多次加载的时候,会直接从缓存读取exports属性,所以a.jsb.js内部的console不会执行。

AMD

AMD规范主要实现为require.js。这边介绍使用require.js实现AMD的模块化:用require.config()指定引用路径,用define()定义模块,用require加载模块。

  • AMD示例
// 独立模块。定义math.js模块
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});

// 非独立模块。定义tool.js模块
define(['m1','m2'], function(m1,m2){
    return {
        method: function() {
            m1.methodA();
			m2.methodB();
        }
    };
});

// 引用模块,将模块放在[]内
require(['math'],function(math){
  var sum = math.add(10,20);
});

  • AMD特点

    • 异步加载模块。模块的加载不会影响后面语句的执行。(加载完成,会执行回调函数)
    • 编译时执行
    • 依赖前置,define的时候就引入
  • require.js包裹CommonJS

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();
  anotherModule.doMoarAwesome();

  exports.asplode = function (){
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
  };
});

CMD

CMD全称Common Module Definition,由国内玉伯大神在开发SeaJS时提出。

  • CMD示例:
//m1.js
define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  var b = require('./b');
  b.doSomething();
})

seajs.use(['m1'],function(m1){
    
})
  • CMD特点
    • 对于依赖的模块,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
    • CMD 推崇依赖就近
    • CMD 严格的区分推崇职责单一,其每个 API 都简单纯粹。例如:AMD 里 require 分全局的和局部的。CMD 里面没有全局的 require,提供 seajs.use() 来实现模块系统的加载启动。

UMD

UMD全称Universal Module Definition,希望提供一个前后端跨平台的解决方案(支持AMD与CommonJS模块方式)。

  • UMD示例
// 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 {};
}));
  • 实现原理
    • 先判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式(CommonJS)
    • 再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
    • 前两个都不存在,则将模块公开到全局(windowglobal)

ES6 Module

使用export导出模块,import导入模块。

  • ES6 Module示例
// m1.js
export {
    a: 1
}
// m2.js
const b = 2;
export default b;

// main.js
import { a } from 'm1';
import b from 'm2';

import('./m1.js').then();
  • ES6 Module特点
    • 编译时加载,又叫静态加载。TreeShaking正是利用此特点实现。当然import()也支持动态加载,返回一个Promise
    • 输出结果是值的引用

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant