在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 模塊化模式
中介者模式
MVVM
亨元模式
設(shè)計模式分類概覽表
ES Harmony
組合模式
CommonJS
jQuery 插件的設(shè)計模式
外觀模式
觀察者模式
建造者模式
構(gòu)造器模式
外觀模式
簡介
AMD
原型模式
設(shè)計模式的分類
觀察者模式
命名空間模式
代理模式
編寫設(shè)計模式
適配器模式
反模式
什么是設(shè)計模式
模塊化模式
MVC
Mixin 模式
裝飾模式
設(shè)計模式的結(jié)構(gòu)
單例模式
迭代器模式
命令模式
工廠模式
MVP
暴露模塊模式
惰性初始模式

模塊化模式

模塊

模塊是任何健壯的應(yīng)用程序體系結(jié)構(gòu)不可或缺的一部分,特點(diǎn)是有助于保持應(yīng)用項(xiàng)目的代碼單元既能清晰地分離又有組織。

在JavaScript中,實(shí)現(xiàn)模塊有幾個選項(xiàng),他們包括:

  • 模塊化模式
  • 對象表示法
  • AMD模塊
  • CommonJS 模塊
  • ECMAScript Harmony 模塊

我們在書中后面的現(xiàn)代模塊化JavaScript設(shè)計模式章節(jié)中將探討這些選項(xiàng)中的最后三個。

模塊化模式是基于對象的文字部分,所以首先對于更新我們對它們的知識是很有意義的。

對象字面值

在對象字面值的標(biāo)記里,一個對象被描述為一組以逗號分隔的名稱/值對括在大括號({})的集合。對象內(nèi)部的名稱可以是字符串或是標(biāo)記符后跟著一個冒號":"。在對象里最后一個名稱/值對不應(yīng)該以","為結(jié)束符,因?yàn)檫@樣會導(dǎo)致錯誤。

var myObjectLiteral = {

    variableKey: variableValue,

    functionKey: function () {
      // ...
    };
};

對象字面值不要求使用新的操作實(shí)例,但是不能夠在結(jié)構(gòu)體開始使用,因?yàn)榇蜷_"{"可能被解釋為一個塊的開始。在對象外新的成員會被加載,使用分配如下:smyModule.property = "someValue"; 下面我們可以看到一個更完整的使用對象字面值定義一個模塊的例子:

var myModule = {

  myProperty: "someValue",

  // 對象字面值包含了屬性和方法(properties and methods).
  // 例如,我們可以定義一個模塊配置進(jìn)對象:
  myConfig: {
    useCaching: true,
    language: "en"
  },

  // 非?;镜姆椒?  myMethod: function () {
    console.log( "Where in the world is Paul Irish today?" );
  },

  // 輸出基于當(dāng)前配置(<span>configuration</span>)的一個值
  myMethod2: function () {
    console.log( "Caching is:" + ( this.myConfig.useCaching ) ? "enabled" : "disabled" );
  },

  // 重寫當(dāng)前的配置(configuration)
  myMethod3: function( newConfig ) {

    if ( typeof newConfig === "object" ) {
      this.myConfig = newConfig;
      console.log( this.myConfig.language );
    }
  }
};

// 輸出: Where in the world is Paul Irish today?
myModule.myMethod();

// 輸出: enabled
myModule.myMethod2();

// 輸出: fr
myModule.myMethod3({
  language: "fr",
  useCaching: false
});

使用對象字面值可以協(xié)助封裝和組織你的代碼。如果你想近一步了解對象字面值可以閱讀 Rebecca Murphey 寫過的關(guān)于此類話題的更深入的文章(depth)。

也就是說,如果我們選擇了這種技術(shù),我們可能對模塊模式有同樣的興趣。即使使用對象字面值,但也只有一個函數(shù)的返回值。

模塊化模式

模塊化模式最初被定義為一種對傳統(tǒng)軟件工程中的類提供私有和公共封裝的方法。

在JavaScript中,模塊化模式用來進(jìn)一步模擬類的概念,通過這樣一種方式:我們可以在一個單一的對象中包含公共/私有的方法和變量,從而從全局范圍中屏蔽特定的部分。這個結(jié)果是可以減少我們的函數(shù)名稱與在頁面中其他腳本區(qū)域定義的函數(shù)名稱沖突的可能性。

私有信息

模塊模式使用閉包的方式來將"私有信息",狀態(tài)和組織結(jié)構(gòu)封裝起來。提供了一種將公有和私有方法,變量封裝混合在一起的方式,這種方式防止內(nèi)部信息泄露到全局中,從而避免了和其它開發(fā)者接口發(fā)生沖圖的可能性。在這種模式下只有公有的API 會返回,其它將全部保留在閉包的私有空間中。

這種方法提供了一個比較清晰的解決方案,在只暴露一個接口供其它部分使用的情況下,將執(zhí)行繁重任務(wù)的邏輯保護(hù)起來。這個模式非常類似于立即調(diào)用函數(shù)式表達(dá)式(IIFE-查看命名空間相關(guān)章節(jié)獲取更多信息),但是這種模式返回的是對象,而立即調(diào)用函數(shù)表達(dá)式返回的是一個函數(shù)。

需要注意的是,在javascript事實(shí)上沒有一個顯式的真正意義上的"私有性"概念,因?yàn)榕c傳統(tǒng)語言不同,javascript沒有訪問修飾符。從技術(shù)上講,變量不能被聲明為公有的或者私有的,因此我們使用函數(shù)域的方式去模擬這個概念。在模塊模式中,因?yàn)殚]包的緣故,聲明的變量或者方法只在模塊內(nèi)部有效。在返回對象中定義的變量或者方法可以供任何人使用。

歷史

從歷史角度來看,模塊模式最初是在2003年由一群人共同發(fā)展出來的,這其中包括Richard Cornford。后來通過Douglas Crockford的演講,逐漸變得流行起來。另外一件事情是,如果你曾經(jīng)用過雅虎的YUI庫,你會看到其中的一些特性和模塊模式非常類似,而這種情況的原因是在創(chuàng)建YUI框架的時候,模塊模式極大的影響了YUI的設(shè)計。

例子

下面這個例子通過創(chuàng)建一個自包含的模塊實(shí)現(xiàn)了模塊模式。

var testModule = (function () {

  var counter = 0;

  return {

    incrementCounter: function () {
      return counter++;
    },

    resetCounter: function () {
      console.log( "counter value prior to reset: " + counter );
      counter = 0;
    }
  };

})();

// Usage:

// Increment our counter
testModule.incrementCounter();

// Check the counter value and reset
// Outputs: 1
testModule.resetCounter();

在這里我們看到,其它部分的代碼不能直接訪問我們的incrementCounter() 或者 resetCounter()的值。counter變量被完全從全局域中隔離起來了,因此其表現(xiàn)的就像一個私有變量一樣,它的存在只局限于模塊的閉包內(nèi)部,因此只有兩個函數(shù)可以訪問counter。我們的方法是有名字空間限制的,因此在我們代碼的測試部分,我們需要給所有函數(shù)調(diào)用前面加上模塊的名字(例如"testModule")。

當(dāng)使用模塊模式時,我們會發(fā)現(xiàn)通過使用簡單的模板,對于開始使用模塊模式非常有用。下面是一個模板包含了命名空間,公共變量和私有變量。

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();

看一下另外一個例子,下面我們看到一個使用這種模式實(shí)現(xiàn)的購物車。這個模塊完全自包含在一個叫做basketModule 全局變量中。模塊中的購物車數(shù)組是私有的,應(yīng)用的其它部分不能直接讀取。只存在與模塊的閉包中,因此只有可以訪問其域的方法可以訪問這個變量。

var basketModule = (function () {

  // privates

  var basket = [];

  function doSomethingPrivate() {
    //...
  }

  function doSomethingElsePrivate() {
    //...
  }

  // Return an object exposed to the public
  return {

    // Add items to our basket
    addItem: function( values ) {
      basket.push(values);
    },

    // Get the count of items in the basket
    getItemCount: function () {
      return basket.length;
    },

    // Public alias to a  private function
    doSomething: doSomethingPrivate,

    // Get the total value of items in the basket
    getTotal: function () {

      var q = this.getItemCount(),
          p = 0;

      while (q--) {
        p += basket[q].price;
      }

      return p;
    }
  };
}());

在模塊內(nèi)部,你可能注意到我們返回了應(yīng)外一個對象。這個自動賦值給了basketModule 因此我們可以這樣和這個對象交互。

// basketModule returns an object with a public API we can use

basketModule.addItem({
  item: "bread",
  price: 0.5
});

basketModule.addItem({
  item: "butter",
  price: 0.3
});

// Outputs: 2
console.log( basketModule.getItemCount() );

// Outputs: 0.8
console.log( basketModule.getTotal() );

// However, the following will not work:

// Outputs: undefined
// This is because the basket itself is not exposed as a part of our
// the public API
console.log( basketModule.basket );

// This also won't work as it only exists within the scope of our
// basketModule closure, but not the returned public object
console.log( basket );

上面的方法都處于basketModule 的名字空間中。

請注意在上面的basket模塊中 域函數(shù)是如何在我們所有的函數(shù)中被封裝起來的,以及我們?nèi)绾瘟⒓凑{(diào)用這個域函數(shù),并且將返回值保存下來。這種方式有以下的優(yōu)勢:

  • 可以創(chuàng)建只能被我們模塊訪問的私有函數(shù)。這些函數(shù)沒有暴露出來(只有一些API是暴露出來的),它們被認(rèn)為是完全私有的。
  • 當(dāng)我們在一個調(diào)試器中,需要發(fā)現(xiàn)哪個函數(shù)拋出異常的時候,可以很容易的看到調(diào)用棧,因?yàn)檫@些函數(shù)是正常聲明的并且是命名的函數(shù)。
  • 正如過去 T.J Crowder 指出的,這種模式同樣可以讓我們在不同的情況下返回不同的函數(shù)。我見過有開發(fā)者使用這種技巧用于執(zhí)行UA(尿檢,抽樣檢查)測試,目的是為了在他們的模塊里面針對IE專門提供一條代碼路徑,但是現(xiàn)在我們也可以簡單的使用特征檢測達(dá)到相同的目的。

模塊模式的變體

Import mixins(導(dǎo)入混合)

這個變體展示了如何將全局(例如 jQuery, Underscore)作為一個參數(shù)傳入模塊的匿名函數(shù)。這種方式允許我們導(dǎo)入全局,并且按照我們的想法在本地為這些全局起一個別名。

// Global module
var myModule = (function ( jQ, _ ) {

    function privateMethod1(){
        jQ(".container").html("test");
    }

    function privateMethod2(){
      console.log( _.min([10, 5, 100, 2, 1000]) );
    }

    return{
        publicMethod: function(){
            privateMethod1();               
        }           
    };

// Pull in jQuery and Underscore
}( jQuery, _ ));

myModule.publicMethod();

Exports(導(dǎo)出)

這個變體允許我們聲明全局對象而不用使用它們,同樣也支持在下一個例子中我們將會看到的全局導(dǎo)入的概念。

// Global module
var myModule = (function () {

    // Module object
  var module = {},
    privateVariable = "Hello World";

  function privateMethod() {
    // ...
  }

  module.publicProperty = "Foobar";
  module.publicMethod = function () {
    console.log( privateVariable );
  };

  return module;

}());

工具箱和框架特定的模塊模式實(shí)現(xiàn)。

Dojo

Dojo提供了一個方便的方法 dojo.setObject() 來設(shè)置對象。這需要將以"."符號為第一個參數(shù)的分隔符,如:myObj.parent.child 是指定義在"myOjb"內(nèi)部的一個對象“parent”,它的一個屬性為"child"。使用setObject()方法允許我們設(shè)置children 的值,可以創(chuàng)建路徑傳遞過程中的任何對象即使這些它們根本不存在。

例如,如果我們聲明商店命名空間的對象basket.coreas,可以實(shí)現(xiàn)使用傳統(tǒng)的方式如下:

var store = window.store || {};

if ( !store["basket"] ) {
  store.basket = {};
}

if ( !store.basket["core"] ) {
  store.basket.core = {};
}

store.basket.core = {
  // ...rest of our logic
};

或使用Dojo1.7(AMD兼容的版本)及以上如下:

require(["dojo/_base/customStore"], function( store ){

  // using dojo.setObject()
  store.setObject( "basket.core", (function() {

      var basket = [];

      function privateMethod() {
          console.log(basket);
      }

      return {
          publicMethod: function(){
                  privateMethod();
          }
      };

  }()));

});

欲了解更多關(guān)于dojo.setObject()方法的信息,請參閱官方文檔 documentation

ExtJS

對于這些使用Sencha的ExtJS的人們,你們很幸運(yùn),因?yàn)楣俜轿臋n包含一些例子,用于展示如何正確地在框架里面使用模塊模式。

下面我們可以看到一個例子關(guān)于如何定義一個名字空間,然后填入一個包含有私有和公有API的模塊。除了一些語義上的不同之外,這個例子和使用vanilla javascript 實(shí)現(xiàn)的模塊模式非常相似。

// create namespace
Ext.namespace("myNameSpace");

// create application
myNameSpace.app = function () {

  // do NOT access DOM from here; elements don't exist yet
  // private variables

  var btn1,
      privVar1 = 11;

  // private functions
  var btn1Handler = function ( button, event ) {
      console.log( "privVar1=" + privVar1 );
      console.log( "this.btn1Text=" + this.btn1Text );
    };

  // public space
  return {
    // public properties, e.g. strings to translate
    btn1Text: "Button 1",

    // public methods
    init: function () {

      if ( Ext.Ext2 ) {

        btn1 = new Ext.Button({
          renderTo: "btn1-ct",
          text: this.btn1Text,
          handler: btn1Handler
        });

      } else {

        btn1 = new Ext.Button( "btn1-ct", {
          text: this.btn1Text,
          handler: btn1Handler
        });

      }
    }
  };
}();

YUI

類似地,我們也可以使用YUI3來實(shí)現(xiàn)模塊模式。下面的例子很大程度上是基于原始由Eric Miraglia實(shí)現(xiàn)的YUI本身的模塊模式,但是和vanillla Javascript 實(shí)現(xiàn)的版本比較起來差異不是很大。

Y.namespace( "store.basket" ) = (function () {

    var myPrivateVar, myPrivateMethod;

    // private variables:
    myPrivateVar = "I can be accessed only within Y.store.basket.";

    // private method:
    myPrivateMethod = function () {
        Y.log( "I can be accessed only from within YAHOO.store.basket" );
    }

    return {
        myPublicProperty: "I'm a public property.",

        myPublicMethod: function () {
            Y.log( "I'm a public method." );

            // Within basket, I can access "private" vars and methods:
            Y.log( myPrivateVar );
            Y.log( myPrivateMethod() );

            // The native scope of myPublicMethod is store so we can
            // access public members using "this":
            Y.log( this.myPublicProperty );
        }
    };

})();

jQuery

因?yàn)閖Query編碼規(guī)范沒有規(guī)定插件如何實(shí)現(xiàn)模塊模式,因此有很多種方式可以實(shí)現(xiàn)模塊模式。Ben Cherry 之間提供一種方案,因?yàn)槟K之間可能存在大量的共性,因此通過使用函數(shù)包裝器封裝模塊的定義。

在下面的例子中,定義了一個library 函數(shù),這個函數(shù)聲明了一個新的庫,并且在新的庫(例如 模塊)創(chuàng)建的時候,自動將初始化函數(shù)綁定到document的ready上。

function library( module ) {

  $( function() {
    if ( module.init ) {
      module.init();
    }
  });

  return module;
}

var myLibrary = library(function () {

  return {
    init: function () {
      // module implementation
    }
  };
}());

優(yōu)勢

既然我們已經(jīng)看到單例模式很有用,為什么還是使用模塊模式呢?首先,對于有面向?qū)ο蟊尘暗拈_發(fā)者來講,至少從javascript語言上來講,模塊模式相對于真正的封裝概念更清晰。

其次,模塊模式支持私有數(shù)據(jù)-因此,在模塊模式中,公共部分代碼可以訪問私有數(shù)據(jù),但是在模塊外部,不能訪問類的私有部分(沒開玩笑!感謝David Engfer 的玩笑)。

缺點(diǎn)

模塊模式的缺點(diǎn)是因?yàn)槲覀儾捎貌煌姆绞皆L問公有和私有成員,因此當(dāng)我們想要改變這些成員的可見性的時候,我們不得不在所有使用這些成員的地方修改代碼。

我們也不能在對象之后添加的方法里面訪問這些私有變量。也就是說,很多情況下,模塊模式很有用,并且當(dāng)使用正確的時候,潛在地可以改善我們代碼的結(jié)構(gòu)。

其它缺點(diǎn)包括不能為私有成員創(chuàng)建自動化的單元測試,以及在緊急修復(fù)bug時所帶來的額外的復(fù)雜性。根本沒有可能可以對私有成員打補(bǔ)丁。相反地,我們必須覆蓋所有的使用存在bug私有成員的公共方法。開發(fā)者不能簡單的擴(kuò)展私有成員,因此我們需要記得,私有成員并非它們表面上看上去那么具有擴(kuò)展性。

想要了解更深入的信息,可以閱讀 Ben Cherry 這篇精彩的文章。

上一篇:工廠模式下一篇:建造者模式