在早期编写JavaScript时,我们只需在<script>标签内写入JavaScript的代码就可以满足我们对页面交互的需要了。但随着时间的推移,时代的发展,原本的那种简单粗暴的编写方式所带来的诸如逻辑混乱,页面复杂,可维护性差,全局变量暴露等问题接踵而至,前辈们为了解决这些问题提出了很种的解决方案,其中之一就是JavaScript模块化编程。总的来说,它有以下四种优点:

  1. 解决项目中的全局变量污染的问题。
  2. 开发效率高,有利于多人协同开发。
  3. 职责单一,方便代码复用和维护 。
  4. 解决文件依赖问题,无需关注引用文件的顺序。

先行者CommonJs

2009年Node.js横空出世,将JavaScript带到了服务器端领域。而对于服务器端来说,没有模块化那可是不行的。因此CommonJs社区的大牛们开始发力了,制定了一个与社区同名的关于模块化的规范——CommonJs。它的规范主要如下:

  1. 模块的标识应遵循的规则(书写规范)。
  2. 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴露出来的API。
  3. 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖。
  4. 如果引入模块失败,那么require函数应该报一个异常。
  5. 模块通过变量exports来向外暴露API,exports只能是一个对象,暴露的API须作为此对象的属性。

根据CommonJS规范的规定,每个文件就是一个模块,有自己的作用域,也就是在一个文件里面定义的变量、函数、类,都是私有的,对其他文件是不可见的。通俗来讲,就是说在模块内定义的变量和函数是无法被其他的模块所读取的,除非定义为全局对象的属性。

 // addA.js
const a = 1;
const addA = function(value) {
return value + a;
}

上面代码中,变量a和函数addA,是当前文件addA.js私有的,其他文件不可见。如果想在多个文件中分享变量a,必须定义为global对象的属性:

 global.a = 1;

这样我们就能在其他的文件中访问变量a了,但这种写法不可取,输出模块对象最好的方式是module.exports:

 // addA.js
var a = 1;
var addA = function(value) {
return value + x;
}
module.exports.addA = addA;

上面代码通过module.exports对象输出了一个函数,该函数就是模块外部与内部通信的桥梁。加载模块需要使用require方法,该方法读取一个文件并执行,最后返回文件内部的module.exports对象。

 var example = require('./addA.js');
console.log(example.addA(1)); //

CommonJs看起来是一个很不错的选择,拥有模块化所需要的严格的入口和出口,看起来一切都很美好,但它的一个特性却决定了它只能在服务器端大规模使用,而在浏览器端发挥不了太大的作用,那就是同步!这在服务器端不是什么问题,但放在浏览器端就出现问题了,因为文件都放在服务器上,如果网速不够快的话,前面的文件如果没有加载完成,浏览器就会失去响应!因此为了在浏览器上也实现模块化得来个异步的模块化才行!根据这个需求,我们的下一位主角——AMD就产生了!

AMD 异步模块定义

AMD的全名叫做:Asynchronous Module Definition即异步模块定义。它采用了异步的方式来加载模块,然后在回调函数中执行主逻辑,因此模块的加载不影响它后面的模块的运行。它的规范如下:

 define(id?, dependencies?, factory);
  1. 用全局函数define来定义模块;
  2. id为模块标识,遵从CommonJS Module Identifiers规范
  3. dependencies为依赖的模块数组,在factory中需传入形参与之一一对应
  4. 如果dependencies的值中有"require"、"exports"或"module",则与commonjs中的实现保持一致
  5. 如果dependencies省略不写,则默认为["require", "exports", "module"],factory中也会默认传入require,exports,module
  6. 如果factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx
  7. 如果factory为对象,则该对象即为模块的返回值

具体分析AMD我们通过require.js来进行。require.js是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一,require.js的出现主要是来解决两个问题:

  1. 实现JavaScript文件的异步加载,避免网页失去响应。
  2. 管理模块的依赖性,管理模块的相互独立性,也就是我们常说的低耦合,这有利于代码的编写与维护。

使用require.js我们首先要加载它,为了避免浏览器未响应,我们在后面可以加上async,告诉浏览器这个文件需要异步加载(IE不支持该属性,所以需要把defer也加上):

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

定义模块时,在require.js中我们可以使用define,但define对于需要定义的模块是否是独立的模块的写法是不同;所谓的独立模块就是指不依赖于其他模块的模块,而非独立模块就是指不依赖于其他模块的模块。

define在定义独立模块时有两种写法,一种是直接定义对象;另一种是定义一个函数,在函数内的返回值就是输出的模块了:

 define({
method1: function() {},
method2: function() {},
});
//等价于
define(function () {
return {
method1: function() {},
method2: function() {},
}
});

如果define定义非独立模块,那么它的语法就规定一定是这样的:

 define(['module1', 'module2'], function(m1, m2) {

     return {
method: function() {
m1.methodA();
m2.methodB();
}
} });

define在这个时候接受两个参数,第一个参数是module是一个数组,它的成员是我们当前定义的模块所依赖的模块,只有顺利加载了这些模块,我们新定义的模块才能成功运行。第二个参数是一个函数,当前面数组内的成员全部加载完之后它才运行,它的参数m与前面的module是一一对应的。这个函数必须返回一个对象,以供其他模块调用,需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

在加载模块方面,AMD和CommonJs都是使用require。require.js也同样如此,它要求两个参数:module,callback:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是需要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
require方法本身也是一个对象,它带有一个config方法,用来配置require.js运行参数。config方法接受一个对象作为参数。

 //别名配置
requirejs.config({
paths: {
jquery: [ //如果第一个路径不能完成加载,就调到第二个路径继续进行加载
'//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',
'lib/jquery' //本地文件中不需要写.js
]
}
}); //引入模块,用变量$表示jquery模块
requirejs(['jquery'], function ($) {
$('body').css('background-color','black');
});

虽然require.js实现了异步的模块化,但它仍然有一些不足的地方,在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载,使得初次加载其他模块的速度较慢,提高了开发成本。

CMD 通用模块定义

CMD的全称是Common Module Definition,即通用模块定义。它是由蚂蚁金服的前端大佬——玉伯提出来的,实现的JavaScript库为sea.js。它和AMD的require.js很像,但加载方式不同,它是按需就近加载的,而不是在模块的开始全部加载完成。它有以下两大核心特点:

  1. 简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码。
  2. 自然直观的代码组织方式:依赖的自动加载、配置的简洁清晰,可以让我们更多地享受编码的乐趣。

在CMD规范中,一个文件就是一个模块,代码书写的格式是这样的:

define(factory);

当factory为函数时,表示模块的构造方法,执行该方法,可以得到该模块对外提供的factory接口,factory 方法在执行时,默认会传入三个参数:require、exports 和 module:

 // 所有模块都通过 define 来定义
define(function(require, exports, module) { // 通过 require 引入依赖
var $ = require('jquery');
var Spinning = require('./spinning'); // 通过 exports 对外提供接口
exports.doSomething = ... // 或者通过 module.exports 提供整个接口
module.exports = ... });

它与AMD的具体区别其实我们也可以通过代码来表现出来,AMD需要在模块开始前就将依赖的模块加载出来,即依赖前置;而CMD则对模块按需加载,即依赖就近,只有在需要依赖该模块的时候再require就行了:

 // AMD规范
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
});
// CMD规范
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b')
// 依赖可以就近书写
b.doSomething()
// ...
});

需要注意的是Sea.js的执行模块顺序也是严格按照模块在代码中出现(require)的顺序。

从运行速度的角度来讲,AMD虽然在第一次使用时较慢,但在后面再访问时速度会很快;而CMD第一次加载会相对快点,但后面的加载都是重新加载新的模块,所以速度会慢点。总的来说,
require.js的做法是并行加载所有依赖的模块, 等完成解析后, 再开始执行其他代码, 因此执行结果只会"停顿"1次, 而Sea.js在完成整个过程时则是每次需要相应模块都需要进行加载,这期间会停顿是多次的,因此require.js从整体而言相对会比Sea.js要快一些。

简述JavaScript模块化编程(一)的更多相关文章

  1. 简述JavaScript模块化编程(二)

    前置阅读:简述JavaScript模块化(一) 在前面一文中,我们对前端模块化所经历的三个阶段进行了了解: CommonJs,由于是同步的,所以主要应用于服务器端,以Node.js为代表. AMD,异 ...

  2. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

  3. Javascript模块化编程(二):AMD规范

    Javascript模块化编程(二):AMD规范   作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...

  4. Javascript模块化编程(一):模块的写法

    Javascript模块化编程(一):模块的写法 作者: 阮一峰 原文链接:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html ...

  5. Javascript模块化编程(二):AMD规范(转)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...

  6. Javascript模块化编程(一):模块的写法(转)

    随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者 ...

  7. Javascript模块化编程(二):AMD规范 作者: 阮一峰

    声明:转载自阮一峰的网络日志 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可 ...

  8. Javascript模块化编程(一):模块的写法 作者: 阮一峰

    声明:转载自阮一峰的网络日志 随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理. ...

  9. Javascript模块化编程之路——(require.js)

    转自:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html Javascript模块化编程(一):模块的写法 随着网站逐渐变成&q ...

随机推荐

  1. win10系统优化方法及chkdsk工具使用

    发现安装了测试版的Win10真的很慢!其实Win10在每次升级后,的确会比老版慢上很多,这只要是因为新增加的安全功能,往往会对系统速度造成拖累.但有很多方法都可以让你的系统运行如飞. http://b ...

  2. MAC OS系统替换homebrew使用阿里云或中科大的镜像源

    阿里云镜像 # 替换brew.git: cd "$(brew --repo)" git remote set-url origin https://mirrors.aliyun.c ...

  3. 运维笔记--Ubuntu离线安装jdk1.8(JDK8)Java环境

    Ubuntu离线环境安装jdk1.8 系统版本:Ubuntu16.04 其他Ubuntu版本安装类似 Jdk版本:8 1. 下载相应离线软件包 Jdk oracle官方下载地址:https://www ...

  4. C#Selenium常用语法功能 很好的文章,值得参考收藏

     https://blog.csdn.net/a1003434346/article/details/80257946 https://www.jianshu.com/p/310623afcde1 h ...

  5. Java13新特性 -- ZGC:取消使用未使用的内存

    在JDK 11中,Java引入了ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的.号称不管你开了多大的堆内存,它都能保证在 10 毫秒内释放 JVM ,不让它停顿在那.但是,当时的设计是 ...

  6. Better ultra_simple for Slamtec RPLIDAR on Linux

    Improved the ultra_simple program to visualize the samples with GLUT on Linux, tested with Slamtec R ...

  7. Linux 对音频万能处理的命令——SOX

    what's the SOX         SoX(即 Sound eXchange)是一个跨平台(Windows,Linux,MacOS 等)的命令行实用程序,可以将各种格式的音频文件转换为需要的 ...

  8. linux 下mysql 关闭 启动

    一. 启动 .使用 service 启动:service mysql start .使用 mysqld 脚本启动:/etc/init.d/mysql start .使用 safe_mysqld 启动: ...

  9. Efficient and Accurate Arbitrary-Shaped Text Detection with Pixel Aggregation Network(利用像素聚合网络进行高效准确的任意形状文本检测)

    PSENet V2昨日刚出,今天翻译学习一下. 场景文本检测是场景文本阅读系统的重要一步,随着卷积神经网络的快速发展,场景文字检测也取得了巨大的进步.尽管如此,仍存在两个主要挑战,它们阻碍文字检测部署 ...

  10. 从支付宝SDK的支付流程理解什么是公钥和私钥,什么是加密和数字签名

    ------------------- 这是自己总结: 支付宝SDK支付用到的公钥与私钥整理如下: 1.商户应用公钥    2.商户应用私钥 3.支付宝公钥 4.支付宝私钥   商户应用的公钥与私钥生 ...