JavaScript - 关于 CommonJs AMD CMD 的简单使用.

模块无非就是把一些通用的定义成文件.
起到复用,结构层次清晰,后期好维护等好处.

不管是什么 CommonJS , AMD 还是 CMD.
最主要的就是要先摸清三个事情.

  • 如何定义模块?
  • 如何使用模块?
  • 这些模块的代码是在哪里跑的?

CommonJS

Node.js 实现了 CommonJS 的模块化规范.(跑在后台服务器的)

CommonJs 提供了四个环境变量,为模块化提供了支持.(模块是怎么定义以及怎么使用的)

  • module
  • expors
  • require
  • global

实际使用时

  • 使用 require 来导入你需要使用到的模块.
  • 使用 exports.xxx 或者 moudle.exports 带导出当前模块输出的接口.

一般推荐使用 module.export = {} 的方式.而非 exports.xxx


// 定义 math.js 模块.

function plus(num1,num2) {
    return num1 + num2
}

// exports.plus = plus 不推荐这种写法.

module.exports = {
    plus
}

// 使用 math.js 模块
// main.js

const math = require('./math')
math.plus(1,2)

CommonJs 使用同步的方式加载模块.
模块执行的环境在本地的服务器的Node环境中.

一切都是这么的和谐自然.

一般后端开发语言都包含模块.

比如:

  • java 的 import
  • c# 的 using
  • Node 的 require

AMD & RequireJS

AMD 全称是 Asynchronous Module Definition.

AMD 模块是跑在浏览器环境中的.(跑在前端浏览器中的)

AMD 规范采用了异步的方式加载模块,模块的加载不影响它后续的语句执行.
所以依赖这个模块的语句,都定义在后面的那个回调函数中.
并以参数的形式提供.
等到所有依赖的模块都加载完毕之后,这个回调函数才会执行.

它是浏览器端的一种模块化异步加载的规范.

requireJS 实现了 AMD 的这套标准.

使用 requireJS 提供的几个简单的 API 接口,就可以让我们实现前端模块化的开发.(模块怎么定义以及怎么使用的.)

  • 使用 requireJS 提供的 define() 来定义模块.
  • 使用 requireJS 提供的 require() 来使用模块.

当然,这里的前端化组件开发,肯定就是不之间的那种多个 <script></script> 按顺序导入的那种情况了.
AMD 实际会根据 define() 或者 require() 中依赖的模块内容,异步的加载对应的js文件.

step 1

首先要去 requireJS 官网下载 requirejS 文件.

step 2

新建一个 index.html,并导入我们下载好的这个 requireJS .

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>AMD-Asynchronous Module Definition</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
<!-- 导入 require.js -->
<script src="./js/require.js" async="true" defer data-main="./js/main.js"></script>

其中

  • async='true' 是 script 标签提供的功能,可以异步的下载js文件,不会导致浏览器阻塞.
  • defer 是因为 IE 浏览器目前不支持 async.
  • data-main 是给 requirejs 指定入口的 js 文件.(就和 webpack 打包工具指定 entry 一样)

这里的

<script src="./js/require.js" async="true" defer data-main="./js/main.js"></script>

做了两件事情:

  • 导入 require.js 文件.
  • main.js 做为整个前端模块的入口.

step 3

定义一个 main.js 需要使用到的模块.

既然这里使用到的是 requirejs 提供的前端化模块方案.
那么肯定就不能像没事人那样写以前的那种js代码文件了.

requireJs 提供了一个全局的 define(name?,[dependencies]?,factory)

用来定义一个 AMD 的模块.

其中:

  • name : 模块的名字.(一般很少用到这个参数)
  • dependencies : 当前模块依赖的模块.数据类型
  • factory: 一个工厂函数,用于返回当前模块的API.

代码格式

define(['moduleA','moduleB','moduleC'],function(moduleA,moduleB,moduleC) {
    // 等待 moduleA , moduleB, moduleC 都加载完毕之后,会进入到这个回调函数.
    
    moduleA.someFn()
    moduleB.someFn()
    moduleC.someFn()
    
    // 这个对象就是当前定义模块返回的API.内容.
    return {
        a: moduleA.xxx,
        b: moduleB.xxx,
        c: moduleC.xxx
    }
})

step 4.编造一个场景,使用rquirejs提供的define()来定义一个模块

regex.js 一个提供正则表达式验证的AMD模块.

define(function () { 
  const phoneRegex = /^(13|14|15|17|18|19)[0-9]{9}$/, // 手机号码(国内)
        emailRegex = /^\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}$/ // 邮箱
  
  function isPhoneNum (phone) { 
    return phoneRegex.test(phone)
  }

  function isEmail (email) {
    return emailRegex.test(email)
  }

  // 此模块返回两个函数
  return {
    isPhoneNum,
    isEmail
  }
})

  • 这里的 regex.js 是一个很简单的正则验证模块.没有依赖其他的模块功能.所以使用了 define(factory) 来定义.
  • 模块最终返回了一个对象,里面包括了两个函数(isPhoneNum,isEmail)

step 5. 使用RequireJS提供的require()来引用regex模块

main.js (入口模块) 使用 regex.js 模块文件.

既然这里使用到的是 requirejs 提供的前端化模块方案.
引用模块需要使用到 requirejs 提供的 require 接口。

RequireJS 提供了一个全局的 require([dependencies],factory)

用来引用一个 AMD 模块(define()定义的)

其中

  • dependencies : 当前require依赖的js模块. 是一个数组.
  • factory : 当所有模块都加载完成之后.会触发这个回调函数.函数形参是按照模块的导入顺序赋值的.

代码格式

require(['moduleA','moduleB','moduleC'],function(){
    moduleA.someFn()
    moduleB.someFn()
    moduleC.someFn()
})

index.html 导入的

<script src="./js/lib/require.js" async="true" defer data-main="./js/main.js"></script>

main.js 文件中.


require(['./module/regex'], function (regex) { 
  document.getElementById('phone').onblur = function (e) { 
    const value = e.target.value
    let result = regex.isPhoneNum(value) ? '手机号码正确' : '手机号码错误'
    console.log(result)
  }

  document.getElementById('email').onblur = (event) => {
    const value = event.target.value
    let result = regex.isEmail(value) ? '邮箱账号正确' : '邮箱账号错误'
    console.log(result)
  }
})

最后测试结果:

image.png

AMD总结:

  • 需要在页面中首先加载 requirejS 文件. 并设置入口模块.(main.js)
<script src="./js/lib/require.js" async="true" defer data-main="./js/main.js"></script>
  • 定义AMD模块使用 RequireJS 提供的 define(name?,[dependencies]?,factory) 方法定义模块.
// AMD 在定义模块时,提倡依赖前置
define(['moduleA','moduleB'],function(moduleA,moduleB){

    // some code here with moduleA...moduleB
})
  • 导入 AMD 模块使用 RequreJS 提供的 require([dependencies],factory) 方法导入并使用模块.
AMD 在使用模块时,提倡依赖前置
require(['moduleA','moduleB'],function(moduleA,moduleB){
    // some code here with moduleA...moduleB
})

CMD & sea.js

CMD 是 Common Module Definition 的缩写.

它和 AMD 一样,也是用于前端浏览器模块化开发的一个标准.(跑在前端浏览器中的)

国内大神根据此标准创建了 sea.js .

为什么有了AMD之后,还要有一个CMD呢?

AMD & require.js 提倡的是依赖前置.

define(['a','b','c'],function(a,b,c){
    
})

而 CMD 和 AMD 不同之处在于:

CMD 提倡依赖后置,或者说懒加载依赖.只有在使用到某些框架的时候,才去加载依赖.

define(function(require,exports,module){
    const moduleA = require('a')
    if (moduleA.test()) {
        // 只有在需要这个模块的时候,才去加载.
        const moduleB = require('b')
        //.....
    }
})

实现了 CMD 标准的 sea.js 也提供了定义模块使用模块的方法.(模块怎么定义以及怎么使用的)

  • CMD 定义模块使用
// math.js
define(function(require,exports,module){
    function add (a, b) {
      return a + b
    }

  module.exports = {
    add
  }
})
  • CMD 导入模块使用
// main.js
seajs.use(['./math.js'],function(math){
    math.add(1,2)
})

CMD&sea.js 的基本使用步骤.

step 1.需要下载sea.js源代码

<script src="./sea.3.0.3.js"></script>

和 require.js 不同的是, sea.js 不是指定所谓 data-main 入口js文件.
sea.js 提供一个全局的 seajs.use([dependencies],callback) 接口,用来使用定义的 CMD 模块.

step 2.

创建一个 regex.js 的正则表达式验证模块.

//regex.js

define(function (require, exports, module) {
  const phoneRegex = /^(13|14|15|17|18|19)[0-9]{9}$/, // 手机号码(国内)
    emailRegex = /^\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}$/ // 邮箱

  function isPhoneNum(phone) {
    return phoneRegex.test(18571656584)
  }

  function isEmail(email) {
    return emailRegex.test(email)
  }

// 和 commonJS很类似的导出语法.
  module.exports = {
    isPhoneNum,
    isEmail
  }
})

注意:

sea.js 提供的是一个和 commonjs 原理很类似的模块定义方法.(利用闭包函数包裹的方式)

step 3.

创建一个使用此模块的 main.js 文件

// main.js

seajs.use(['./regex.js'], function (regex) {
  window.onload = () => {
    document.getElementById('18571656584').onblur = function (e) {
      const value = e.target.value
      let result = regex.isPhoneNum(value) ? 'CMD-SEAJS-手机号码正确' : 'CMD-SEA-JS手机号码错误'
      console.log(result)
    }
    document.getElementById('email').onblur = (event) => {
      const value = event.target.value
      let result = regex.isEmail(value) ? 'CMD-SEA-JS邮箱账号正确' : 'CMD-SEA-JS邮箱账号错误'
      console.log(result)
    }
  }
})

sea.js 使用全局提供的 seajs.use() 来使用模块.

step 4.

在界面中导入这个js文件.

<script src="./main.js"></script>

step 5.

结果:

image.png

CMD模块使用步骤总结

<script src="./sea.3.0.3.js"></script>
  • 使用 seajs 提供的define()函数定义 CMD 模块.
define(function(require,exports,module){
    //..
    
    if (someCondition) {
        // CMD 在导入模块是提倡依赖后置
        const m = require('./someModule.js')
    }
    
    module.exports = {}
})
  • 利用 seajs 提供的 seajs.use([dependencies],callback) 来使用定义好的模块.
seajs.use(['a,'b'],function(a,b) {
    //....
})

总结:

  • 前端模块化指的是在浏览器执行环境中的模块化.
  • 提供前端模块化的方式有两种.
    • AMD 异步模块定义和实现了此标准的 requireJs
      • AMD 提倡依赖前置.
      • 使用 define() 来定义模块.
      • 使用 require() 来使用模块.
    • CMD 通用模块定义和实现了此标准的 seajs
      • CMD 提倡依赖后置.
      • 使用 define(function(require,exports,module){}) 来定义模块.
      • 使用全局提供的 seajs.use([dependencies],callback) 来使用模块.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343