【查缺补漏】:JS模块化| 8月更文挑战

227 阅读4分钟

模块化概述

很长一段时间,JavaScript存在模块化思想,比如早期流行的命名空间的开发思想,但是JavaScript一直没有 模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。ES6发布之后,在语言标准层面上实现了模块化功能——ES6 Module。它完全可以取代CJS和AMD规范,成为浏览器和服务器端通用的模块解决方案。

CommonJS

  • 一个文件一个模块,如果有多个导出,则取最后一个导出
  • 使用module.exports ={}或者exports.xx = ...暴露模块
  • 使用require()方法引入模块
  • require()是同步执行的
  • 只可以在NodeJS环境使用,不适用于浏览器

示例

// a.js
let fn = function(msg){
  console.log(msg);
}
exports.printMsg = fn;
 
//b.js
const a=require('./a.js');
a.printMsg('hello CommonJS'); // hello CommonJS

AMD(异步模块定义)

  • 使用 define(...) 定义一个模块
  • 使用require(...) 加载一个模块
  • 依赖前置,提前执行
  • RequireJS 是AMD 的一种实现

示例

// 模块的定义
/**
*@param id 模块名称,如果为空,模块的名字默认为模块加载器请求的制定脚本名
*@param dependencies 模块依赖
*@param factory 工厂函数,模块初始化执行函数或对象
*/
define(id,dependencies,factory)
 
// 模块的使用,使用 require 加载模块
require([module],callback);

CMD(通用模块定义)

  • 一个文件为一个模块
  • 使用 define(...) 定义一个模块 (和AMD相似)
  • 使用require(...) 加载一个模块(和AMD 相似)
  • SeaJS 是CMD 的一种实现

示例

define(function(require,exports,module){
  var a=require('./a');
  a.sayHello();
  var b=require('./b');// 依赖就近书写;
  b.sayHello();
  //...
});

UMD(万能模块定义)

  • 可以在服务端使用,也可以在浏览器端使用 他主要做了三件事情:
  1. 判断是否支持AMD
  2. 判断是否支持CJS
  3. 如果都不支持,使用全局变量

他的主要代码如下:

(function (root, factory) {
    // 对应上述的三个步骤
    if (typeof define === 'function' && define.amd) {
        // 1.判断是否支持 AMD
        // 如果 define 这个方法是被定义 并且 define 这个方法是 AMD 的规范,那就把 factory 这个模块实体用 define 方法以 AMD 的规范 定义
        define([], factory); // [] 是依赖,factory 是模块实体
    } else if (typeof exports === 'object') {
        // 2. 判断是否支持 CommonJS
        // 如果 exports 是等于一个对象,则表明是在 Node 环境中运行,则支持 CommonJS,那就用 module.exports 暴露整个模块实体
        module.exports = factory();
    } else {
        // 3. 如果都不支持,使用全局变量
        // Browser globals (root 即是 window)
        root.returnExports = factory();
  }
}(this, function () {
    // Module Defination
    var sum = function(x, y){
        return x + y;
    }
    var sub = function(x, y){
        return x - y;
    }
    var math = {
        findSum: function(a, b){
            return sum(a,b);
        },
        findSub: function(a, b){
            return sub(a, b);
        }
    }
    return math;
}));

ES Module (ES6 模块)

  • 使用 import 导入模块
  • 使用 export 导出模块

示例

// 导出模块
export var a='123'; //导出变量
export function fn(){}; // 导出函数
export default {name:'ann',age:18} // 导出对象; export 不能直接导出对象必须加上default;
export class Myclass{} // 导出类;

对比总结

CMD和AMD 的最显著的区别 AMD 是 提前执行,CMD 是 延迟执行,依赖就近
AMD: 执行过程中会将所有的依赖模块前置执行,也就是自己的代码逻辑开始前全部执行;
CMD:如果require 但整个逻辑未使用这个依赖 或者为执行到逻辑使用它的地方前,不会执行。 ES6 模块和Common js 模块的差异是:
1、CommonJS 输出的是一个值的拷贝;ES6 模块输出的是值的引用;
2、CommonJS 模块是运行时加载;ES6模块是编译时输出接口;

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

除了静态加载带来的各种好处,ES6 模块还有以下好处。

  • 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
  • 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
  • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。