JavaScript模块化

参考探索js的模块化

什么是JavaScript模块化?

模块化在我看来,就是把一些公共的函数封装起来给其他地方调用,而不用重复去写一些冗余的函数代码。

JavaScript模块化大致发展过程

CommonJS(服务端) => AMD (浏览器端) => CMD / UMD => ES Module

CommonJS

CommonJS主要用于服务器端。 这个规范是同步的

特点:
  • 模块可以多次加载,首次加载的结果将会被缓存,想让模块重新运行需要清除缓存。

  • 模块的加载是一项阻塞操作,也就是同步加载。

      // a.js
      module.exports = {
        moduleFunc: function() {
          return true;
        };
      }
      // 或
      exports.moduleFunc = function() {
        return true;
      };
    
      // 在 b.js 中引用
      var moduleA = require('a.js');
      // 或
      var moduleFunc = require('a.js').moduleFunc;
    
      console.log(moduleA.moduleFunc());
      console.log(moduleFunc())
    

AMD规范

在commonJS中, 模块的加载过程是一个同步的过程, 很明显地,如果在浏览器端, 肯定就会引起浏览器页面的阻塞,因此,这时候对模块异步加载的需求就出现了。从而出现AMD规范

异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)

这时候RequireJS应运而生

RequireJS

特点:
  • 前置依赖,异步加载

  • 便于管理模块之间的依赖性,有利于代码的编写和维护。

      // a.js
      define(function (require, exports, module) {
        console.log('a.js');
        exports.name = 'Jack';
      });
    
      // b.js
      define(function (require, exports, module) {
        console.log('b.js');
        exports.desc = 'Hello World';
      });
    
      // main.js
      require(['a', 'b'], function (moduleA, moduleB) {
        console.log('main.js');
        console.log(moduleA.name + ', ' + moduleB.desc);
      });
    
      // 执行顺序:
      // a.js
      // b.js
      // main.js
    

然而, 他也有他的不足之处
按照 AMD 的规范,在定义模块的时候需要把所有依赖模块都罗列一遍(前置依赖),而且在使用时还需要在 factory 中作为形参传进去。

define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){ ..... });

是不是看起来又丑又复杂。。

RequireJS 模块化的顺序是这样的:模块预加载 => 全部模块预执行 => 主逻辑中调用模块,所以实质是依赖加载完成后还会预先一一将模块执行一遍,这种方式会使得程序效率有点低。

这个时候就出现了CMD规范,典型的就是seajs模块化

SeaJS

SeaJS 模块化的顺序是这样的:模块预加载 => 主逻辑调用模块前才执行模块中的代码,通过依赖的延迟执行,很好解决了 RequireJS 被诟病的缺点。

    // a.js
    define(function (require, exports, module) {
      console.log('a.js');
      exports.name = 'Jack';
    });

    // main.js
    define(function (require, exports, module) {
      console.log('main.js');
      var moduleA = require('a');
      console.log(moduleA.name);
    });

    // 执行顺序
    // main.js
    // a.js

ES6的module

ES Module 的思想是尽量的静态化,即在编译时就确定所有模块的依赖关系,以及输入和输出的变量,和 CommonJS 和 AMD/CMD 这些标准不同的是,它们都是在运行时才能确定需要依赖哪一些模块并且执行它。ES Module 使得静态分析成为可能

  • 模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

  • 通过 export 命令定义了模块的对外接口,其他 JS 文件就可以通过 import 命令加载这个模块。

      模块的定义
      /**
       * export 只支持对象形式导出,不支持值的导出,export default 命令用于指定模块的默认输出,
       * 只支持值导出,但是只能指定一个,本质上它就是输出一个叫做default 的变量或方法
       */
      // 写法 1
      export var m = 1;
      // 写法 2
      var m = 1;
      export { m };
      // 写法 3
      var n = 1;
      export { n as m };
      // 写法 4
      var n = 1;
      export default n;
    
     模块的引入
      // 解构引入
      import { firstName, lastName, year } from 'a-module';
      // 为输入的变量重新命名
      import { lastName as surname } from 'a-module';
      // 引出模块对象(引入所有)
      import * as ModuleA from 'a-module';
    

在使用 ES Module 值得注意的是:import 和 export 命令只能在模块的顶层,在代码块中将会报错
这是因为 ES Module 需要在编译时期进行模块静态优化,import 和 export 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,这种设计有利于编译器提高效率,但也导致无法在运行时加载模块(动态加载)。

推荐阅读更多精彩内容