让知识连接你我
投稿赚钱
当前位置: 首页 > 前端开发 > Js模块化方案总结?什么是 CommonJS、AMD、CMD、UMD、ES Module?
  • 101
  • 微信分享

    扫一扫,在手机上查看

Js模块化方案总结?什么是 CommonJS、AMD、CMD、UMD、ES Module?

2019.09.09 10:00 263 浏览 举报

  这篇文章包含两部分,首先部分依据简明的描述详细介绍什么叫 CommonJS、AMD、CMD、UMD、ES Module 还有它们的常用用法,第二部分则依据具体现象指明在正常的 webpack 构建流程中该怎样规定打包配置中的工程化参数。

  JavaScript 工程化方案

  工程化这种话题在 ES6 以前是不会有的,因此这也被诟病为初期 JavaScript 开发全局性污染 和依靠管理状况现象的根源。这种历史背景和发展趋势概述在这篇文章将不易提及,因此有兴趣可以自动搜索 JavaScript 发展历程通过了解。直接进入正题,让我们讨论一下常用的工程化方案常有什么还有他们常有什么具体内容。

  1. CommonJS

  CommonJS 的一两个组件就是一个脚本制作文档,依据实施该文档来载入组件。CommonJS 标准规定,每一个组件内部,module 函数代表目前组件。这种函数是一两个目标,它的 exports 特性(即module.exports )是对内的接口。载入某个组件,虽然是载入该组件的 module.exports 特性。

  让我们见过这种的组件引入:

  var myModule = require('module');
  myModule.sayHello();

  这是由于让我们把组件的形式定义在了组件的特性上:

  // module.js
  module.exports.sayHello = function() {
      console.log('Hello ');
  };
  // 如果这样写
      module.exports = sayHello;
  // 调用则需要改为
      var sayHello = require('module');
      sayHello();

  require 命令头一次载入该脚本制作时就会实施整个脚本制作,随后在内存中形成一两个目标(组件可以反复载入,不过在头一次载入时才会运行,最后被缓存),这种最后长成这种:

  {
      id: '...',
      exports: { ... },
      loaded: true,
      ...
  }

  Node.js 的组件机制保持就是遵循了 CommonJS 的标准。

  不过 Node.js 另外做了某件事,即为每一个组件提供了一两个 exports 函数,以偏向 module.exports,这相等于在每一个组件最初,写有如此一行代码:

  var exports = module.exports;

  CommonJS 组件的特点:

  各种代码都运行在组件作用域,不易污染全局性作用域。

  独立性是组件的重要特点就,组件内部较好不与程序的另外部分直接交互。

  组件可以反复载入,不过只会在头一次载入时运行一次,随后运行最后就被缓存了,以后再载入,就直接读取缓存最后。

  要想让组件之后运行,务必清除缓存。

  组件载入的顺序,遵循其在代码中出现的顺序。

  2. AMD

  CommonJS 标准很好,不过不适用以浏览器环境,因此得到 AMD 和 CMD 两种方案。AMD 全称 Asynchronous Module Definition,即异步组件定义。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。除了和 CommonJS 同步加载方式不同之外,AMD 在模块的定义与引用上也有所不同。

  define(id?, dependencies?, factory);

  AMD 的模块引入由 define 方法来定义,在 define API 中:

  id:模块名称,或者模块加载器请求的指定脚本的名字;

  dependencies:是个定义中模块所依赖模块的数组,默认为 [“require”, “exports”, “module”],举个例子比较好理解,当我们创建一个名为 “alpha” 的模块,使用了require,exports,和名为 “beta” 的模块,需要如下书写(示例1);

  factory:为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值;

  // 示例1
  define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
      exports.verb = function() {
      return beta.verb();
  // 或者
      return require("beta").verb();
      }
  });

  如果模块定义不存在依赖,那么可以直接定义对象:

  define({
      add: function(x, y){
          return x + y;
      }
  });

  而使用时我们依旧通过 require 关键字,它包含两个参数,第一个数组为要加载的模块,第二个参数为回调函数:

  require([module], callback);

  举个例子:

  require(['math'], function (math) {
      math.add(2, 3);
  });

  3. CMD

  CMD 全称为 Common Module Definition,是 Sea.js 所推广的一个模块化方案的输出。在 CMD define 的入参中,虽然也支持包含 id, deps 以及 factory 三个参数的形式,但推荐的是接受 factory 一个入参,然后在入参执行时,填入三个参数 require、exports 和 module:

  define(function(require, exports, module) {
      var a = require('./a');
      a.doSomething();
      var b = require('./b');
      b.doSomething();
      ...
  })

  通过执行该构造方法,可以得到模块向外提供的接口。在与 AMD 比较上存在两个主要的不同点:

  对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

  CMD 推崇依赖就近,AMD 推崇依赖前置。

  如果说的不清楚,那么我们直接看上面的代码用 AMD 该怎么写:

  define(['./a', './b'], function(a, b) {
      a.doSomething();
      b.doSomething();
      ...
  })

  4. UMD

  UMD,全称 Universal Module Definition,即通用模块规范。既然 CommonJs 和 AMD 风格一样流行,那么需要一个可以统一浏览器端以及非浏览器端的模块化方案的规范。

  直接来看看官方给出的 jQuery 模块如何用 UMD 定义的代码:

  (function (factory) {
      if (typeof define === 'function' && define.amd) {
      // AMD. Register as an anonymous module.
      define(['jquery'], factory);
  } else if (typeof module === 'object' && module.exports) {
      // Node/CommonJS
  module.exports = function( root, jQuery ) {
      if ( jQuery === undefined ) {
      // require('jQuery') returns a factory that requires window to
      // build a jQuery instance, we normalize how we use modules
      // that require this pattern but the window provided is a noop
      // if it's defined (how jquery works)
  if ( typeof window !== 'undefined' ) {
      jQuery = require('jquery');
  }
  else {
      jQuery = require('jquery')(root);
      }
  }
      factory(jQuery);
      return jQuery;
      };
  } else {
  // Browser globals
      factory(jQuery);
      }
  }(function ($) {
      $.fn.jqueryPlugin = function () { return true; };
     }));

  5. ES Modules

  当然,以上说的种种都是社区提供的方案,历史上,JavaScript 一直没有模块系统,直到 ES6 在语言标准的层面上,实现了它。其设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。而 ES Modules 不是对象,而是通过export 命令显式指定输出的代码。

  ES Modules 的模块化能力由export 和import 组成,export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。我们可以这样定义一个模块:

  // 第一种方式
      export var firstName = 'Michael';
      export var lastName = 'Jackson';
      export var year = 1958;
  // 第二种方式
      var firstName = 'Michael';
      var lastName = 'Jackson';
      var year = 1958;
      export { firstName, lastName, year };

  然后再这样引入他们:

  import { firstName, lastName, year } from 'module';
  import { firstName as newName } from 'module';
  import * as moduleA from 'module';

  除以上两种命令外,还有一个export default 命令用于指定模块的默认输出(一个模块只能有一个默认输出)。如果使用了export default 语法,在 import 时则可以任

意命名。由于export default 命令的本质是将后面的值,赋给default 变量,所以也可以直接将一个值写在export default 之后。当然,引用方式也存在多种:
  import { default as foo } from 'module';
  import foo from 'module';

  需要注意的是 Modules 会自动采用严格模式,且 import 命令具有提升效果,会提升到整个模块的头部,首先执行。

  webpack 打包输出配

  说完理论,来看看实际项目中遇到的问题。当我们开发完一个 JavaScript 模块必然要经历打包的流程,而在 webpack 配置中,通过指定 output 选项就可以告诉 webpack 如何输出 bundle, asset 以及其他载入的内容。那么如何实现不同环境可兼容的构建呢?

  import
  this
  window
  require

  output 中有一个属性叫做libraryTarget ,被用来指定如何暴露你的模块的属性。你可以这样尝试赋值给一个变量或者指定对象的属性:

  // 加载完成后将模块赋值给一个指定变量(默认值)
  {
      libraryTarget: 'var',
      ...
  }
      // 赋值为指定对象的一个属性,比如 `this` 或者 `window`
  {
      libraryTarget: "this",
      // libraryTarget: "window",
      ...
  }
      // 同样的,若是指定 commonjs,那么便可以将模块分配给 exports,这也意味着可以用于 CommonJS 环境:
  {
      libraryTarget: "commonjs",
  
    }

  如果需要更完整的模块化 bundle,以确保和各模块系统兼容,那么可以这样尝试:

  // 内容分配给 module.exports 对象,用于 CommonJS 环境
  {
      libraryTarget: 'commonjs2',
      ...
  }
      // 暴露为 AMD 模块,通过特定属性引入
  {
      libraryTarget: 'amd',
      ...
  }
      // 所有模块系统兼容的万金油,可以在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量
  {
      libraryTarget: 'umd',
      ...
  }

  因此,如果只看 output 内容,那么我的一个 webpack 生产环境配置可以写成这样:

  module.exports = {
      output: {
      // webpack 如何输出结果的相关选项
      path: path.resolve(__dirname, "dist"),
      filename: 'index.js',
      library: 'hijiangtao',
      umdNamedDefine: true,
      libraryTarget: 'umd',
      },
  }


本文首次发布于开创者素材 ,转载请注明出处,谢谢合作!

相关文章推荐