深入理解JavaScript 模块模式

(原文)http://www.cnblogs.com/starweb/archive/2013/02/17/2914023.html

英文:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

模块模式是JavaScript一种常用的编码模式。这是一般的理解,但也有一些高级应用没有得到很多关注。在本文中,我将回顾基础知识,浏览一些不错的高级技巧,甚至我认为是原生基础的。
基础知识
首先我们开始简单概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式众所周知。如果你已经很熟悉模型模式,可以直接阅读“高级模式”。
匿名闭包
这是一切成为可能的基础,也是JavaScript最好的特性。我们将简单的创建匿名函数,并立即执行。所有函数内部代码都在闭包(closure)内。它提供了整个应用生命周期的私有和状态。

  1. (function () {
  2. // ... all vars and functions are in this scope only
  3. // still maintains access to all globals
  4. }());

复制代码

注意匿名函数周围的()。这是语言的要求。关键字function一般认为是函数声明,包括()就是函数表达式。
引入全局
JavaScript有个特性,称为隐性全局。使用变量名称时,解释器会从作用域向后寻找变量声明。如果没找到,变量会被假定入全局(以后可以全局调用)。如果会被分配使用,在还不存在时全局创建它。这意味着在匿名函数里使用全局变量很简单。不幸的是,这会导致代码难以管理,文件中不容易区分(对人而言)哪个变量是全局的。
幸好,匿名函数还有一个不错的选择。全局变量作为参数传递给匿名函数。将它们引入我们的代码中,既更清晰,又比使用隐性全局更快。下面是一个例子:

  1. (function ($, YAHOO) {
  2. // 当前域有权限访问全局jQuery($)和YAHOO
  3. }(jQuery, YAHOO));

复制代码

模块出口
有时你不只想用全局变量,但你需要先声明他们(模块的全局调用)。我们用匿名函数的返回值,很容易输出他们。这样做就完成了基本的模块模式。以下是一个完整例子:

  1. var MODULE = (function () {
  2. var my = {},
  3. privateVariable = 1;
  4. function privateMethod() {
  5. // ...
  6. }
  7. my.moduleProperty = 1;
  8. my.moduleMethod = function () {
  9. // ...
  10. };
  11. return my;
  12. }());

复制代码

注意,我们声明了一个全局模块MODULE,有两个公开属性:方法MODULE.moduleMethod和属性MODULE.moduleProperty。而且,匿名函数的闭包还维持了私有内部状态。同时学会之上的内容,我们就很容易引入需要的全局变量,和输出到全局变量。
高级模式
对许多用户而言以上的还不足,我们可以采用以下的模式创造强大的,可扩展的结构。让我们使用MODULE模块,一个一个继续。
扩充
模块模式的一个限制是整个模块必须在一个文件里。任何人都了解长代码分割到不同文件的必要。还好,我们有很好的办法扩充模块。(在扩充文件)首先我们引入模块(从全局),给他添加属性,再输出他。下面是一个例子扩充模块:

  1. var MODULE = (function (my) {
  2. my.anotherMethod = function () {
  3. // 此前的MODULE返回my对象作为全局输出,因此这个匿名函数的参数MODULE就是上面MODULE匿名函数里的my
  4. };
  5. return my;
  6. }(MODULE));

复制代码

我们再次使用var关键字以保持一致性,虽然其实没必要。代码执行后,模块获得一个新公开方法MODULE.anotherMethod。扩充文件没有影响模块的私有内部状态。
松耦合扩充 上面的例子需要我们首先创建模块,然后扩充它,这并不总是必要的。提升JavaScript应用性能最好的操作就是异步加载脚本。因而我们可以创建灵活多部分的模块,可以将他们无顺序加载,以松耦合扩充。每个文件应有如下的结构:

  1. var MODULE = (function (my) {
  2. // add capabilities...
  3. return my;
  4. }(MODULE || {}));

复制代码

这个模式里,var语句是必须的,以标记引入时不存在会创建。这意味着你可以像LABjs一样同时加载所有模块文件而不被阻塞。
紧耦合扩充
虽然松耦合很不错,但模块上也有些限制。最重要的,你不能安全的覆写模块属性(因为没有加载顺序)。初始化时也无法使用其他文件定义的模块属性(但你可以在初始化后运行)。紧耦合扩充意味着一组加载顺序,但是允许覆写。下面是一个例子(扩充最初定义的MODULE):

  1. var MODULE = (function (my) {
  2. var old_moduleMethod = my.moduleMethod;
  3. my.moduleMethod = function () {
  4. // method override, has access to old through old_moduleMethod...
  5. };
  6. return my;
  7. }(MODULE));

复制代码

我们覆写的MODULE.moduleMethod,但依旧保持着私有内部状态。
克隆和继承

  1. var MODULE_TWO = (function (old) {
  2. var my = {},
  3. key;
  4. for (key in old) {
  5. if (old.hasOwnProperty(key)) {
  6. my[key] = old[key];
  7. }
  8. }
  9. var super_moduleMethod = old.moduleMethod;
  10. my.moduleMethod = function () {
  11. // override method on the clone, access to super through super_moduleMethod
  12. };
  13. return my;
  14. }(MODULE));

复制代码

这种方式也许最不灵活。他可以实现巧妙的组合,但是牺牲了灵活性。正如我写的,对象的属性或方法不是拷贝,而是一个对象的两个引用。修改一个会影响其他。这可能可以保持递归克隆对象的属性固定,但无法固定方法,除了带eval的方法。不过,我已经完整的包含了模块。(其实就是做了一次浅拷贝)。
跨文件私有状态
一个模块分割成几个文件有一个严重缺陷。每个文件都有自身的私有状态,且无权访问别的文件的私有状态。这可以修复的。下面是一个松耦合扩充的例子,不同扩充文件之间保持了私有状态:

  1. var MODULE = (function (my) {
  2. var _private = my._private = my._private || {},
  3. _seal = my._seal = my._seal || function () {
  4. delete my._private;
  5. delete my._seal;
  6. delete my._unseal;
  7. },//模块加载后,调用以移除对_private的访问权限
  8. _unseal = my._unseal = my._unseal || function () {
  9. my._private = _private;
  10. my._seal = _seal;
  11. my._unseal = _unseal;
  12. };//模块加载前,开启对_private的访问,以实现扩充部分对私有内容的操作
  13. // permanent access to _private, _seal, and _unseal
  14. return my;
  15. }(MODULE || {}));

复制代码

何文件都可以在本地的变量_private中设置属性,他会对别的扩充立即生效(即初始化时所有扩充的私有状态都保存在_private变量,并被my._private输出)。模块完全加载了,应用调用MODULE._seal()方法阻止对私有属性的读取(干掉my._private输出)。如果此后模块又需要扩充,带有一个私有方法。加载扩充文件前调用MODULE._unseal()方法(恢复my._private,外部恢复操作权限)。加载后调用再seal()。
这个模式一直随我工作至今,我还没看到别的地方这样做的。我觉得这个模式很有用,值得写上。
子模块
最后的高级模式实际上最简单。有很多好方法创建子模块。和创建父模块是一样的:

  1. MODULE.sub = (function () {
  2. var my = {};
  3. // 就是多一级命名空间
  4. return my;
  5. }());

复制代码

虽然很简单,但我还是提一下。子模块有所有正常模块的功能,包括扩充和私有状态。
总结
大多数高级模式可以互相组合成更多有用的模式。如果要我提出一个复杂应用的设计模式,我会组合松耦合、私有状态和子模块。
这里我还没有涉及性能,不过我有个小建议:模块模式是性能增益的。他简化了许多,加快代码下载。松耦合可以无阻塞并行下载,等价于提高下载速度。可能初始化比别的方法慢一点,但值得权衡。只要全局正确的引入,运行性能不会有任何损失,可能还因为局部变量和更少的引用,加快子模块的加载。
最后,一个例子动态加载子模块到父模块(动态创建)中。这里就不用私有状态了,其实加上也很简单。这段代码允许整个复杂分成的代码核心及其子模块等平行加载完全。

  1. var UTIL = (function (parent, $) {
  2. var my = parent.ajax = parent.ajax || {};
  3. my.get = function (url, params, callback) {
  4. // ok, so I'm cheating a bit :)
  5. return $.getJSON(url, params, callback);
  6. };
  7. // etc...
  8. return parent;
  9. }(UTIL || {}, jQuery));

复制代码

我希望你能受益的,并请发表评论,分享您的想法。 现在,继续前进,并写出更好的,更模块化的JavaScript!

转自:http://www.oschina.net/translate/javascript-module-pattern-in-depth

(转)深入理解JavaScript 模块模式的更多相关文章

  1. 深入理解JavaScript 模块模式

    http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html 模块模式是JavaScript一种常用的编码模式.这是一般的 ...

  2. Javascript 模块模式

    模块模式(Module Pattern)提供了一种代码封装的方式,可以优雅地创建非耦合的代码块. 它是利用即时函数为对象创建私有变量和特权方法.严格来说,Javascript中没有私有成员的概念,所有 ...

  3. javascript模块模式

    目前模块模式得到了广泛应用,因为它提供了结构化的思想并且有助于组织日益增长的代码.模块模式提供了一种创建自包含非耦合代码片段有利工具,可以将它视为黑盒功能. 板栗: var array = (func ...

  4. JQuery日记6.5 Javascript异步模式(一)

    理解力JQuery前实现异步队列,有必要理解javascript异步模式. Javascript异步其实并不严重格异步感,js使某些片段异步方式在将来运行,流不必等待继续向下进行. 在多线程的语言中最 ...

  5. Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 如何实现发布--订阅模式? 发布---订阅模式的代码封装 如何取消订阅事件? 全局--发布订阅对象代码封装 理解模块间通信 回到 ...

  6. 深入理解JavaScript系列(47):对象创建模式(上篇)

    介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度. ...

  7. 深入理解JavaScript系列(48):对象创建模式(下篇)

    介绍 本篇主要是介绍创建对象方面的模式的下篇,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式6:函数语法糖 函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用pro ...

  8. 【转】Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时 ...

  9. 深入理解JavaScript系列(36):设计模式之中介者模式

    介绍 中介者模式(Mediator),用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 主要内容来自:http://www ...

随机推荐

  1. Java堆和栈详解

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...

  2. ListView实现Item上下拖动交换位置 并且实现下拉刷新 上拉加载更多

    ListView实现Item上下拖动交换位置  并且实现下拉刷新  上拉加载更多 package com.example.ListViewDragItem; import android.app.Ac ...

  3. iOS开发之生成二维码

    一.二维码的生成 从iOS7开始集成了二维码的生成和读取功能 此前被广泛使用的zbarsdk目前不支持64位处理器   1.二维码的内容(传统的条形码只能放数字) 纯文本 名片 URL   2.生成二 ...

  4. [问题2015S04] 复旦高等代数 II(14级)每周一题(第五教学周)

    [问题2015S04] 设 \(A\) 为 \(n\) 阶方阵, \(C\) 为 \(k\times n\) 矩阵, 且对任意的 \(\lambda\in\mathbb{C}\), \(\begin{ ...

  5. 微软 WP 新策略:非开发者账号免费解锁 1 部手机(转)

    好消息,微软现在非开发者用户解锁1部WP8手机和部署两个应用,即我们只 需注册个windows live账号即可解锁我们的WP8了!! 准备工作:1.需要一台装有windows8 64位系统的电脑. ...

  6. php手册杂记

    1, strcmp()是比较两个字符串的大小,两个字符串相同时返回0,第一个字符串大于第二个字符串时返回一个正值,否则返回负值.比较两个字符串的算法是:逐个比较两个串中对应的字符,字符大小按照ASCI ...

  7. jQuery核心之 $

    参考jQuery官网API文档 $ 和 $() 的区别很重要: 1.$(document).ready() 和 $(document).load() 的 区别:     前者等到DOM准备好了之后就会 ...

  8. JavaScript严格模式详解

    转载自阮一峰的博客 Javascript 严格模式详解   作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...

  9. 阿里云对象存储OSS————跨域资源共享(CORS)(m3u8 无法加载m3u8:跨域访问被拒绝)

    今天在做视频直播录像的时候,添加一个录制APP的.M3U8文件到OSS的一个test文件中存储,结果是访问不到了: 提示:无法加载m3u8:跨域访问被拒绝!!!!! 项目代码测试地址:https:// ...

  10. Python中的logging模块

    http://python.jobbole.com/86887/ 最近修改了项目里的logging相关功能,用到了python标准库里的logging模块,在此做一些记录.主要是从官方文档和stack ...