JavaScript 模块化详解

前端异步编程规范

一. 模块化的理解

在JavaScript发展初期就是为了实现简单的页面交互逻辑,而如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,此时在JS方面就会考虑使用模块化规范去管理。模块化已经发展了有十余年了,不同的工具和轮子层出不穷,但总结起来,它们解决的问题主要有三个:

  • 外部模块的管理;
  • 内部模块的组织;
  • 模块源码到目标代码的编译和转换;

以下为各个工具或者框架的诞生时间,先了解下时间节奏,方便对内容有所了解:

生态 诞生时间
Node.js 2009年
NPM 2010年
requireJS(AMD) 2010年
seaJS(CMD) 2011年
broswerify 2011年
webpack 2012年
grunt 2012年
gulp 2013年
react 2013年
vue 2014年
redux 2015年
angular 2016年
vite 2020年
snowpack 2020年

1. 什么是模块

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起;
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;

2. 模块化的进化过程

2.1 全局 function 模式

将不同的功能封装成不同的全局函数。

js 复制代码
function m1(){
  //...
}
function m2(){
  //...
}

问题:污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系。

2.2 namespace 模式

简单的对象封装。

  • 作用:减少了全局变量,解决命名冲突;
  • 问题:数据不安全(外部可以直接修改模块内部的数据);
js 复制代码
let myModule = {
  data: 'www.baidu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

myModule.data = 'other data' // 能直接修改模块内部的数据
myModule.foo() // foo() other data

这样的写法会暴露所有模块成员,内部状态可以被外部改写。

2.3 IIFE 模式

匿名函数自调用(闭包)。

  • 作用:数据是私有的, 外部只能通过暴露的方法操作;
  • 编码:将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口;
  • 弊端:1. 依赖顺序必须要正确; 2. 代码不易维护。

module.js文件:

js 复制代码
(function (window) {
    // 内部私有的数据
    let data = 'www.chenghuai.com'

    // 操作数据的函数:
    
    // 用于暴露的函数
    function foo() {
        console.log(`foo() ${data}`)
    }
    
    // 用于暴露的函数
    function bar() {
        console.log(`bar() ${data}`)
        // 内部调用
        otherFun() 
    }

    // 内部私有的函数
    function otherFun() {
        console.log('otherFun()')
    }

    //暴露行为:
    window.myModule = {foo, bar} //ES6写法
})(window)

index.html文件:

js 复制代码
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
    myModule.foo() // foo() www.chenghuai.com
    myModule.bar() // bar() www.chenghuai.com
    console.log(myModule.data) //undefined 不能访问模块内部数据
    myModule.data = 'xxxx' // 不能修改的模块内部的data
    myModule.foo() // 没有改变,依然是 foo() www.chenghuai.com
</script>

2.4 IIFE 模式增强

当使用第三方插件库时,如jQuery,用法如下:

module.js文件:

js 复制代码
(function(window, $) {
  // 内部私有的数据
  let data = 'www.baidu.com'

  // 用于暴露的函数
  function foo() {
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  
  function bar() {
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  
  // 内部私有的函数
  function otherFun() {
    console.log('otherFun()')
  }
  
  // 暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)

index.html文件:

js 复制代码
<!-- 引入的js必须有一定顺序 -->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
  myModule.foo()
</script>

上面的例子通过jquery方法将页面的背景颜色改成红色,所以必须先引入jQuery库,就把这个库当作参数传入。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

3. 模块化的好处

  • 避免命名冲突(减少命名空间污染);
  • 更好的分离, 按需加载;
  • 更高复用性;
  • 高可维护性;

4. 引入多个<script>后出现的问题

  • 引入顺序:一旦引入顺序出现问题,就可能报错;

  • 请求过多:首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多;

  • 依赖模糊:我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错;

  • 难以维护:以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。

模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,因此才有了后续的commonjs, AMD, ESM, CMD规范。

二. 模块化的规范

1. CommonJS

1.1 概念

Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

总的来说,就是让 JS 能够在浏览器之外运行的模块定义(同步执行在服务器端)。

1.2 特点

  • 所有代码都运行在模块作用域,不会污染全局作用域;
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存;
  • 模块加载的顺序,按照其在代码中出现的顺序;

1.3 基本语法

  • 暴露模块:module.exports = {}exports.key = value
  • 引入模块:require('xxx'),如果是第三方模块,xxx为模块名 ;如果是自定义模块,xxx为模块文件路径

此处我们有个疑问:CommonJS暴露的模块到底是什么? CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

如下面的example.js,通过module.exports输出变量x和函数addX:

js 复制代码
let x = 5;
let addX = function (value) {
  return value + x;
};
// 暴露模块
module.exports.x = x;
module.exports.addX = addX;

require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

如下面的app.js,通过require引入example.js并使用:

js 复制代码
// 引入自定义模块
let example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1));  // 6

通过node运行app.js得到结果:

1.4 模块的加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝(导出的值导出了,就不会修改我们原本的值了)。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与ES6模块化有重大差异(下文会介绍),请看下面这个例子:

lib.js:

js 复制代码
let counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter,
  incCounter
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。

app.js:

js 复制代码
let counter = require('./lib').counter;
let incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3

上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到counter了。这是因为counter是一个原始类型的值,原始类型会被缓存,除非写成一个函数,才能得到内部变动后的值。

1.5 服务器端实现

  1. 安装node
  2. npm init:项目初始化
js 复制代码
|-modules
  |-module1.js
  |-module2.js
  |-module3.js
|-app.js
|-package.json
  {
    "name": "commonJS-node",
    "version": "1.0.0"
  }
  1. 下载第三方模块
js 复制代码
npm install uniq --save 

uniq用于数组去重

  1. 定义模块代码

module1.js:

js 复制代码
module.exports = {
  msg: 'module1',
  foo() {
    console.log(this.msg)
  }
}

module2.js:

js 复制代码
// 暴露一个方法
module.exports = function() {
  console.log('module2')
}

module3.js:

js 复制代码
// 暴露一个方法 和 数组
exports.foo = function() {
  console.log('foo() module3')
}

exports.arr = [1, 2, 3, 3, 2]

app.js文件:

js 复制代码
// 引入第三方库,第三方库放置在最前面
let uniq = require('uniq')

// 引入自定义模块module1,module2,module3
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')

// 调用模块中的函数
module1.foo() 
module2() 
module3.foo() 
console.log(uniq(module3.arr)) 
  1. node app.js 得到结果:
js 复制代码
module1
module2
foo() module3
[1, 2, 3]

1.6 浏览器端实现

使用Browserify:Browserify 会对代码进行解析,整理出代码中的所有模块依赖关系,然后把相关的模块代码都打包在一起,形成一个完整的JS文件,这个文件中不会存在 require 这类的模块化语法,变成可以在浏览器中运行的普通JS。

我们来看一下Browserify的用法:

  1. npm init:创建项目结构
js 复制代码
|-js
  |-dist // 打包生成文件的目录
  |-src // 源码所在的目录
    |-module1.js
    |-module2.js
    |-module3.js
    |-app.js // 应用主源文件
|-index.html // 运行于浏览器上的页面
|-package.json
  {
    "name": "browserify-test",
    "version": "1.0.0"
  }
  1. 下载browserify:
  • 全局: npm install browserify -g
  • 局部: npm install browserify --save-dev
  1. 定义模块代码 注意:index.html文件要运行在浏览器上,需要借助browserify将app.js文件打包编译,然后将打包文件引入在index.html上。如果直接在index.html引入app.js就会报错!

  2. 打包处理js 根目录下运行

js 复制代码
browserify js/src/app.js -o js/dist/bundle.js
  1. 页面中引入使用 在index.html文件中引入编译后文件bundle.js:
js 复制代码
<script type="text/javascript" src="js/dist/bundle.js"></script>
  1. 在浏览器中运行index.html,即可在控制台看见结果:
js 复制代码
module1
module2
foo() module3
[1, 2, 3]

2. AMD (Asynchronous Module Definition)

2.1 概念

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要早。

总的来说就是:

  • CommonJS:是同步的,执行在服务器端;
  • AMD:是异步加载的,执行在浏览器端;

2.2 基本语法

定义暴露模块:

js 复制代码
// 定义没有依赖的模块
define(function() {
   return 模块
})

// 定义有依赖的模块
define(['module1', 'module2'], function(m1, m2) {
   return 模块
})

引入使用模块:

js 复制代码
require(['module1', 'module2'], function(m1, m2) {
   // 使用m1/m2
})

2.3 AMD 实现

通过比较是否使用AMD,来说明AMD实际使用的效果。

2.3.1 未使用 AMD 规范

首先定义 dataService.js文件,暴露 getMsg 方法:

js 复制代码
(function (window) {
  let msg = 'www.hll.com'
  
  function getMsg() {
    return msg.toUpperCase()
  }
  
  window.dataService = { getMsg }
})(window)

然后定义 alerter.js文件,引入dataService,暴露 alerter 方法:

js 复制代码
(function (window, dataService) {
  let name = 'hll'
  
  function showMsg() {
    // 需要使用 dataService 中的 getMsg 方法
    alert(dataService.getMsg() + ', ' + name)
  }
  
  window.alerter = {showMsg}
})(window, dataService)

定义 main.js文件,引入alerter,并调用 alerter 的showMsg方法:

js 复制代码
(function (alerter) {
  alerter.showMsg()
})(alerter)

最后,在index.html文件中按顺序依次引入js文件:

js 复制代码
<div><h1>Modular Demo 1: 未使用AMD(require.js)</h1></div>
<script type="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
<script type="text/javascript" src="js/main.js"></script>

最后得到如下结果:

js 复制代码
'WWW.HLL.COM', 'hll'

这种方式缺点很明显:首先由于引入多个js文件所以会发送多个请求,其次引入的js文件顺序不能搞错,否则会报错。

2.3.2 使用 require.js

RequireJS 是一个工具库,主要用于客户端的模块管理。

RequireJS 的模块管理遵守AMD 规范,它的基本思想是:通过define方法,将代码定义为模块;再通过require方法,实现代码的模块加载。

接下来介绍AMD规范在浏览器实现的步骤:

  1. 下载require.js
  1. npm init:创建项目结构
js 复制代码
|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html
  1. 定义 dataService.js 文件
js 复制代码
// 定义没有依赖的模块
define(function() {
  let msg = 'www.hl.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  
  // 暴露模块
  return { getMsg } 
})
  1. 定义 alerter.js 文件
js 复制代码
// 定义有依赖的模块
define(['dataService'], function(dataService) {
  let name = 'hl'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  
  // 暴露模块
  return { showMsg }
})
  1. 定义 main.js 文件
js 复制代码
(function() {
  // 路径配置
  require.config({
    // 基本路径 出发点在根目录下
    baseUrl: 'js/', 
    // 映射: 
    paths: {
      // 模块标识名: 路径
      alerter: './modules/alerter', // 此处不能带.js后缀,若写成alerter.js, 会报错
      dataService: './modules/dataService'
    }
  })
  
  // 引入alerter
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
  1. 在 index.html 中引入
js 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>Modular Demo</title>
  </head>
  <body>
    <!-- 引入require.js并指定js主文件的入口 -->
    <script data-main="js/main" src="js/libs/require.js"></script>
  </body>
</html>
  1. 最后得到如下结果:
js 复制代码
'WWW.HLL.COM', 'hll'

如果我们想引入第三方库,该怎样实现呢? 假如我们在 alerter.js 文件中引入jQuery第三方库。 alerter.js文件:

js 复制代码
define(['dataService', 'jquery'], function(dataService, $) {
  let name = 'hl
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  $('body').css('background', 'green')
  
  // 暴露模块
  return { showMsg }
})

main.js 文件也要有相应的路径配置:

js 复制代码
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      // 自定义模块
      alerter: './modules/alerter', 
      dataService: './modules/dataService',
      // 第三方库模块
      jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
    }
  })
  // 引入 alerter
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
2.3.3 总结

通过两者的比较,可以得出 AMD 模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许异步加载模块,也可以根据需要动态加载模块。

3. CMD(Common Module Definition)

3.1 概念

CMD 规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJSAMD 规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循CMD模块定义规范。

总的来说,CMD 纯粹为浏览器所服务,具有异步加载功能,common.js + AMD -> sea.js

3.2 基本语法

定义暴露模块:

js 复制代码
// 定义没有依赖的模块
define(function(require, exports, module) {
  //暴露模块的两种方式:
  exports.a = 'xxx'
  module.exports = {...}
})

// 定义有依赖的模块
define(function(require, exports, module) {
  // 引入依赖模块(同步)
  let module2 = require('./module2')
  // 引入依赖模块(异步)
    require.async('./module3', function (m3) {
        // m3...
    })
    
  //暴露模块
  exports.a = 'xxx'
})

引入使用模块:

js 复制代码
define(function (require) {
  // 引入
  let m1 = require('./module1')
  let m4 = require('./module4')
  // 使用
  m1.show()
  m4.show()
})

3.3 CMD 实现

  1. 下载sea.js,并导入到项目中

然后将sea.js导入项目:js/libs/sea.js

  1. 创建项目结构
js 复制代码
|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html
  1. 定义模块代码

module1.js文件:

js 复制代码
// 定义没有依赖的模块
define(function (require, exports, module) {
  //内部变量数据
  let data = 'hl.com'
  //内部函数
  function show() {
    console.log('module1 show() ' + data)
  }
  
  //向外暴露
  exports.show = show
})

module2.js文件:

js 复制代码
// 定义没有依赖的模块
define(function (require, exports, module) {
  module.exports = {
    msg: 'I am hl'
  }
})

module3.js文件:

js 复制代码
// 定义没有依赖的模块
define(function(require, exports, module) {
  // 定义一个常量并导出
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
})

module4.js文件:

js 复制代码
// 定义有依赖的模块
define(function (require, exports, module) {
  //引入依赖模块(同步)
  let module2 = require('./module2')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  // 导出
  exports.show = show
  
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
})

main.js文件:

js 复制代码
// 引入使用模块
define(function (require) {
  let m1 = require('./module1')
  let m4 = require('./module4')
  m1.show()
  m4.show()
})
  1. 在index.html中引入sea.js
js 复制代码
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>
  1. 最后得到结果如下:
js 复制代码
module1 show(), hl
module4 show() I am hl
异步引入依赖模块3 abc123

3.4 AMD 与 CMD区别

1.依赖引用:

  • AMD:依赖前置;
  • CMD:就近依赖;

2.模块导出:

  • AMD:return 返回值;
  • CMD:module.exports;
js 复制代码
// AMD
// 1.依赖前置
define(['Module1'], function (module1) {
    let result1 = module1.exec();
    // 2.使用 return 导出
    return {
      result1,
    }
});

// CMD
define(function (requie, exports, module) {
    // 1.依赖就近书写
    let module1 = require('Module1');
    let result1 = module1.exec();
    // 2.使用 module.exports 导出
    module.exports = {
      result1
    }
});

总结:从上面的代码比较中我们可以得出 AMD规范和CMD规范的区别

  1. 对依赖的处理:
  • AMD推崇依赖前置,即通过依赖数组的方式提前声明当前模块的依赖;
  • CMD推崇依赖就近,在编程需要用到的时候通过调用require方法动态引入;
  1. 在本模块的对外输出:
  • AMD推崇通过返回值的方式对外输出;
  • CMD推崇通过给module.exports赋值的方式对外输出;

ESM 下模块之间的依赖关系是高度确定的,与运行状态无关,编译工具只需要对 ESM 模块做静态分析,就可以从代码字面量中推断出哪些模块值未曾被其它模块使用,这是实现 Tree Shaking 技术的必要条件。

4. ES6模块化

4.1 概念

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJSAMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

4.2 基本使用

  • export命令用于规定模块的对外接口;
  • import命令用于输入其他模块提供的功能。 如下所示,我们定义一个模块 math.js:
js 复制代码
let basicNum = 0; 
let add = function (a, b) { return a + b; }; 
// 导出
export { basicNum, add }; 

在main.js中引入math.js,并使用:

js 复制代码
// 引入
import { basicNum, add } from './math';

function test() {
    console.log(add(99 + basicNum));
}
test();  // 99

注意:模块默认输出, 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

4.3 ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝ES6 模块输出的是值的引用
  • CommonJS 模块是运行时 加载,ES6 模块是编译时输出接口;

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成,而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子。

count.js文件:

js 复制代码
export let counter = 3;
export function incCounter() {
  counter ++;
}

main.js 文件:

js 复制代码
import { counter, incCounter } from './count';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

由此可见,counter的值被改变了。

ES6 模块的运行机制与 CommonJS 不一样,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

4.4 ES6 实现

下面我们使用 BabelES6 编译为 ES5 代码,并使用 Browserify 编译打包js。

  1. npm init:生成 package.json 文件
js 复制代码
 {
   "name" : "es6-babel-browserify",
   "version" : "1.0.0"
 }
  1. 安装babel-cli, babel-preset-es2015 和 browserify
js 复制代码
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
  1. 定义.babelrc文件
js 复制代码
  {
    "presets": ["es2015"]
  }
  1. 定义模块代码

module1.js文件

js 复制代码
// 分别暴露 foo 和 bar
export function foo() {
  console.log('foo() module1')
}
export function bar() {
  console.log('bar() module1')
}

module2.js文件

js 复制代码
// 统一暴露 fun1 和 fun2
function fun1() {
  console.log('fun1() module2')
}
function fun2() {
  console.log('fun2() module2')
}
export { fun1, fun2 }

module3.js文件:

js 复制代码
// 默认暴露:
// export default 可以暴露任意数据类项,暴露的是什么数据,接收到就是什么数据

export default () => {
  console.log('默认暴露')
}

app.js文件:

js 复制代码
// 引入
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
// 使用
foo()
bar()
fun1()
fun2()
module3()
  1. 编译
  • 使用 Babel 将 ES6 编译为 ES5 代码(但包含CommonJS语法) : babel js/src -d js/lib
  • 使用 Browserify 编译 js : browserify js/lib/app.js -o js/lib/bundle.js
  1. 在 index.html 文件中引入
js 复制代码
 <script type="text/javascript" src="js/lib/bundle.js"></script> 
  1. 最后得到如下结果
js 复制代码
foo() module1
bar() module1
fun1() module2
fun2() module2
默认暴露
  1. 若需要引入第三方库

首先,要安装依赖:

js 复制代码
npm install jquery@1 --save

然后,在 app.js 文件中引入:

js 复制代码
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
// 引入第三方库
import $ from 'jquery'

foo()
bar()
fun1()
fun2()
module3()
// 使用第三方库
$('body').css('background', 'green')

5. UMD(Universal Module Definition)

UMD 是一种javascript通用模块定义规范,让你的模块能在javascript所有运行环境中发挥作用。

意味着要同时满足CommonJS, AMD, CMD的标准,适配所有js运行环境,以下为实现:

js 复制代码
(function(root, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        console.log('是commonjs模块规范,nodejs环境')
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        console.log('是AMD模块规范,如require.js')
        define(factory)
    } else if (typeof define === 'function' && define.cmd) {
        console.log('是CMD模块规范,如sea.js')
        define(function(require, exports, module) {
            module.exports = factory()
        })
    } else {
        console.log('没有模块环境,直接挂载在全局对象上')
        root.umdModule = factory();
    }
}(this, function() {
    return {
        name: '我是一个umd模块'
    }
}))

三. 总结

  1. CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMDCMD解决方案;
  2. AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块,不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;
  3. CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行;
  4. ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS, AMDCMD规范,成为浏览器和服务器通用的模块解决方案;
  5. UMD为同时满足CommonJS, AMD, CMD标准而实现的聚合,判断条件就是UMD的实现,为了兼容不同的环境。
相关推荐
勿语&15 分钟前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
Jiaberrr6 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
安冬的码畜日常8 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ8 小时前
html+css+js实现step进度条效果
javascript·css·html
john_hjy9 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd9 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome