随着BS架构的发展,网站逐渐变成了互联网应用程序,嵌入网络的JavaScript代码越来越庞大,越来越复杂(业务逻辑处理或用户交互很多写在前端)。网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等。。开发者不得不使用软件工程的方法,管理网页的业务逻辑。因此JavaScript模块化编程已经成了一个迫切的需求,理想的情况下是开发者只需要实现核心的业务逻辑,其他业务处理都可以加载别人已经写好的模块,做到明确分工而不会相互影响。

但是,JavaScript却不是一种模块化编程语言,它不支持类(class),更别说模块(module)了。虽然ECMAScipt正在谋划支持和推广类和模块的概念,但要实际投入生产还是遥遥无期,只能自己另外想办法。为此JavaSript社区做了很多努力,努力在现有的运行环境中,利用现有的资源,实现模块化的效果。

模块化的原始写法

模块的定义就是实现特定功能的一组方法,只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块了。

  1. function f1() {
  2. // doSomething
  3. }
  4.  
  5. function f2() {
  6. // doSomething
  7. }

上面的函数f1()和f2()共同组成了一个模块,使用的时候直接通过函数名调用就行了。这种做法的缺点很明显,既污染了全局变量(f1和f2处在全局的上下文栈中,属于window的属性),也无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接的关系。

模块化的对象写法

为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

  1. var module1 = {
  2. status: 233,
  3.  
  4. f1: function() {
  5. // doSomething
  6. },
  7.  
  8. f2: function() {
  9. // doSomething
  10. }
  11. }

上面的函数f1()和函数f2()都封装在了module1对象里,使用的时候就是通过访问module1对象的属性。

  1. module1.f1();

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

  1. module1.status = 666;

模块化的立即执行函数写法

使用立即执行函数(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。

  1. var module1 = (function() {
  2. var status = 233;
  3.  
  4. var f1 = function() {
  5. // doSomething
  6. };
  7.  
  8. var f2 = function() {
  9. // doSomething
  10. };
  11. })();

使用这样的写法,外部的代码就无法读取到内部的变量。

  1. console.log(module1.status); // undefined

这种写法,就是JavaScript模块化的基本写法,后面的实现基本上都是依照这个思路。

模块化的放大模式

如果一个模块很大,就会要拆分成几个小的模块,或者一个模块需要继承另一个模块,这个时候就要采用放大模式(Augmentation)。

  1. var module1 = (function(mod) {
  2. mod.f3 = function() {
  3. // doSomething
  4. };
  5.  
  6. return mod;
  7. })(module1);

这里为module1模块添加了一个新函数f3(),然后返回新的module1模块。也就是把旧的对象传进来,给这个对象添加属性,然后返回添加了属性后的对象,相当于扩展。

模块化的宽放大模式

在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在的空对象,这时就要采用宽放大模式(Loose Augmentation)。

  1. var module1 = (function(mod) {
  2. // doSomething
  3.  
  4. return mod;
  5. })(window.module1 || {});

与放大模式相比,宽放大模式就是立即执行函数的参数可以是空对象。

模块化的全局变量输入

独立性是模块化的重要特点,模块内部最好不与程序的其他部分直接交互。

为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

  1. var module1 = (function($, YAHOO) {
  2. // doSomething
  3. })(jQuery, YAHOO);

这里的module1模块中需要使用jQuery库和YUI库,于是就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,也使得模块之间的依赖关系变得明显。

模块化的几种规范

模块化的前提是要遵循同一套规范,否则模块之间的调用会十分困难。

JavaScript官方没有模块化的规范,目前通用的民间规范主要有CommonJS(服务端js模块化的规范,NodeJS是这种规范的实现)、AMD(Asynchronous Module Definition异步模块定义,RequireJS遵循此规范)和CMD(Common Module Definition,通用模块定义,SeaJS遵循此规范)。

模块化在服务端的规范:CommonJS

2009年,美国程序员Ryan Dahl创造了node.js项目,将JavaScipt语言用于服务器端编程(后端)。这标志着JavaScipt模块化编程正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性的方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载并调用模块中提供的方法:

  1. var math = require('math');
  2. math.add(2, 3); //

更多的用法这里就不说了,只需要知道CommonJS是使用require()函数加载模块就行了。

模块化规范从服务端到客户端的发展

有了服务端的模块化之后,大家就想要客户端的模块化了。而且最好两者能兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是由于一个重大局限,使得CommonJS规范不适用于浏览器环境。这是因为,在上面的代码中,调用math的方法必须要在math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

这对于服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是对于浏览器来说,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,可能会导致浏览器处于假死状态。

因此浏览器端的模块化不能使用同步加载(Synchronous),只能使用异步加载(Asynchronous)。这就是AMD规范诞生的背景。

模块化在客户端的规范:AMD

AMD(Asynchronous Module Definition,异步模块定义)采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,直到加载完成之后,这个回调函数才会运行。

AMD也采用require()语句加载模块,不同于CommonJS的是,它要求两个参数:

  1. require([module], callback);

第一个参数[moudle],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是这样:

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

这样,math.add()与math模块的加载就不是同步的,浏览器也不会发生假死的状况。所以很显然地是AMD比较适合浏览器环境。应用AMD规范的主要有require.js。

模块化在客户端的规范:CMD

CMD是SeaJS在推广过程中对模块定义的规范化产出。

AMD和CMD的区别:

1.对于依赖的模块,AMD是提前执行,CMD是延迟执行。CMD推崇的是as lazy as possible,即尽可能得懒加载(延迟加载),即在需要得时候才加载。

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

  1. // CMD
  2. define(function(require, exports, module) {
  3. var a = require('./a');
  4. a.doSomething();
  5. var b = require('./b'); // 依赖可以就近书写
  6. b.doSomething();
  7. })
  8.  
  9. // AMD 默认推荐的是
  10. define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  11. a.doSomething();
  12. b.doSomething();
  13. })

3.AMD的API默认是一个当多个用,CMD的API则是严格区分,推崇职责单一。比如在AMD里,require分全局和局部,而在CMD里则没有全局require,而是根据模块系统的完备性,提供seajs.use来实现模块系统的加载启动。CMD里,每个API都简单存粹。

模块化的优点总结

1.解决了命名的冲突问题。多人开发的场景下,容易出现命名冲突,模块化通过内部封装与外部隔离能有效防止命名冲突的问题。

2.解决了文件的依赖问题,使文件易于管理。如果有很多js文件相互依赖,依赖关系和加载顺序都是让人头冷的问题。使用模块化就可以很好地实现依赖管理(使用依赖都要提前声明)。

3.提高代码的可读性。各个模块各自完成自己的功能,专司其职,除了问题也会便于维护。

4.提高代码的复用性。可以抽提特定的通用功能作为一个通用的模块。

"可是怎么办,想起你的时候,心还是会疼。"

javascript模块化编程思想、实现与规范的更多相关文章

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

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

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

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

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

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

  4. Javascript模块化编程(二):AMD规范【转】

    作者: 阮一峰 日期: 2012年10月30日 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为 ...

  5. javascript模块化编程(AMD规范的加载器)

    关于AMD规范可以参考阮一峰的这篇文章Javascript模块化编程(二):AMD规范 简单来说,AMD规范就是异步方式加载模块的一种方式,避免因为模块加载过慢而导致浏览器“假死”. 先贴一个学习地址 ...

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

    转自 ruanyifeng 系列目录: Javascript模块化编程(一):模块的写法 Javascript模块化编程(二):AMD规范 Javascript模块化编程(三):Require.js的 ...

  7. Javascript模块化编程(二)AMD规范(规范使用模块)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块,先想一想,为什么模块很重要?接下来为您详细介绍,感兴趣的朋友可以了解下啊.今天介绍如何规范地使用模块. 七.模块 ...

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

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

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

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

随机推荐

  1. Python线程与进程 I/O多路复用

    SSHClient Paramiko模块 远程执行命令 #用户名密码方式: import paramiko ssh = paramiko.SSHClient() ssh.set_missing_hos ...

  2. Pycharm快捷键集合

    运行类:Alt + Shift + F10 运行模式配置Alt + Shift + F9 调试模式配置Shift + F10 运行Shift + F9 调试Ctrl + Shift + F10 运行编 ...

  3. uni-app学习(四)好用的插件2

    1. uni-app学习(四)好用的插件2 1.1. 树形结构 点击这里 1.2. 下拉刷新上拉加载组件 如果想把下拉上拉做成自定义的,更加好看,可以使用这个插件 地址这里 举个例子 1.3. 浮动键 ...

  4. python 生成 树状结构

    树状结构: 字典里只有一个键值对, key 为根, 值为一个列表, 列表里的某个或多个元素可以再进行分支(分支还是列表) 比如: 邮件的发件人, 收件人, 转发关系树状结构 forwarding_re ...

  5. Dynamics CRM使用元数据之一:查询实体的主字段(托管代码版本)

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复159或者20151013可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! Dynamics CRM是基于元 ...

  6. 微信小程序底部导航栏(tabbar)

    在app.json处设置“tabBar”,例子如下: { "pages": [ "pages/index/index", "pages/pages1/ ...

  7. DevOps 工程师成长日记系列二:配置

    原文地址:https://medium.com/@devfire/how-to-become-a-devops-engineer-in-six-months-or-less-part-2-config ...

  8. JavaScript—字符串(String)用法

    字符串(String)去除空格 str = " hello python " // 去除左空格: str=str.replace( /^\s*/, ''); // 去除右空格: s ...

  9. docker介绍和安装(一)

    虚拟化简介 虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器.网络.内存及存储等,予以抽象.转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以 ...

  10. JAVA框架中XML文件

    其实在JAVA开发中servlet配置,映射注入配置等等都可以用xml来配置 在此处的department是实体类的名字,而不是对应的数据库表的名字 数据库表的字段名=#{实体类属性名} 逆向工程生成 ...