JS模块化初探-CommonJs、AMD、CMD和RequistJs

什么是模块化?

简单理解:将各个功能封装为独立的模块,当需要某个功能时,只需要加载相应的模块即可

为什么出现模块化?

随着技术的发展,web应用的代码日益增大,由于JavaScript的语言特性(在ES5之前js并没有像java一样拥有类和模块的语言等特性),导致其无法驾驭规模如此大的代码,因此迫切需要通过一种方式来模拟出类似于模块的功能,所以便出现了模块化

模块化的价值

  • 解决命名冲突
  • 便于依赖管理
  • 提高代码可读性
  • 代码解耦,提高复用性

没有出现模块化时,代码存在的问题

  • 命名冲突
    假如封装了一个函数叫fun1,该函数被用于项目中,那么fun1这个名字就不能被其他人重新用于创建函数或变量, 否则会出现命名冲突
  • 文件依赖问题
    就是各个js文件中的依赖关系只能通过人肉的方式进行组织,文件依赖没有很好的管理起来

注意: 到这里时查看前端模块化开发的价值这篇文章

CommonJS、AMD和CMD

一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,各行其是就都乱套了,由此便出现了CommonJS、AMD、CMD等规范(注意:这些都是规范,规范的作用是让人来遵守的,因此出现了很多支持这些规范的工具)

注意:到这里时,阅读以下文章了解下这三者之间的关系:

CommonJS介绍

阅读这篇文章:
CommonJS规范
目标:

  • 了解CommonJS的特点
  • 了解CommonJS模块规范与AMD规范的区别

注意:

  • 如果对于文章中出现的CommonJS模块编写语法不熟悉,先不要深究,等进行实践时在进行深入了解,先只了解其特点

Node应用由模块组成,采用CommonJS模块规范。
规范特点:

  • CommonJS规范加载模块是同步的,也就是只有加载完成后,才能执行后面的操作,由于这个特性,CommonJS模块一般只用于服务器端,而不用于浏览器端
  • 所有代码均运行在模块作用域,不会污染全局变量;也就是说模块内部的变量,无法被外部模块调用,除非将其定义为global对象的属性
  • 模块加载顺序,按照其在代码中的书写顺序进行加载
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后将运行结果缓存,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存

语法特点

  • 定义模块: 定义模块 根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性
  • 模块输出:模块只有一个出口——module.exports对象,我们需要把模块希望输出的内容放入该对象上
  • 加载模块:加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象

代码示例

// 模块定义 myModel.js

var name = 'Byron';
function printName(){
    console.log(name);
}
function printFullName(firstName){
    console.log(firstName + name);
}
module.exports = {
    printName: printName,
    printFullName: printFullName
}

// 在其他文件中加载模块
var nameModule = require('./myModel.js');
nameModule.printName();

AMD规范

AMD 即 Asynchronous Module Definition异步模块定义),它是一个在浏览器端进行模块化开发的规范

因为该规范不是JavaScript原生支持的,因此使用该规范进行开发时,需要用到对应的库函数,如:RequireJS

RequireJS主要解决两个问题:

  • 多个js文件可能有依赖问题,被依赖的文件需要早于依赖它的文件被加载
  • js加载的时候浏览器会停止渲染,加载的文件越多,页面失去响应的时间越长

语法

定义模块
AMD规范定义了define()函数,它是一个全局变量,用于定于模块,语法:
define(id?, dependencies?, factory);

  • id:可选参数,用于定于模块的标识,如果没有提供该参数,模块名则为脚本文件名
  • dependencies:当前模块依赖的模块名称数组
  • factory:工厂方法,模块初始化要执行的函数或对象,如果是函数,它应该只被执行一次;如果是对象,此对象应该为模块的输出值

加载模块
在页面上使用require()函数加载模块,语法:
require([dependencies], function(){});
requiire接收两个参数:

  1. 第一个参数是一个数组,表示所依赖的模块
  2. 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数的形式传入该函数,从而在函数内部就可以使用这些模块
    注意: require()函数在加载依赖的模块时是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块加载完成后才会执行,*解决了依赖问题

例子

// 定义一个myModule.js模块
define(['dependency1', 'dependency2'], function(){
  var name = 'hexon';
  function sayName(){
    console.log(name);
  }
  return {
    sayName: sayName
  };
});

// 加载模块
require(['myModule'], function(my){
  my.sayName();  
});

示例解析

  • 通过define定义个模块,该模块的名字没有写明
  • 'dependency1'和''dependency2'为该模块所依赖的文件
  • 当依赖的文件加载完成后,执行function,返回一个对象,对象中包含需要提供给外部调用的内容,这里提供了一个sayName方法
  • 通过require加载模块,'myModule'为需要加载的模块,my为'myModule'模块的别名,通过my来调用myModule模块内的资源

参考文章:AMD (中文版):

AMD规范与CommonJS规范的兼容性

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

AMD规范使用define方法定义模块,下面就是一个例子:

define(['package/lib'], function(lib){
  function foo(){
    lib.log('hello world!');
  }

  return {
    foo: foo
  };
});

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面这样:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();
  anotherModule.doMoarAwesome();

  exports.asplode = function (){
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
  };
});

CMD规范

CMD即Common Module Definition通用模块定义,CMD规范是国内发展而来的,CMD的代表库有SeaJS,SeaJS要解决的问题和RequireJS一样,只不过在模块定义方式和模块加载时机上有所不同,SeaJS是需要时才去加载,而RequireJS是预先加载好
语法
SeaJS推崇一个模块一个文件,遵循统一写法,使用define关键字定义一个模块
define
define(factory)

Module Context

define(function(require, exports, module) {

  // The module code goes here

});

示例代码
A typical sample

math.js

define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});

increment.js

define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});

program.js

define(function(require, exports, module) {
  var inc = require('increment').increment;
  var a = 1;
  inc(a); // 2

  module.id == "program";
});

参考:CMD介绍

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

推荐阅读更多精彩内容