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

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

模塊化模式

模塊

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

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

  • 模塊化模式
  • 對(duì)象表示法
  • AMD模塊
  • CommonJS 模塊
  • ECMAScript Harmony 模塊

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

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

對(duì)象字面值

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

var myObjectLiteral = {

    variableKey: variableValue,

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

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

var myModule = {

  myProperty: "someValue",

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

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

  // 輸出基于當(dāng)前配置(<span>configuration</span>)的一個(gè)值
  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
});

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

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

模塊化模式

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

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

私有信息

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

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

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

歷史

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

例子

下面這個(gè)例子通過(guò)創(chuàng)建一個(gè)自包含的模塊實(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();

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

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

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 );

    }
  };

})();

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

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)外一個(gè)對(duì)象。這個(gè)自動(dòng)賦值給了basketModule 因此我們可以這樣和這個(gè)對(duì)象交互。

// 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 的名字空間中。

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

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

模塊模式的變體

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

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

// 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)出)

這個(gè)變體允許我們聲明全局對(duì)象而不用使用它們,同樣也支持在下一個(gè)例子中我們將會(huì)看到的全局導(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提供了一個(gè)方便的方法 dojo.setObject() 來(lái)設(shè)置對(duì)象。這需要將以"."符號(hào)為第一個(gè)參數(shù)的分隔符,如:myObj.parent.child 是指定義在"myOjb"內(nèi)部的一個(gè)對(duì)象“parent”,它的一個(gè)屬性為"child"。使用setObject()方法允許我們?cè)O(shè)置children 的值,可以創(chuàng)建路徑傳遞過(guò)程中的任何對(duì)象即使這些它們根本不存在。

例如,如果我們聲明商店命名空間的對(duì)象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()方法的信息,請(qǐng)參閱官方文檔 documentation

ExtJS

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

下面我們可以看到一個(gè)例子關(guān)于如何定義一個(gè)名字空間,然后填入一個(gè)包含有私有和公有API的模塊。除了一些語(yǔ)義上的不同之外,這個(gè)例子和使用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來(lái)實(shí)現(xiàn)模塊模式。下面的例子很大程度上是基于原始由Eric Miraglia實(shí)現(xiàn)的YUI本身的模塊模式,但是和vanillla Javascript 實(shí)現(xiàn)的版本比較起來(lái)差異不是很大。

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

在下面的例子中,定義了一個(gè)library 函數(shù),這個(gè)函數(shù)聲明了一個(gè)新的庫(kù),并且在新的庫(kù)(例如 模塊)創(chuàng)建的時(shí)候,自動(dò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)勢(shì)

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

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

缺點(diǎn)

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

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

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

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

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