前言

我们再一次被计算机的名词、概念笼罩。

Backbone、Emberjs、Spinejs、Batmanjs 等MVC框架侵袭而来。
CommonJS、AMD、NodeJS、RequireJS、SeaJS、Curljs 等模块化的JavaScript概念及库扑面而来。

模块化JavaScript的概念尤为突出,似乎有赶超07年Ajax风潮之趋势。

写函数(过程式)
2005年以前,JavaScript没人重视,只作为表单验证等少量应用。那时一个网页上写不了几行JS代码,1000行算很复杂了。这时组织代码的方式是过程式,几十行的代码甚至连一个函数都不用写。稍多的需要提取抽象出一个函数,更复杂一些则需要更多函数,函数间互相调用。

写类(面向对象)
2006年,Ajax席卷全球。JavaScript被重视了,越来越多的后端逻辑放到了前端。网页中的JS代码量急剧增加。这时写函数方式组织大量代码显得力不从心。有时调试一个小功能,从一个函数可能会跳到第N个函数去。这时写类的方式出现了,Prototype 率先流行开来。用它组织代码,写出的都是一个个类,每个类都是Class.create创建的。又有YUI、Ext等重量级框架。虽然它们的写类方式各不同,但它们的设计思路却都是要满足大量JavaScript代码的开发。

写模块(现在,未来?)
2009年,Nodejs诞生!这个服务器端的JavaScript采用模块化的写法很快征服了浏览器端的JSer。牛人们纷纷仿效,各种写模块的规范也是层出不穷。CommonJS想统一前后端的写法,AMD则认为自己是适合浏览器端的。好吧,无论写模块的风格是啥样,写模块化的JavaScript却已开始流行了。你,准备好了吗?

模块化的JavaScript是神马? 这是我们发明了又一个银弹吗?无论是啥,就当学习吧。至于适不适合项目中使用,各自斟酌。

写到这也没说什么是“模块”。其实在计算机领域,模块化的概念被推崇了近四十年。软件总体结构体现模块化思想,即把软件划分为一些独立命名的部件,每个部件称为一个模块,当把所有模块组装在一起的时候,便可获得问题的一个解。模块化以分治法为依据,但是否意味着我们把软件无限制的细分下去?事实上当分割过细,模块总数增多,每个模块的成本确实减少了,但模块接口所需代价随之增加。

要确保模块的合理分割则须了解信息隐藏,内聚度及耦合度。

信息隐藏
模块应设计的使其所包含的信息(过程和数据)对于那些不需要用到它的模块不可见。每个模块只完成一个独立的功能,然后提供该功能的接口。模块间通过接口访问。JavaScript中可以用函数去隐藏,封装,而后返回接口对象。如下是一个提供事件管理的模块event。

函数内为了实现想要的接口bind、unbind、trigger可能需要写很多很多代码,但这些代码(过程和数据)对于其它模块来说不必公开,外部只要能访问接口bind,unbind,trigger即可。
信息隐藏对于模块设计好处十分明显,它不仅支持模块的并行开发,而且还可减少测试或后期维护工作量。如日后要修改代码,模块的隐藏部分可随意更改,前提是接口不变。如事件模块开始实现时为了兼容旧版本IE及标准浏览器,写了很多IE Special代码,有一天旧版本IE消失了(猴年马月),只需从容删去即可。

内聚度
内聚是来自结构化设计的一个概念,简单说内聚测量了单个模块内各个元素的联系程度。最不希望出现的内聚就是偶然性内聚,即将完全无关的抽象塞进同一个模块或类中。最希望出现的内聚是功能性内聚,即一个模块或类的各元素一同工作,提供某种清晰界定的行为。
内聚度指模块内部实现,它是信息隐藏和局部化概念的自然扩展,它标志着一个模块内部各成分彼此结合的紧密程度。好处也很明显,当把相关的任务分组后去阅读就容易多了。设计时应该尽可能的提高模块内聚度,从而获得较高的模块独立性。

耦合度
耦合也是来自结构化设计,Stevens、Myers和Constantine将耦合定义为「一个模块与另一个模块之间建立起的关联强度的测量。强耦合使系统变得复杂,因为如果模块与其它模块高度相连,它就难以独立的被理解、变化和修正」
内聚度是指特定模块内部实现的一种度量,耦合度则是指模块之间的关联程度的度量。耦合度取决于模块之间接口的复杂性,进入或调用模块的位置等。与内聚度相反,在设计时应尽量追求松散耦合的系统。

JavaScript中模块“写法”

在JavaScript模块到底是什么,能用代码具体展现一下吗?其实上面已经写了一段事件模块代码

这能代表“模块”吗?这就是一个JS对象啊,以为有多么深奥。

是的,JavaScript中模块多数时候被实现为一个对象。这么看来,多数时候我们都写过“模块”(但没有在整个项目中应用模块化思想)。或许每个人写模块的方式(风格)还不同。比如上面的事件模块是一个匿名函数执行,匿名函数中封装了很多代码,最后通过return返回给Event变量,这个Event就是事件模块的接口。

又如jQuery,它也是一个匿名函数执行,但它并不返回接口对象。而是将自己暴露给window对象。

再如SeaJS,它一开始就将接口公开了

后续是很多的匿名函数执行给变量seajs添加很多工具方法。注意,这里的this在浏览器环境指window对象,如果是定位在浏览器中,这个this也可以去掉。就象Ext。

我们已经看到了四种方式写模块(把jQuery,SeaJS,Ext看成模块,呃很大的模块)。哪一种更好呢? 哪一种更适合在浏览器端呢?纯从代码风格上说,是萝卜白菜各有所爱。只要我们运用了“模块化”的思想来开发就行了。

但如果有一种统一的语法格式来写模块岂不是更好,就不会出现各用各的风格来写模块而使代码乱糟糟。

这就是目前的现状,开发者强烈需要一种统一的风格来写模块(最好是语言内置了)。这时一些组织出现了,最具代表的如CommonJS,AMD。此外ECMAScript也开始着手模块的标准化写法。

无论它们提供什么样的写法,我们需要的仅仅是:

  • 将一些零散代码封装成一个有用的单元(encapsulate)
  • 导出模块的接口API(exports)
  • 方便友好引用其它模块(dependency)

服务器端的JSer是幸运的,它有Node.js,Node.js遵循了一个称为CommonJS的规范。CommonJS其中就有对写模块的标准化。当然模块化只是其中的一部分而已。

具体来说Node.js实现了:

在模块化方面,它实现了Modules/1.0(已经更新到1.1.1),以下是node中是写模块的一个示例。

这就写了一个math、increment、main模块。math提供了add方法来实现数字相加。increment模块依赖于math模块,它提供increment方法实现相加。main获取到increment方法,执行相加操作。

以上代码示例可以看到:

  • node要求一个js文件对应一个模块。可以把该文件中的代码想象成是包在一个匿名函数中,所有代码都在匿名函数中,其它模块不可访问除exports外的私有变量
  • 使用exports导出API
  • 使用require加载其它模块

CommonJS module基本要求如下:

  • 标示符require,为一个函数,它仅有一个参数为字符串,该字符串须遵守Module Identifiers的6点规定
  • require方法返回指定的模块API
  • 如果存在依赖的其它模块,那么依次加载
  • require不能返回,则抛异常
  • 仅能使用标示符exports导出API

Modules/1.1较1.0仅增加了标示符module,require函数增加了main和paths属性。而仔细比对1.1与1.1.1后发现除了格式调整了下几乎没有变化。

Node.js模块格式在浏览器中的尝试

前面提到Node.js有一套简洁的格式写模块,它遵循的就是 Moudles。
浏览器里的JavaScript呢? 尽管语言本身暂不支持模块(ES6打算支持),但可以用现有的API包装一个写法出来。
毫无疑问,首先想到的是Node.js的Modules格式,它是最好的效仿对象。因为前后端有一个统一的方式写JS模块岂不乐哉!
但一开始就碰到一些难题:
服务器端JS模块文件就在本地,浏览器端则需要通过网络请求。
服务器端可以很容易的实现同步或异步请求模块,浏览器端则问题多多。
如下。

这段代码中require如果是异步执行的,则event.bind的执行有可能会出错。
那实现同步的require不就行了吗?的确可以使用 XHR 实现同步载入模块JS文件。但XHR的缺点也是明显的,它不能跨域,这点让人很难接受,因为有些场景需要模块部署在不同的服务器。
那只能通过script tag来实现模块加载了!但script tag默认就是异步的,要实现Node.js的一模一样风格(Modules)很难,几乎是不可能。

这时,“救世主”出现了:Modules/Wrappings ,顾名思义包裹的模块。该规范约定如下:

  • 定义模块用module变量,它有一个方法declare。
  • declare接受一个函数类型的参数,如称为factory。
  • factory有三个参数分别为require、exports、module。
  • factory使用返回值和exports导出API。
  • factory如果是对象类型,则将该对象作为模块输出。

描述有拗口,代码却很简单,使用了一个function包裹模块(Node.js模块则无需包裹)。

Modules/Wrappings的出现使得浏览器中实现它变得可能,包裹的函数作为回调。即使用script tag作为模块加载器,script完全下载后去回调,回调中进行模块定义。

好了,截止目前我们已经看到了两种风格的模块定义:Modules 和 Modules/Wrappings。

CommonJS Modules有1.0、1.1、1.1.1三个版本:
Node.js、SproutCore实现了 Modules 1.0
SeaJS、AvocadoDB、CouchDB等实现了Modules 1.1.1
SeaJS、FlyScript实现了Modules/Wrappings

注意:
SeaJS未实现全部的 Modules 1.1.1,如require函数的main,paths属性在SeaJS中没有。但SeaJS给require添加了async、resolve、load、constructor。
SeaJS没有使用 Modules/Wrappings 中的module.declare定义模块,而是使用define函数(看起来象AMD中的define,实则不然)。

AMD:浏览器中的模块规范

前面提到,为实现与Node.js相同方式的模块写法,大牛们做了很多努力。

但浏览器环境不同于服务器端,它的模块有一个HTTP请求过程(而Node.js的模块文件就在本地),这个请求过程多数使用script tag,script 默认的异步性导致很难实现与Node.js一模一样的模块格式。

Modules/Wrappings 使得实现变为现实。虽然和Node.js的模块写法不完全一致,但也有很多相似之处,使得熟悉Node.js的程序员有一些亲切感。

但Node.js终究是服务器端的JavaScript,没有必要把这些条条框框放到浏览器JavaScript环境中。

这时AMD 诞生了,它的全称为异步模块定义。从名称上看便知它是适合script tag的。也可以说AMD是专门为浏览器中JavaScript环境设计的规范。它吸取了CommonJS的一些优点,但又不照搬它的格式。开始AMD作为CommonJS的transport format 存在,因无法与CommonJS开发者达成一致而独立出来。它有自己的wiki 和讨论组 。

AMD设计出一个简洁的写模块API:
define(id?, dependencies?, factory);
其中:
id: 模块标识,可以省略。
dependencies: 所依赖的模块,可以省略。
factory: 模块的实现,或者一个JavaScript对象。
特别指出,id遵循CommonJS Module Identifiers 。dependencies元素的顺序和factory参数一一对应。

以下是使用AMD模式开发的简单三层结构(基础库/UI层/应用层),用于展示模块的五种写法:

  1. 定义无依赖的模块(base.js)
  2. 定义有依赖的模块(ui.js,page.js)
  3. 定义数据对象模块(data.js)
  4. 具名模块
  5. 包装模块

以上同时演示了define的前三种用法。细心的会发现,还有两种没有出现:

如果不考虑多了一层函数外,格式和Node.js是一样的:使用require获取依赖模块,使用exports导出API。

除了define外,AMD还保留一个关键字require。require 作为规范保留的全局标识符,可以实现为 module loader,也可以不实现。

目前,实现AMD的库有RequireJS 、curl 、Dojo 、bdLoad、JSLocalnet 、Nodules 等。也有很多库支持AMD规范,即将自己作为一个模块存在,如MooTools 、jQuery 、qwery 、bonzo 甚至还有 firebug 。

UMD:各种模块格式的糅合

UMD是AMD 和CommonJS的糅合,前面花了很长的篇幅介绍了两大类模块规范,CommonJS(Modules/Modules/Wrappings)及AMD。

我们知道Modules/Wrappings是出于对Node.js模块格式的偏好而包装下使其在浏览器中得以实现。而Modules/Wrappings的格式通过某些工具(如r.js)也能运行在Node.js中。事实上,这两种格式同时有效且都被广泛使用。

AMD以浏览器为第一(browser-first)的原则发展,选择异步加载模块。它的模块支持对象(objects)、函数(functions)、构造器(constructors)、字符串(strings)、JSON等各种类型的模块。因此在浏览器中它非常灵活。

CommonJS module以服务器端为第一(server-first)的原则发展,选择同步加载模块。它的模块是无需包装的(unwrapped modules)且贴近于ES.next/Harmony的模块格式。但它仅支持对象类型(objects)模块。这迫使一些人又想出另一个更通用格式 UMD(Universal Module Definition)。希望提供一个前后端跨平台的解决方案。

UMD的实现很简单,先判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式。接着判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。前两个都不存在,则将模块公开到全局(window或global)。下面是一个示例:

下面是一个示例:

虽然UMD八字还没有一撇,有些开源库却开始支持UMD了,如大名鼎鼎的《JavaScript设计模式》作者Dustin Diaz开发的qwery。代码如下:

ECMAScript6:未来的JS模块化

ECMAScript的下一个版本Harmony已经考虑到了模块化的需求。

ES6 modules还需要很长时间来规范化,可谓任重而道远。且它有个问题,即新的语法关键字不能向下兼容(如低版本IE浏览器)。

喜欢(14) 评论(3) 分享
  1. Klima Servisi

    Teknolojinin bize sunduğu imkânların en güzel örneklerinden biri olan klimanın keyfi ne kadar güzelse servis işlemlerde o kadar zordur. Müdahale eden kişinin hem klimanın kullanımıyla hem de cihazın tüm teknik donanımıyla ilgili bilgisi olması lazım aksi takdirde cihaz çok olumsuz şekilde arızalanabilir size maddi hasar çıkartabilir. En önemlisi ise sağlığınızdan olabilirsiniz.

    Klima Servis olarak tüm bunların farkındalığıyla klima sektöründe yer alan firmaların cihazları hakkındaki teknik bilgilerle uzmanlaşmış servis elemanları yetiştirdik. Uzmanlarımız cihazlarınıza son model ekipmanları ile müdahale ederken teknik bilgilerini sonuna kadar kullanırken herhangi bir parça değişiminde orjinal yedek parça kullanarak cihazınızın bir daha aynı sebepten dolayı arıza yapmasının önüne geçer.

    Bilgisizlikten ve bilgisiz yaklaşımdan oluşan hasarları hayatınızdan silmek uzun zaman alabilir. İşte bu yüzden Kazımkarabekir Klima Servisi mize ulaşarak, klimalarınızın en ufak sorununda bile en hızla şekilde müdahale edecek ekiplerimizden klima servisi, klima bakımı, klima montajı, klima sökme takma, klima alt yapı hazırlama gibi hizmetleri talep edebilirsiniz.

Leave a Reply

© 2018 JDC. All Rights Reserved.