<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 0,  comments - 17,  trackbacks - 0
         摘要:   來源:網絡 Struts的核心是struts-config.xml配置文件,在這個文件里描述了所有的Struts組件。 在這里包括配置主要的組件及次要的組件,下面是struts-config.xml包含主要元素的內容: 1.     struts-config.xml的主要元素: <?xml version=”...  閱讀全文
    posted @ 2008-12-16 11:11 xyz 閱讀(502) | 評論 (0)編輯 收藏

    Javascript 閉包

    翻譯:為之漫筆
    鏈接:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html
    英文原版:http://jibbering.com/faq/faq_notes/closures.html

    簡介

    Closure
    所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。

    閉包是 ECMAScript (JavaScript)最強大的特性之一,但用好閉包的前提是必須理解閉包。閉包的創建相對容易,人們甚至會在不經意間創建閉包,但這些無意創建的閉包卻存在潛在的危害,尤其是在比較常見的瀏覽器環境下。如果想要揚長避短地使用閉包這一特性,則必須了解它們的工作機制。而閉包工作機制的實現很大程度上有賴于標識符(或者說對象屬性)解析過程中作用域的角色。

    關于閉包,最簡單的描述就是 ECMAScript 允許使用內部函數--即函數定義和函數表達式位于另一個函數的函數體內。而且,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。也就是說,內部函數會在外部函數返回后被執行。而當這個內部函數執行時,它仍然必需訪問其外部函數的局部變量、參數以及其他內部函數。這些局部變量、參數和函數聲明(最初時)的值是外部函數返回時的值,但也會受到內部函數的影響。

    遺憾的是,要適當地理解閉包就必須理解閉包背后運行的機制,以及許多相關的技術細節。雖然本文的前半部分并沒有涉及 ECMA 262 規范指定的某些算法,但仍然有許多無法回避或簡化的內容。對于個別熟悉對象屬性名解析的人來說,可以跳過相關的內容,但是除非你對閉包也非常熟悉,否則最好是不要跳過下面幾節。

    對象屬性名解析

    ECMAScript 認可兩類對象:原生(Native)對象和宿主(Host)對象,其中宿主對象包含一個被稱為內置對象的原生對象的子類(ECMA 262 3rd Ed Section 4.3)。原生對象屬于語言,而宿主對象由環境提供,比如說可能是文檔對象、DOM 等類似的對象。

    原生對象具有松散和動態的命名屬性(對于某些實現的內置對象子類別而言,動態性是受限的--但這不是太大的問題)。對象的命名屬性用于保存值,該值可以是指向另一個對象(Objects)的引用(在這個意義上說,函數也是對象),也可以是一些基本的數據類型,比如:String、Number、Boolean、Null 或 Undefined。其中比較特殊的是 Undefined 類型,因為可以給對象的屬性指定一個 Undefined 類型的值,而不會刪除對象的相應屬性。而且,該屬性只是保存著 undefined 值。

    下面簡要介紹一下如何設置和讀取對象的屬性值,并最大程度地體現相應的內部細節。

    值的賦予

    對象的命名屬性可以通過為該命名屬性賦值來創建,或重新賦值。即,對于:

    var objectRef = new Object(); //創建一個普通的 javascript 對象。
    

    可以通過下面語句來創建名為 “testNumber” 的屬性:

    objectRef.testNumber = 5;
    /* - 或- */
    objectRef["testNumber"] = 5;
    

    在賦值之前,對象中沒有“testNumber” 屬性,但在賦值后,則創建一個屬性。之后的任何賦值語句都不需要再創建這個屬性,而只會重新設置它的值:

    objectRef.testNumber = 8;
    /* - 或- */
    objectRef["testNumber"] = 8;
    

    稍后我們會介紹,Javascript 對象都有原型(prototypes)屬性,而這些原型本身也是對象,因而也可以帶有命名的屬性。但是,原型對象命名屬性的作用并不體現在賦值階段。同樣,在將值賦給其命名屬性時,如果對象沒有該屬性則會創建該命名屬性,否則會重設該屬性的值。

    值的讀取

    當讀取對象的屬性值時,原型對象的作用便體現出來。如果對象的原型中包含屬性訪問器(property accessor)所使用的屬性名,那么該屬性的值就會返回:

    /* 為命名屬性賦值。如果在賦值前對象沒有相應的屬性,那么賦值后就會得到一個:*/
    objectRef.testNumber = 8;
    /* 從屬性中讀取值 */
    var val = objectRef.testNumber;
    /* 現在, - val - 中保存著剛賦給對象命名屬性的值 8*/
    

    而且,由于所有對象都有原型,而原型本身也是對象,所以原型也可能有原型,這樣就構成了所謂的原型鏈。原型鏈終止于鏈中原型為 null 的對象。Object 構造函數的默認原型就有一個 null 原型,因此:

    var objectRef = new Object(); //創建一個普通的 JavaScript 對象。
    

    創建了一個原型為 Object.prototype 的對象,而該原型自身則擁有一個值為 null 的原型。也就是說,objectRef 的原型鏈中只包含一個對象-- Object.prototype。但對于下面的代碼而言:

    /* 創建 - MyObject1 - 類型對象的函數*/
    function MyObject1(formalParameter){
    /* 給創建的對象添加一個名為 - testNumber -
    的屬性并將傳遞給構造函數的第一個參數指定為該屬性的值:*/
    this.testNumber = formalParameter;
    }
    /* 創建 - MyObject2 - 類型對象的函數*/
    function MyObject2(formalParameter){
    /* 給創建的對象添加一個名為 - testString -
    的屬性并將傳遞給構造函數的第一個參數指定為該屬性的值:*/
    this.testString = formalParameter;
    }
    
    /* 接下來的操作用 MyObject1 類的實例替換了所有與 MyObject2
    類的實例相關聯的原型。而且,為 MyObject1 構造函數傳遞了參數
    - 8 - ,因而其 - testNumber - 屬性被賦予該值:*/
    MyObject2.prototype = new MyObject1( 8 );
    
    /* 最后,將一個字符串作為構造函數的第一個參數,
    創建一個 - MyObject2 - 的實例,并將指向該對象的
    引用賦給變量 - objectRef - :*/
    var objectRef = new MyObject2( "String_Value" );
    

    被變量 objectRef 所引用的 MyObject2 的實例擁有一個原型鏈。該鏈中的第一個對象是在創建后被指定給 MyObject2 構造函數的 prototype 屬性的 MyObject1 的一個實例。MyObject1 的實例也有一個原型,即與 Object.prototype 所引用的對象對應的默認的 Object 對象的原型。最后, Object.prototype 有一個值為 null 的原型,因此這條原型鏈到此結束。

    當某個屬性訪問器嘗試讀取由 objectRef 所引用的對象的屬性值時,整個原型鏈都會被搜索。在下面這種簡單的情況下:

    var val = objectRef.testString;
    

    因為 objectRef 所引用的 MyObject2 的實例有一個名為“testString”的屬性,因此被設置為“String_Value”的該屬性的值被賦給了變量 val。但是:

    var val = objectRef.testNumber;
    

    則不能從 MyObject2 實例自身中讀取到相應的命名屬性值,因為該實例沒有這個屬性。然而,變量 val 的值仍然被設置為 8,而不是未定義--這是因為在該實例中查找相應的命名屬性失敗后,解釋程序會繼續檢查其原型對象。而該實例的原型對象是 MyObject1 的實例,這個實例有一個名為“testNumber”的屬性并且值為 8,所以這個屬性訪問器最后會取得值 8。而且,雖然 MyObject1MyObject2 都沒有定義 toString 方法,但是當屬性訪問器通過 objectRef 讀取 toString 屬性的值時:

    var val = objectRef.toString;
    

    變量 val 也會被賦予一個函數的引用。這個函數就是在 Object.prototypetoString 屬性中所保存的函數。之所以會返回這個函數,是因為發生了搜索objectRef 原型鏈的過程。當在作為對象的 objectRef 中發現沒有“toString”屬性存在時,會搜索其原型對象,而當原型對象中不存在該屬性時,則會繼續搜索原型的原型。而原型鏈中最終的原型是 Object.prototype,這個對象確實有一個 toString 方法,因此該方法的引用被返回。

    最后:

    var val = objectRef.madeUpProperty;
    

    返回 undefined,因為在搜索原型鏈的過程中,直至 Object.prototype 的原型--null,都沒有找到任何對象有名為“madeUpPeoperty”的屬性,因此最終返回 undefined

    不論是在對象或對象的原型中,讀取命名屬性值的時候只返回首先找到的屬性值。而當為對象的命名屬性賦值時,如果對象自身不存在該屬性則創建相應的屬性。

    這意味著,如果執行像 objectRef.testNumber = 3 這樣一條賦值語句,那么這個 MyObject2 的實例自身也會創建一個名為“testNumber”的屬性,而之后任何讀取該命名屬性的嘗試都將獲得相同的新值。這時候,屬性訪問器不會再進一步搜索原型鏈,但 MyObject1 實例值為 8 的“testNumber”屬性并沒有被修改。給 objectRef 對象的賦值只是遮擋了其原型鏈中相應的屬性。

    注意:ECMAScript 為 Object 類型定義了一個內部 [[prototype]] 屬性。這個屬性不能通過腳本直接訪問,但在屬性訪問器解析過程中,則需要用到這個內部 [[prototype]] 屬性所引用的對象鏈--即原型鏈。可以通過一個公共的 prototype 屬性,來對與內部的 [[prototype]] 屬性對應的原型對象進行賦值或定義。這兩者之間的關系在 ECMA 262(3rd edition)中有詳細描述,但超出了本文要討論的范疇。

    標識符解析、執行環境和作用域鏈

    執行環境

    執行環境是 ECMAScript 規范(ECMA 262 第 3 版)用于定義 ECMAScript 實現必要行為的一個抽象的概念。對如何實現執行環境,規范沒有作規定。但由于執行環境中包含引用規范所定義結構的相關屬性,因此執行環境中應該保有(甚至實現)帶有屬性的對象--即使屬性不是公共屬性。

    所有 JavaScript 代碼都是在一個執行環境中被執行的。全局代碼(作為內置的 JS 文件執行的代碼,或者 HTML 頁面加載的代碼)是在我將稱之為“全局執行環境”的執行環境中執行的,而對函數的每次調用(有可能是作為構造函數)同樣有關聯的執行環境。通過 eval 函數執行的代碼也有截然不同的執行環境,但因為 JavaScript 程序員在正常情況下一般不會使用 eval,所以這里不作討論。有關執行環境的詳細說明請參閱 ECMA 262(第 3 版)第 10.2 節。

    當調用一個 JavaScript 函數時,該函數就會進入相應的執行環境。如果又調用了另外一個函數(或者遞歸地調用同一個函數),則又會創建一個新的執行環境,并且在函數調用期間執行過程都處于該環境中。當調用的函數返回后,執行過程會返回原始執行環境。因而,運行中的 JavaScript 代碼就構成了一個執行環境棧。

    在創建執行環境的過程中,會按照定義的先后順序完成一系列操作。首先,在一個函數的執行環境中,會創建一個“活動”對象。活動對象是規范中規定的另外一種機制。之所以稱之為對象,是因為它擁有可訪問的命名屬性,但是它又不像正常對象那樣具有原型(至少沒有預定義的原型),而且不能通過 JavaScript 代碼直接引用活動對象。

    為函數調用創建執行環境的下一步是創建一個 arguments 對象,這是一個類似數組的對象,它以整數索引的數組成員一一對應地保存著調用函數時所傳遞的參數。這個對象也有 lengthcallee 屬性(這兩個屬性與我們討論的內容無關,詳見規范)。然后,會為活動對象創建一個名為“arguments”的屬性,該屬性引用前面創建的 arguments 對象。

    接著,為執行環境分配作用域。作用域由對象列表(鏈)組成。每個函數對象都有一個內部的 [[scope]] 屬性(該屬性我們稍后會詳細介紹),這個屬性也由對象列表(鏈)組成。指定給一個函數調用執行環境的作用域,由該函數對象的 [[scope]] 屬性所引用的對象列表(鏈)組成,同時,活動對象被添加到該對象列表的頂部(鏈的前端)。

    之后會發生由 ECMA 262 中所謂“可變”對象完成的“變量實例化”的過程。只不過此時使用活動對象作為可變對象(這里很重要,請注意:它們是同一個對象)。此時會將函數的形式參數創建為可變對象命名屬性,如果調用函數時傳遞的參數與形式參數一致,則將相應參數的值賦給這些命名屬性(否則,會給命名屬性賦 undefined 值)。對于定義的內部函數,會以其聲明時所用名稱為可變對象創建同名屬性,而相應的內部函數則被創建為函數對象并指定給該屬性。變量實例化的最后一步是將在函數內部聲明的所有局部變量創建為可變對象的命名屬性。

    根據聲明的局部變量創建的可變對象的屬性在變量實例化過程會被賦予 undefined 值。在執行函數體內的代碼、并計算相應的賦值表達式之前不會對局部變量執行真正的實例化。

    事實上,擁有 arguments 屬性的活動對象和擁有與函數局部變量對應的命名屬性的可變對象是同一個對象。因此,可以將標識符 arguments 作為函數的局部變量來看待。

    回到頂部

    最后,在this可以被使用之前,還必須先對其賦值。如果賦的值是一個對象的引用,則 this.m 訪問的便是該對象上的 m。如果(內部)賦的值是 null,則this就指向全局對象。 (此段由 pangba 劉未鵬 翻譯)

    (原文備考:Finally a value is assigned for use with the this keyword. If the value assigned refers to an object then property accessors prefixed with the this keyword reference properties of that object. If the value assigned (internally) is null then the this keyword will refer to the global object. )

    創建全局執行環境的過程會稍有不同,因為它沒有參數,所以不需要通過定義的活動對象來引用這些參數。但全局執行環境也需要一個作用域,而它的作用域鏈實際上只由一個對象--全局對象--組成。全局執行環境也會有變量實例化的過程,它的內部函數就是涉及大部分 JavaScript 代碼的、常規的頂級函數聲明。而且,在變量實例化過程中全局對象就是可變對象,這就是為什么全局性聲明的函數是全局對象屬性的原因。全局性聲明的變量同樣如此。

    全局執行環境也會使用 this 對象來引用全局對象。

    作用域鏈與 [[scope]]

    調用函數時創建的執行環境會包含一個作用域鏈,這個作用域鏈是通過將該執行環境的活動(可變)對象添加到保存于所調用函數對象的 [[scope]] 屬性中的作用域鏈前端而構成的。所以,理解函數對象內部的 [[scope]] 屬性的定義過程至關重要。

    在 ECMAScript 中,函數也是對象。函數對象在變量實例化過程中會根據函數聲明來創建,或者是在計算函數表達式或調用 Function 構造函數時創建。

    通過調用 Function 構造函數創建的函數對象,其內部的 [[scope]] 屬性引用的作用域鏈中始終只包含全局對象。

    通過函數聲明或函數表達式創建的函數對象,其內部的 [[scope]] 屬性引用的則是創建它們的執行環境的作用域鏈。

    在最簡單的情況下,比如聲明如下全局函數:-

    function exampleFunction(formalParameter){
    ...   // 函數體內的代碼
    }
    

    當為創建全局執行環境而進行變量實例化時,會根據上面的函數聲明創建相應的函數對象。因為全局執行環境的作用域鏈中只包含全局對象,所以它就給自己創建的、并以名為“exampleFunction”的屬性引用的這個函數對象的內部 [[scope]] 屬性,賦予了只包含全局對象的作用域鏈。

    當在全局環境中計算函數表達式時,也會發生類似的指定作用域鏈的過程:-

    var exampleFuncRef = function(){
    ...   // 函數體代碼
    }
    

    在這種情況下,不同的是在全局執行環境的變量實例化過程中,會先為全局對象創建一個命名屬性。而在計算賦值語句之前,暫時不會創建函數對象,也不會將該函數對象的引用指定給全局對象的命名屬性。但是,最終還是會在全局執行環境中創建這個函數對象(當計算函數表達式時。譯者注),而為這個創建的函數對象的 [[scope]] 屬性指定的作用域鏈中仍然只包含全局對象。

    內部的函數聲明或表達式會導致在包含它們的外部函數的執行環境中創建相應的函數對象,因此這些函數對象的作用域鏈會稍微復雜一些。在下面的代碼中,先定義了一個帶有內部函數聲明的外部函數,然后調用外部函數:

    function exampleOuterFunction(formalParameter){
    function exampleInnerFuncitonDec(){
    ... // 內部函數體代碼
    }
    ...  // 其余的外部函數體代碼
    }
    exampleOuterFunction( 5 );
    

    與外部函數聲明對應的函數對象會在全局執行環境的變量實例化過程中被創建。因此,外部函數對象的 [[scope]] 屬性中會包含一個只有全局對象的“單項目”作用域鏈。

    當在全局執行環境中調用 exampleOuterFunction 函數時,會為該函數調用創建一個新的執行環境和一個活動(可變)對象。這個新執行環境的作用域就由新的活動對象后跟外部函數對象的 [[scope]] 屬性所引用的作用域鏈(只有全局對象)構成。在新執行環境的變量實例化過程中,會創建一個與內部函數聲明對應的函數對象,而同時會給這個函數對象的 [[scope]] 屬性指定創建該函數對象的執行環境(即新執行環境。譯者注)的作用域值--即一個包含活動對象后跟全局對象的作用域鏈。

    到目前為止,所有過程都是自動、或者由源代碼的結構所控制的。但我們發現,執行環境的作用域鏈定義了執行環境所創建的函數對象的 [[scope]] 屬性,而函數對象的 [[scope]] 屬性則定義了它的執行環境的作用域(包括相應的活動對象)。不過,ECMAScript 也提供了用于修改作用域鏈 with 語句。

    with 語句會計算一個表達式,如果該表達式是一個對象,那么就將這個對象添加到當前執行環境的作用域鏈中(在活動<可變>對象之前)。然后,執行 with 語句(它自身也可能是一個語句塊)中的其他語句。之后,又恢復到調用它之前的執行環境的作用域鏈中。

    with 語句不會影響在變量實例化過程中根據函數聲明創建函數對象。但是,可以在一個 with 語句內部對函數表達式求值:-

    /* 創建全局變量 - y - 它引用一個對象:- */
    var y = {x:5}; // 帶有一個屬性 - x - 的對象直接量
    function exampleFuncWith(){
    var z;
    /* 將全局對象 - y - 引用的對象添加到作用域鏈的前端:- */
    with(y){
    /* 對函數表達式求值,以創建函數對象并將該函數對象的引用指定給局部變量 - z - :-  */
    z = function(){
    ... // 內部函數表達式中的代碼;
    }
    }
    ...
    }
    /* 執行 - exampleFuncWith - 函數:- */
    exampleFuncWith();
    

    在調用 exampleFuncWith 函數所創建的執行環境中包含一個由其活動對象后跟全局對象構成的作用域鏈。而在執行 with 語句時,又會把全局變量 y 引用的對象添加到這個作用域鏈的前端。在對其中的函數表達式求值的過程中,所創建函數對象的 [[scope]] 屬性與創建它的執行環境的作用域保持一致--即,該屬性會引用一個由對象 y 后跟調用外部函數時所創建執行環境的活動對象,后跟全局對象的作用域鏈。

    當與 with 語句相關的語句塊執行結束時,執行環境的作用域得以恢復(y 會被移除),但是已經創建的函數對象(z。譯者注)的 [[scope]] 屬性所引用的作用域鏈中位于最前面的仍然是對象 y

    標識符解析

    標識符是沿作用域鏈逆向解析的。ECMA 262 將 this 歸類為關鍵字而不是標識符,并非不合理。因為解析 this 值時始終要根據使用它的執行環境來判斷,而與作用域鏈無關。

    標識符解析從作用域鏈中的第一個對象開始。檢查該對象中是否包含與標識符對應的屬性名。因為作用域鏈是一條對象鏈,所以這個檢查過程也會包含相應對象的原型鏈(如果有)。如果沒有在作用域鏈的第一個對象中發現相應的值,解析過程會繼續搜索下一個對象。這樣依次類推直至找到作用域鏈中包含以標識符為屬性名的對象為止,也有可能在作用域鏈的所有對象中都沒有發現該標識符。

    當基于對象使用屬性訪問器時,也會發生與上面相同的標識符解析過程。當屬性訪問器中有相應的屬性可以替換某個對象時,這個屬性就成為表示該對象的標識符,該對象在作用域鏈中的位置進而被確定。全局對象始終都位于作用域鏈的尾端。

    因為與函數調用相關的執行環境將會把活動(可變)對象添加到作用域鏈的前端,所以在函數體內使用的標識符會首先檢查自己是否與形式參數、內部函數聲明的名稱或局部變量一致。這些都可以由活動(可變)對象的命名屬性來確定。

    閉包

    自動垃圾收集

    ECMAScript 要求使用自動垃圾收集機制。但規范中并沒有詳細說明相關的細節,而是留給了實現來決定。但據了解,相當一部分實現對它們的垃圾收集操作只賦予了很低的優先級。但是,大致的思想都是相同的,即如果對象不再“可引用(由于不存在對它的引用,使執行代碼無法再訪問到它)”時,該對象就成為垃圾收集的目標。因而,在將來的某個時刻會將這個對象銷毀并將它所占用的一切資源釋放,以便操作系統重新利用。

    正常情況下,當退出一個執行環境時就會滿足類似的條件。此時,作用域鏈結構中的活動(可變)對象以及在該執行環境中創建的任何對象--包括函數對象,都不再“可引用”,因此將成為垃圾收集的目標。

    構成閉包

    閉包是通過在對一個函數調用的執行環境中返回一個函數對象構成的。比如,在對函數調用的過程中,將一個對內部函數對象的引用指定給另一個對象的屬性。或者,直接將這樣一個(內部)函數對象的引用指定給一個全局變量、或者一個全局性對象的屬性,或者一個作為參數以引用方式傳遞給外部函數的對象。例如:-

    function exampleClosureForm(arg1, arg2){
    var localVar = 8;
    function exampleReturned(innerArg){
    return ((arg1 + arg2)/(innerArg + localVar));
    }
    /* 返回一個定義為 exampleReturned 的內部函數的引用 -:-  */
    return exampleReturned;
    }
    var globalVar = exampleClosureForm(2, 4);
    

    這種情況下,在調用外部函數 exampleClosureForm 的執行環境中所創建的函數對象就不會被當作垃圾收集,因為該函數對象被一個全局變量所引用,而且仍然是可以訪問的,甚至可以通過 globalVar(n) 來執行。

    的確,情況比正常的時候要復雜一些。因為現在這個被變量 globalVar 引用的內部函數對象的 [[scope]] 屬性所引用的作用域鏈中,包含著屬于創建該內部函數對象的執行環境的活動對象(和全局對象)。由于在執行被 globalVar 引用的函數對象時,每次都要把該函數對象的 [[scope]] 屬性所引用的整個作用域鏈添加到創建的(內部函數的)執行環境的作用域中(即此時的作用域中包括:內部執行環境的活動對象、外部執行環境的活動對象、全局對象。譯者注), 所以這個(外部執行環境的)活動對象不會被當作垃圾收集。

    閉包因此而構成。此時,內部函數對象擁有自由的變量,而位于該函數作用域鏈中的活動(可變)對象則成為與變量綁定的環境。

    由于活動(可變)對象受限于內部函數對象(現在被 globalVar 變量引用)的 [[scope]] 屬性中作用域鏈的引用,所以活動對象連同它的變量聲明--即屬性的值,都會被保留。而在對內部函數調用的執行環境中進行作用域解析時,將會把與活動(可變)對象的命名屬性一致的標識符作為該對象的屬性來解析。活動對象的這些屬性值即使是在創建它的執行環境退出后,仍然可以被讀取和設置。

    在上面的例子中,當外部函數返回(退出它的執行環境)時,其活動(可變)對象的變量聲明中記錄了形式參數、內部函數定義以及局部變量的值。arg1 屬性的值為 2,而 arg2 屬性的值為 4localVar 的值是 8,還有一個 exampleReturned 屬性,它引用由外部函數返回的內部函數對象。(為方便起見,我們將在后面的討論中,稱這個活動<可變>對象為 "ActOuter1")。

    如果再次調用 exampleClosureForm 函數,如:-

    var secondGlobalVar = exampleClosureForm(12, 3);
    

    - 則會創建一個新的執行環境和一個新的活動對象。而且,會返回一個新的函數對象,該函數對象的 [[scope]] 屬性引用的作用域鏈與前一次不同,因為這一次的作用域鏈中包含著第二個執行環境的活動對象,而這個活動對象的屬性 arg1 值為 12 而屬性 arg2 值為 3。(為方便起見,我們將在后面的討論中,稱這個活動<可變>對象為 "ActOuter2")。

    通過第二次執行 exampleClosureForm 函數,第二個、也是截然不同的閉包誕生了。

    通過執行 exampleClosureForm 創建的兩個函數對象分別被指定給了全局變量 globalVarsecondGlobalVar,并返回了表達式 ((arg1 + arg2)/(innerArg + localVar))。該表達式對其中的四個標識符應用了不同的操作符。如何確定這些標識符的值是體現閉包價值的關鍵所在。

    我們來看一看,在執行由 globalVar 引用的函數對象--如 globalVar(2)--時的情形。此時,會創建一個新的執行環境和相應的活動對象(我們將稱之為“ActInner1”),并把該活動對象添加到執行的函數對象的 [[scope]] 屬性所引用的作用域鏈的前端。ActInner1 會帶有一個屬性 innerArg,根據傳遞的形式參數,其值被指定為 2。這個新執行環境的作用域鏈變成: ActInner1->ActOuter1->全局對象.

    為了返回表達式 ((arg1 + arg2)/(innerArg + localVar)) 的值,要沿著作用域鏈進行標識符解析。表達式中標識符的值將通過依次查找作用域鏈中每個對象(與標識符名稱一致)的屬性來確定。

    作用域鏈中的第一個對象是 ActInner1,它有一個名為 innerArg 的屬性,值是 2。所有其他三個標識符在 ActOuter1 中都有對應的屬性:arg12arg24localVar8。最后,函數調用返回 ((2 + 2)/(2 + 8))

    現在再來看一看由 secondGlobalVar 引用的同一個函數對象的執行情況,比如 secondGlobalVar(5)。我們把這次創建的新執行環境的活動對象稱為 “ActInner2”,相應的作用域鏈就變成了:ActInner2->ActOuter2->全局對象。ActInner2 返回 innerArg 的值 5,而 ActOuter2 分別返回 arg1arg2localVar 的值 1238。函數調用返回的值就是 ((12 + 3)/(5 + 8))

    如果再執行一次 secondGlobalVar,則又會有一個新活動對象被添加到作用域鏈的前端,但 ActOuter2 仍然是鏈中的第二個對象,而他的命名屬性會再次用于完成標識符 arg1arg2localVar 的解析。

    這就是 ECMAScript 的內部函數獲取、維持和訪問創建他們的執行環境的形式參數、聲明的內部函數以及局部變量的過程。這個過程說明了構成閉包以后,內部的函數對象在其存續過程中,如何維持對這些值的引用、如何對這些值進行讀取的機制。即,創建內部函數對象的執行環境的活動(可變)對象,會保留在該函數對象的 [[scope]] 屬性所引用的作用域鏈中。直到所有對這個內部函數的引用被釋放,這個函數對象才會成為垃圾收集的目標(連同它的作用域鏈中任何不再需要的對象)。

    內部函數自身也可能有內部函數。在通過函數執行返回內部函數構成閉包以后,相應的閉包自身也可能會返回內部函數從而構成它們自己的閉包。每次作用域鏈嵌套,都會增加由創建內部函數對象的執行環境引發的新活動對象。ECMAScript 規范要求作用域鏈是臨時性的,但對作用域鏈的長度卻沒有加以限制。在具體實現中,可能會存在實際的限制,但還沒有發現有具體限制數量的報告。目前來看,嵌套的內部函數所擁有的潛能,仍然超出了使用它們的人的想像能力。

    通過閉包可以做什么?

    對這個問題的回答可能會令你驚訝--閉包什么都可以做。據我所知,閉包使得 ECMAScript 能夠模仿任何事物,因此局限性在于設計和實現要模仿事物的能力。只是從字面上看可能會覺得這么說很深奧,下面我們就來看一些更有實際意義的例子。

    例 1:為函數引用設置延時

    閉包的一個常見用法是在執行函數之前為要執行的函數提供參數。例如:將函數作為 setTimout 函數的第一個參數,這在 Web 瀏覽器的環境下是非常常見的一種應用。

    setTimeout 用于有計劃地執行一個函數(或者一串 JavaScript 代碼,不是在本例中),要執行的函數是其第一個參數,其第二個參數是以毫秒表示的執行間隔。也就是說,當在一段代碼中使用 setTimeout 時,要將一個函數的引用作為它的第一個參數,而將以毫秒表示的時間值作為第二個參數。但是,傳遞函數引用的同時無法為計劃執行的函數提供參數。

    然而,可以在代碼中調用另外一個函數,由它返回一個對內部函數的引用,再把這個對內部函數對象的引用傳遞給 setTimeout 函數。執行這個內部函數時要使用的參數在調用返回它的外部函數時傳遞。這樣,setTimeout 在執行這個內部函數時,不用傳遞參數,但該內部函數仍然能夠訪問在調用返回它的外部函數時傳遞的參數:

    function callLater(paramA, paramB, paramC){
    /* 返回一個由函數表達式創建的匿名內部函數的引用:- */
    
    return (function(){
    /* 這個內部函數將通過 - setTimeout - 執行,
    而且當它執行時它會讀取并按照傳遞給
    外部函數的參數行事:
    */
    paramA[paramB] = paramC;
    });
    }
    ...
    /* 調用這個函數將返回一個在其執行環境中創建的內部函數對象的引用。
    傳遞的參數最終將作為外部函數的參數被內部函數使用。
    返回的對內部函數的引用被賦給一個全局變量:-
    */
    
    var functRef = callLater(elStyle, "display", "none");
    /* 調用 setTimeout 函數,將賦給變量 - functRef -
    的內部函數的引用作為傳遞的第一個參數:- */
    
    hideMenu=setTimeout(functRef, 500);
    

    例 2: 通過對象實例方法關聯函數

    回到頂部

    許多時候我們需要將一個函數對象暫時掛到一個引用上留待后面執行,因為不等到執行的時候是很難知道其具體參數的,而先前將它賦給那個引用的時候更是壓根不知道的。 (此段由 pangba 劉未鵬 翻譯)

    (luyy朋友的翻譯_2008-7-7更新)很多時候需要將一個函數引用進行賦值,以便在將來某個時候執行該函數,在執行這些函數時給函數提供參數將會是有用處的,但這些參數在執行時不容易獲得,他們只有在上面賦值給時才能確定。

    (原文備考:There are many other circumstances when a reference to a function object is assigned so that it would be executed at some future time where it is useful to provide parameters for the execution of that function that would not be easily available at the time of execution but cannot be known until the moment of assignment.)

    一個相關的例子是,用 JavaScript 對象來封裝與特定 DOM 元素的交互。這個 JavaScript 對象具有 doOnClickdoMouseOverdoMouseOut 方法,并且當用戶在該特定的 DOM 元素中觸發了相應的事件時要執行這些方法。不過,可能會創建與不同的 DOM 元素關聯的任意數量的 JavaScript 對象,而且每個對象實例并不知道實例化它們的代碼將會如何操縱它們(即注冊事件處理函數與定義相應的事件處理函數分離。譯者注)。這些對象實例并不知道如何在全局環境中引用它們自身,因為它們不知道將會指定哪個全局變量(如果有)引用它們的實例。

    因而問題可以歸結為執行一個與特定的 JavaScript 對象關聯的事件處理函數,并且要知道調用該對象的哪個方法。

    下面這個例子使用了一個基于閉包構建的一般化的函數(此句多謝未鵬指點),該函數會將對象實例與 DOM 元素事件關聯起來,安排執行事件處理程序時調用對象實例的指定方法,給象的指定方法傳遞的參數是事件對象和與元素關聯的引用,該函數返回執行相應方法后的返回值。

    /* 一個關聯對象實例和事件處理器的函數。
    它返回的內部函數被用作事件處理器。對象實例以 - obj - 參數表示,
    而在該對象實例中調用的方法名則以 - methodName - (字符串)參數表示。
    */
    
    function associateObjWithEvent(obj, methodName){
    /* 下面這個返回的內部函數將作為一個 DOM 元素的事件處理器*/
    
    return (function(e){
     /* 在支持標準 DOM 規范的瀏覽器中,事件對象會被解析為參數 - e - ,
    若沒有正常解析,則使用 IE 的事件對象來規范化事件對象。
    */
    
    e = e||window.event;
    /* 事件處理器通過保存在字符串 - methodName - 中的方法名調用了對象
    - obj - 的一個方法。并傳遞已經規范化的事件對象和觸發事件處理器的元素
    的引用 - this - (之所以 this 有效是因為這個內部函數是作為該元素的方法執行的)
    */
    
    return obj[methodName](e, this);
    });
    }
    /* 這個構造函數用于創建將自身與 DOM 元素關聯的對象,
    DOM 元素的 ID 作為構造函數的字符串參數。
    所創建的對象會在相應的元素觸發 onclick、
    onmouseover 或 onmouseout 事件時,
    調用相應的方法。
    */
    
    function DhtmlObject(elementId){
    /* 調用一個返回 DOM 元素(如果沒找到返回 null)引用的函數,
    必需的參數是 ID。 將返回的值賦給局部變量 - el -。
    */ 
    var el = getElementWithId(elementId);
     /* - el - 值會在內部通過類型轉換變為布爾值,以便 - if - 語句加以判斷。
    因此,如果它引用一個對象結果將返回 true,如果是 null 則返回 false。
    下面的代碼塊只有當 - el - 變量返回一個 DOM 元素時才會被執行。
    */
    if(el){
    /* 為給元素的事件處理器指定一個函數,該對象調用了
    - associateObjWithEvent - 函數。
    同時對象將自身(通過 - this - 關鍵字)作為調用方法的對象,
    并提供了調用的方法名稱。 - associateObjWithEvent - 函數會返回
    一個內部函數,該內部函數被指定為 DOM 元素的事件處理器。
    在響應事件時,執行這個內部函數就會調用必要的方法。
    */
    el.onclick = associateObjWithEvent(this, "doOnClick");
    el.onmouseover = associateObjWithEvent(this, "doMouseOver");
    el.onmouseout = associateObjWithEvent(this, "doMouseOut");
    ...
    }
    }
    DhtmlObject.prototype.doOnClick = function(event, element){
    ... // doOnClick 方法體。.
    }
    DhtmlObject.prototype.doMouseOver = function(event, element){
    ... // doMouseOver 方法體。
    }
    DhtmlObject.prototype.doMouseOut = function(event, element){
    ... // doMouseOut 方法體。
    }
    

    這樣,DhtmlObject 的任何實例都會將自身與相應的 DOM 元素關聯起來,而這些 DOM 元素不必知道其他代碼如何操縱它們(即當觸發相應事件時,會執行什么代碼。譯者注),也不必理會全局命名空間的影響以及與 DhtmlObject 的其他實例間存在沖突的危險。

    例 3:包裝相關的功能

    閉包可以用于創建額外的作用域,通過該作用域可以將相關的和具有依賴性的代碼組織起來,以便將意外交互的風險降到最低。假設有一個用于構建字符串的函數,為了避免重復性的連接操作(和創建眾多的中間字符串),我們的愿望是使用一個數組按順序來存儲字符串的各個部分,然后再使用 Array.prototype.join 方法(以空字符串作為其參數)輸出結果。這個數組將作為輸出的緩沖器,但是將數組作為函數的局部變量又會導致在每次調用函數時都重新創建一個新數組,這在每次調用函數時只重新指定數組中的可變內容的情況下并不是必要的。

    一種解決方案是將這個數組聲明為全局變量,這樣就可以重用這個數組,而不必每次都建立新數組。但這個方案的結果是,除了引用函數的全局變量會使用這個緩沖數組外,還會多出一個全局屬性引用數組自身。如此不僅使代碼變得不容易管理,而且,如果要在其他地方使用這個數組時,開發者必須要再次定義函數和數組。這樣一來,也使得代碼不容易與其他代碼整合,因為此時不僅要保證所使用的函數名在全局命名空間中是唯一的,而且還要保證函數所依賴的數組在全局命名空間中也必須是唯一的。

    而通過閉包可以使作為緩沖器的數組與依賴它的函數關聯起來(優雅地打包),同時也能夠維持在全局命名空間外指定的緩沖數組的屬性名,免除了名稱沖突和意外交互的危險。

    其中的關鍵技巧在于通過執行一個單行(in-line)函數表達式創建一個額外的執行環境,而將該函數表達式返回的內部函數作為在外部代碼中使用的函數。此時,緩沖數組被定義為函數表達式的一個局部變量。這個函數表達式只需執行一次,而數組也只需創建一次,就可以供依賴它的函數重復使用。

    下面的代碼定義了一個函數,這個函數用于返回一個 HTML 字符串,其中大部分內容都是常量,但這些常量字符序列中需要穿插一些可變的信息,而可變的信息由調用函數時傳遞的參數提供。

    通過執行單行函數表達式返回一個內部函數,并將返回的函數賦給一個全局變量,因此這個函數也可以稱為全局函數。而緩沖數組被定義為外部函數表達式的一個局部變量。它不會暴露在全局命名空間中,而且無論什么時候調用依賴它的函數都不需要重新創建這個數組。

    /* 聲明一個全局變量 - getImgInPositionedDivHtml -
    并將一次調用一個外部函數表達式返回的內部函數賦給它。
    這個內部函數會返回一個用于表示絕對定位的 DIV 元素
    包圍著一個 IMG 元素 的 HTML 字符串,這樣一來,
    所有可變的屬性值都由調用該函數時的參數提供:
    */
    var getImgInPositionedDivHtml = (function(){
    /* 外部函數表達式的局部變量 - buffAr - 保存著緩沖數組。
    這個數組只會被創建一次,生成的數組實例對內部函數而言永遠是可用的
    因此,可供每次調用這個內部函數時使用。
    其中的空字符串用作數據占位符,相應的數據
    將由內部函數插入到這個數組中:
    */
    var buffAr = [
    '<div id="',
    '',   //index 1, DIV ID 屬性
    '" style="position:absolute;top:',
    '',   //index 3, DIV 頂部位置
    'px;left:',
    '',   //index 5, DIV 左端位置
    'px;width:',
    '',   //index 7, DIV 寬度
    'px;height:',
    '',   //index 9, DIV 高度
    'px;overflow:hidden;\"><img src=\"',
    '',   //index 11, IMG URL
    '\" width=\"',
    '',   //index 13, IMG 寬度
    '\" height=\"',
    '',   //index 15, IMG 調蓄
    '\" alt=\"',
    '',   //index 17, IMG alt 文本內容
    '\"><\/div>'
    ];
    /* 返回作為對函數表達式求值后結果的內部函數對象。
    這個內部函數就是每次調用執行的函數
    - getImgInPositionedDivHtml( ... ) -
    */
    return (function(url, id, width, height, top, left, altText){
    /* 將不同的參數插入到緩沖數組相應的位置:
    */
    buffAr[1] = id;
    buffAr[3] = top;
    buffAr[5] = left;
    buffAr[13] = (buffAr[7] = width);
    buffAr[15] = (buffAr[9] = height);
    buffAr[11] = url;
    buffAr[17] = altText;
    /* 返回通過使用空字符串(相當于將數組元素連接起來)
    連接數組每個元素后形成的字符串:
    */
    return buffAr.join('');
    }); //:內部函數表達式結束。
    })();
    /*^^- :單行外部函數表達式。*/
    

    如果一個函數依賴于另一(或多)個其他函數,而其他函數又沒有必要被其他代碼直接調用,那么可以運用相同的技術來包裝這些函數,而通過一個公開暴露的函數來調用它們。這樣,就將一個復雜的多函數處理過程封裝成了一個具有移植性的代碼單元。

    其他例子

    有關閉包的一個可能是最廣為人知的應用是 Douglas Crockford's technique for the emulation of private instance variables in ECMAScript objects。這種應用方式可以擴展到各種嵌套包含的可訪問性(或可見性)的作用域結構,包括 the emulation of private static members for ECMAScript objects

    閉包可能的用途是無限的,可能理解其工作原理才是把握如何使用它的最好指南。

    意外的閉包

    在創建可訪問的內部函數的函數體之外解析該內部函數就會構成閉包。這表明閉包很容易創建,但這樣一來可能會導致一種結果,即沒有認識到閉包是一種語言特性的 JavaScript 作者,會按照內部函數能完成多種任務的想法來使用內部函數。但他們對使用內部函數的結果并不明了,而且根本意識不到創建了閉包,或者那樣做意味著什么。

    正如下一節談到 IE 中內存泄漏問題時所提及的,意外創建的閉包可能導致嚴重的負面效應,而且也會影響到代碼的性能。問題不在于閉包本身,如果能夠真正做到謹慎地使用它們,反而會有助于創建高效的代碼。換句話說,使用內部函數會影響到效率。

    使用內部函數最常見的一種情況就是將其作為 DOM 元素的事件處理器。例如,下面的代碼用于向一個鏈接元素添加 onclick 事件處理器:

    /* 定義一個全局變量,通過下面的函數將它的值
    作為查詢字符串的一部分添加到鏈接的 - href - 中:
    */
    var quantaty = 5;
    /* 當給這個函數傳遞一個鏈接(作為函數中的參數 - linkRef -)時,
    會將一個 onclick 事件處理器指定給該鏈接,該事件處理器
    將全局變量 - quantaty - 的值作為字符串添加到鏈接的 - href -
    屬性中,然后返回 true 使該鏈接在單擊后定位到由  - href -
    屬性包含的查詢字符串指定的資源:
    */
    function addGlobalQueryOnClick(linkRef){
    /* 如果可以將參數 - linkRef - 通過類型轉換為 ture
    (說明它引用了一個對象):
    */
    if(linkRef){
    /* 對一個函數表達式求值,并將對該函數對象的引用
    指定給這個鏈接元素的 onclick 事件處理器:
    */
    linkRef.onclick = function(){
    /* 這個內部函數表達式將查詢字符串
    添加到附加事件處理器的元素的 - href - 屬性中:
    */
    this.href += ('?quantaty='+escape(quantaty));
    return true;
    };
    }
    }
    

    無論什么時候調用 addGlobalQueryOnClick 函數,都會創建一個新的內部函數(通過賦值構成了閉包)。從效率的角度上看,如果只是調用一兩次 addGlobalQueryOnClick 函數并沒有什么大的妨礙,但如果頻繁使用該函數,就會導致創建許多截然不同的函數對象(每對內部函數表達式求一次值,就會產生一個新的函數對象)。

    上面例子中的代碼沒有關注內部函數在創建它的函數外部可以訪問(或者說構成了閉包)這一事實。實際上,同樣的效果可以通過另一種方式來完成。即單獨地定義一個用于事件處理器的函數,然后將該函數的引用指定給元素的事件處理屬性。這樣,只需創建一個函數對象,而所有使用相同事件處理器的元素都可以共享對這個函數的引用:

    /* 定義一個全局變量,通過下面的函數將它的值
    作為查詢字符串的一部分添加到鏈接的 - href - 中:
    */
    var quantaty = 5;
    /* 當把一個鏈接(作為函數中的參數 - linkRef -)傳遞給這個函數時,
    會給這個鏈接添加一個 onclick 事件處理器,該事件處理器會
    將全局變量  - quantaty - 的值作為查詢字符串的一部分添加到
    鏈接的 - href -  中,然后返回 true,以便單擊鏈接時定位到由
    作為 - href - 屬性值的查詢字符串所指定的資源:
    */
    function addGlobalQueryOnClick(linkRef){
    /* 如果 - linkRef - 參數能夠通過類型轉換為 true
    (說明它引用了一個對象):
    */
    if(linkRef){
    /* 將一個對全局函數的引用指定給這個鏈接
    的事件處理屬性,使函數成為鏈接元素的事件處理器:
    */
    linkRef.onclick = forAddQueryOnClick;
    }
    }
    /* 聲明一個全局函數,作為鏈接元素的事件處理器,
    這個函數將一個全局變量的值作為要添加事件處理器的
    鏈接元素的  - href - 值的一部分:
    */
    function forAddQueryOnClick(){
    this.href += ('?quantaty='+escape(quantaty));
    return true;
    }
    

    在上面例子的第一個版本中,內部函數并沒有作為閉包發揮應有的作用。在那種情況下,反而是不使用閉包更有效率,因為不用重復創建許多本質上相同的函數對象。

    類似地考量同樣適用于對象的構造函數。與下面代碼中的構造函數框架類似的代碼并不罕見:

    function ExampleConst(param){
    /* 通過對函數表達式求值創建對象的方法,
    并將求值所得的函數對象的引用賦給要創建對象的屬性:
    */
    this.method1 = function(){
    ... // 方法體。
    };
    this.method2 = function(){
    ... // 方法體。
    };
    this.method3 = function(){
    ... // 方法體。
    };
    /* 把構造函數的參數賦給對象的一個屬性:
    */
    this.publicProp = param;
    }
    

    每當通過 new ExampleConst(n) 使用這個構造函數創建一個對象時,都會創建一組新的、作為對象方法的函數對象。因此,創建的對象實例越多,相應的函數對象也就越多。

    Douglas Crockford 提出的模仿 JavaScript 對象私有成員的技術,就利用了將對內部函數的引用指定給在構造函數中構造對象的公共屬性而形成的閉包。如果對象的方法沒有利用在構造函數中形成的閉包,那么在實例化每個對象時創建的多個函數對象,會使實例化過程變慢,而且將有更多的資源被占用,以滿足創建更多函數對象的需要。

    這那種情況下,只創建一次函數對象,并把它們指定給構造函數 prototype 的相應屬性顯然更有效率。這樣一來,它們就能被構造函數創建的所有對象共享了:

    function ExampleConst(param){
    /* 將構造函數的參數賦給對象的一個屬性:
    */
    this.publicProp = param;
    }
    /* 通過對函數表達式求值,并將結果函數對象的引用
    指定給構造函數原型的相應屬性來創建對象的方法:
    */
    ExampleConst.prototype.method1 = function(){
    ... // 方法體。
    };
    ExampleConst.prototype.method2 = function(){
    ... // 方法體。
    };
    ExampleConst.prototype.method3 = function(){
    ... // 方法體。
    };
    

    Internet Explorer 的內存泄漏問題

    Internet Explorer Web 瀏覽器(在 IE 4 到 IE 6 中核實)的垃圾收集系統中存在一個問題,即如果 ECMAScript 和某些宿主對象構成了 "循環引用",那么這些對象將不會被當作垃圾收集。此時所謂的宿主對象指的是任何 DOM 節點(包括 document 對象及其后代元素)和 ActiveX 對象。如果在一個循環引用中包含了一或多個這樣的對象,那么這些對象直到瀏覽器關閉都不會被釋放,而它們所占用的內存同樣在瀏覽器關閉之前都不會交回系統重用。

    當兩個或多個對象以首尾相連的方式相互引用時,就構成了循環引用。比如對象 1 的一個屬性引用了對象 2 ,對象 2 的一個屬性引用了對象 3,而對象 3 的一個屬性又引用了對象 1。對于純粹的 ECMAScript 對象而言,只要沒有其他對象引用對象 1、2、3,也就是說它們只是相互之間的引用,那么仍然會被垃圾收集系統識別并處理。但是,在 Internet Explorer 中,如果循環引用中的任何對象是 DOM 節點或者 ActiveX 對象,垃圾收集系統則不會發現它們之間的循環關系與系統中的其他對象是隔離的并釋放它們。最終它們將被保留在內存中,直到瀏覽器關閉。

    閉包非常容易構成循環引用。如果一個構成閉包的函數對象被指定給,比如一個 DOM 節點的事件處理器,而對該節點的引用又被指定給函數對象作用域中的一個活動(或可變)對象,那么就存在一個循環引用。DOM_Node.onevent ->function_object.[[scope]] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。形成這樣一個循環引用是輕而易舉的,而且稍微瀏覽一下包含類似循環引用代碼的網站(通常會出現在網站的每個頁面中),就會消耗大量(甚至全部)系統內存。

    多加注意可以避免形成循環引用,而在無法避免時,也可以使用補償的方法,比如使用 IE 的 onunload 事件來來清空(null)事件處理函數的引用。時刻意識到這個問題并理解閉包的工作機制是在 IE 中避免此類問題的關鍵。

    comp.lang.javascript FAQ notes T.O.C.

    • 撰稿 Richard Cornford,2004 年 3 月
    • 修改建議來自:
      • Martin Honnen.
      • Yann-Erwan Perio (Yep).
      • Lasse Reichstein Nielsen. (definition of closure)
      • Mike Scirocco.
      • Dr John Stockton.
    posted @ 2008-11-15 00:14 xyz 閱讀(438) | 評論 (0)編輯 收藏
         摘要: Normal 0 0 2 來源:www.java3z.com Struts的核心是struts-config.xml配置文件,在這個文件里描述了所有的Struts組件。 在這里包括配置主要的組件及次要的組件,下面是struts-config.xml包含主要元素的內容: 一、  &n...  閱讀全文
    posted @ 2008-11-11 16:36 xyz 閱讀(710) | 評論 (0)編輯 收藏
         摘要: 來源:http://www.tkk7.com/konhon/archive/2006/03/29/38012.html < description >   < display >   < icon >  這三個元素提供了W...  閱讀全文
    posted @ 2008-10-31 12:38 xyz 閱讀(1215) | 評論 (0)編輯 收藏

    來源:http://hi.baidu.com/yangfan356/blog/item/78b98f3dedd6fcc29f3d62ab.html
    解密.htm.html.shtm.shtml的區別與聯系 

      每一個網頁或者說是web頁都有其固定的后綴名,不同的后綴名對應著不同的文件格式和不同的規則、協議、用法,最常見的web頁的后綴名是.html和.htm,但這只是web頁最基本的兩種文件格式,今天我們來介紹一下web頁的其它一些文件格式。

    首先,介紹一下html與htm:

      關于HTML,HTML(HyperTextMark-upLanguage)即超文本標記語言,是WWW的描述語言。設計HTML語言的目的是為了能把存放在一臺電腦中的文本或圖形與另一臺電腦中的文本或圖形方便地聯系在一起,形成有機的整體,人們不用考慮具體信息是在當前電腦上還是在網絡的其它電腦上。我們只需使用鼠標在某一文檔中點取一個圖標,Internet就會馬上轉到與此圖標相關的內容上去,而這些信息可能存放在網絡的另一臺電腦中。 HTML文本是由HTML命令組成的描述性文本,HTML命令可以說明文字、圖形、動畫、聲音、表格、鏈接等。HTML的結構包括頭部(Head)、主體(Body)兩大部分,其中頭部描述瀏覽器所需的信息,而主體則包含所要說明的具體內容。

      關于HTM,實際上HTM與HTML沒有本質意義的區別,只是為了滿足DOS僅能識別8+3的文件名而已,因為一些老的系統(win32)不能識別四位文件名,所以某些網頁服務器要求index.html最后一個l不能省略。MSIE能自動識別和打開這些文件,但編寫網頁地址的時候必須是完全對應的,也就是說index.htm和index.html是兩個不同的文件,對應著不同的地址。值得一提的是UNIX系統中對大小寫敏感,不吻合的話就可能報沒有文件或者找不到文件。

    其次,介紹一下shtml和shtm:

      關于shtml,shtml是一種基于SSI技術的文件,也就是Server Side Include--SSI 服務器端包含指令,一些Web Server如果有SSI功能的話就會對shtml文件特殊招待,服務器會先掃一次shtml文件看沒有特殊的SSI指令存在,如果有的話就按Web Server設定規則解釋SSI指令,解釋完后跟一般html一起調去客戶端。

      關于shtm,shtm與shtml的關系和htm與html的關系大致相似,這里就不多說了。

    html或htm與shtml或shtm的關系是什么?

      html或者htm是一種靜態的頁面格式,也就是說不需要服務器解析其中的腳本,或者說里面沒有服務器端執行的腳本,而shtml或者shtm由于它基于SSI技術,當有服務器端可執行腳本時被當作一種動態編程語言來看待,就如asp、jsp或者php一樣。當shtml或者shtm中不包含服務器端可執行腳本時其作用和html或者htm是一樣的。

     

    什么是SHTML?與HTML的區別

      問起SHTML和HTML的區別,如果用一句話來解釋就是:SHTML 不是HTML而是一種服務器 API,shtml是服務器動態產成的html。

      雖然兩者都是超文本格式,但shtml是一種用于SSI技術的文件。也就是Server Side Include--SSI 服務器端包含指令。如果Web Server有SSI功能的話(大多數(尤其是基于Unix平臺)的WEB服務器如Netscape Enterprise Server等均支持SSI命令)。 會對shtml文件特殊招待。 先掃一次shtml文件看沒有特殊的SSI指令現在。 有就按Web Server設定規則解釋SSI指令。 解釋完后跟一般html一起掉去客戶端。

    shtml:
      使用SSI(Server Side Include)的html文件擴展名,SSI(Server Side Include),通常稱為"服務器端嵌入"或者叫"服務器端包含",是一種類似于ASP的基于服務器的網頁制作技術。

    SSI工作原理:
      將內容發送到瀏覽器之前,可以使用“服務器端包含 (SSI)”指令將文本、圖形或應用程序信息包含到網頁中。例如,可以使用 SSI 包含時間/日期戳、版權聲明或供客戶填寫并返回的表單。對于在多個文件中重復出現的文本或圖形,使用包含文件是一種簡便的方法。將內容存入一個包含文件中即可,而不必將內容輸入所有文件。通過一個非常簡單的語句即可調用包含文件,此語句指示 Web 服務器將內容插入適當網頁。而且,使用包含文件時,對內容的所有更改只需在一個地方就能完成。

      因為包含 SSI 指令的文件要求特殊處理,所以必須為所有 SSI 文件賦予 SSI 文件擴展名。默認擴展名是 .stm、.shtm 和 .shtml

      Web 服務器在處理網頁的同時處理 SSI 指令。當 Web 服務器遇到 SSI 指令時,直接將包含文件的內容插入 HTML 網頁。如果“包含文件”中包含 SSI 指令,則同時插入此文件。除了用于包含文件的基本指令之外,還可以使用 SSI 指令插入文件的相關信息(如文件的大小)或者運行應用程序或 shell 命令。

      網站維護常常碰到的一個問題是,網站的結構已經固定,卻為了更新一點內容而不得不重做一大批網頁。SSI提供了一種簡單、有效的方法來解決這一問題,它將一個網站的基本結構放在幾個簡單的HTML文件中(模板),以后我們要做的只是將文本傳到服務器,讓程序按照模板自動生成網頁,從而使管理大型網站變得容易。

      所以,利用SHTML格式的頁面目的和 ASP 差不多,但是因為是 API 所以運轉速度更快,效率更高,比ASP快,比HTML慢,但由于可以使用服務器端包含,因此使頁面更新容易(特別是批量更新banner,版權等),想象一下吧,你有一段 HTML,要在中間穿插一些特殊的服務端腳本,比如插入其他 HTML 段落,你選擇 ASP 來完成這個任務,但是如果任務更繁重,需要更多的時間,比如 5 s,這個時候你不用 ASP 而用 SHTML,或許處理時間就只用 4 s 了.

     

      動態網頁是與靜態網頁相對應的,也就是說,網頁 URL的后綴不是.htm、.html、.shtml、.xml等靜態網頁的常見形式,而是以.asp、.jsp、.php、.perl、.cgi等形式為后綴,并且在動態網頁網址中有一個標志性的符號“?”,如有這樣一個動態網頁的地址為:

      http://www.pagehome.cn/ip/index.asp?id=1

      這就是一個典型的動態網頁URL形式。

      這里說的動態網頁,與網頁上的各種動畫、滾動字幕等視覺上的“動態效果”沒有直接關系,動態網頁也可以是純文字內容的,也可以是包含各種動畫的內容,這些只是網頁具體內容的表現形式,無論網頁是否具有動態效果,采用動態網站技術生成的網頁都稱為動態網頁。

      從網站瀏覽者的角度來看,無論是動態網頁還是靜態網頁,都可以展示基本的文字和圖片信息,但從網站開發、管理、維護的角度來看就有很大的差別。網絡營銷教學網站將動態網頁的一般特點簡要歸納如下:

      (1)動態網頁以數據庫技術為基礎,可以大大降低網站維護的工作量;

      (2)采用動態網頁技術的網站可以實現更多的功能,如用戶注冊、用戶登錄、在線調查、用戶管理、訂單管理等等;

      (3)動態網頁實際上并不是獨立存在于服務器上的網頁文件,只有當用戶請求時服務器才返回一個完整的網頁;

      (4)動態網頁中的“?”對搜索引擎檢索存在一定的問題,搜索引擎一般不可能從一個網站的數據庫中訪問全部網頁,或者出于技術方面的考慮,搜索蜘蛛不去抓取網址中“?”后面的內容,因此采用動態網頁的網站在進行搜索引擎推廣時需要做一定的技術處理才能適應搜索引擎的要求。

    dhtml:


      確切地說,DHTML只是一種制作網頁的概念,實際上沒有一個組織或機構推出過所謂的DHTML標準或技術規范之類的。DHTML不是一種技術、標準或規范,DHTML只是一種將目前已有的網頁技術、語言標準整和運用,制作出能在下載后仍然能實時變換頁面元素效果的網頁的設計概念。
      DHTML大致包含以下網頁技術、標準或規范:
      HTML 4.0 :沒什么好說的,網頁的基礎語言標準。
      CSSL:注意!不是CSS,是CSSL,它是Clent-Side Scripting Language的縮寫,譯作“客戶端腳本語言”,主要有JavaScript(JS),VBScript(VBS),JScript。Netscape主要支持JS,IE主要支持JS,VBS和JScript。
      DOM:Document Object Model的縮寫,譯作“文檔對象模型”,是W3C日前極力推廣的web技術標準之一,它將網頁中的內容抽象成對象,每個對象擁有各自的屬性(Properties)、方法(Method)和事件(Events),這些都可以通過上面講到的CSSL來進行控制。IE和NS的對象模型都是以W3C的公布的DOM為基準,加上自己的Extended Object(擴展對象)來生成的。
      CSS :這才是Cascading Style Sheets(層疊樣式表單)的縮寫,也是在論壇討論最多的技術規范,它是HTML的輔助設計規范,用來彌補HTML在排版上的所受的限制導致的不足,它是DOM的一部分。理論上說通過CSSL動態地改變CSS屬性可以做出任何你想要的頁面視覺效果。
      所以,簡單地說,要實現DHTML,就是以HTML為基礎,運用DOM將頁面元素對象化,利用CSSL控制這些對象的CSS屬性以達到網頁的動態視覺效果。

    shtml:

      問起SHTML和HTML的區別,如果用一句話來解釋就是:SHTML 不是HTML而是一種服務器 API,shtml是服務器動態產成的html.

      雖然兩者都是超文本格式,但shtml是一種用于SSI技術的文件。 也就是Server Side Include--SSI 服務器端包含指令。 如果Web Server有SSI功能的話(大多數(尤其是基于Unix平臺)的WEB服務器如Netscape Enterprise Server等均支持SSI命令)。 會對shtml文件特殊招待。 先掃一次shtml文件看沒有特殊的SSI指令現在。 有就按Web Server設定規則解釋SSI指令。 解釋完后跟一般html一起掉去客戶端。
    shtml:使用SSI(Server Side Include)的html文件擴展名,SSI(Server Side Include),通常稱為"服務器端嵌入"或者叫"服務器端包含",是一種類似于ASP的基于服務器的網頁制作技術。

      SSI工作原理:將內容發送到瀏覽器之前,可以使用“服務器端包含 (SSI)”指令將文本、圖形或應用程序信息包含到網頁中。例如,可以使用 SSI 包含時間/日期戳、版權聲明或供客戶填寫并返回的表單。對于在多個文件中重復出現的文本或圖形,使用包含文件是一種簡便的方法。將內容存入一個包含文件中即可,而不必將內容輸入所有文件。通過一個非常簡單的語句即可調用包含文件,此語句指示 Web 服務器將內容插入適當網頁。而且,使用包含文件時,對內容的所有更改只需在一個地方就能完成。

      因為包含 SSI 指令的文件要求特殊處理,所以必須為所有 SSI 文件賦予 SSI 文件擴展名。默認擴展名是 .stm、.shtm 和 .shtml

      Web 服務器在處理網頁的同時處理 SSI 指令。當 Web 服務器遇到 SSI 指令時,直接將包含文件的內容插入 HTML 網頁。如果“包含文件”中包含 SSI 指令,則同時插入此文件。除了用于包含文件的基本指令之外,還可以使用 SSI 指令插入文件的相關信息(如文件的大小)或者運行應用程序或 shell 命令。

      網站維護常常碰到的一個問題是,網站的結構已經固定,卻為了更新一點內容而不得不重做一大批網頁。SSI提供了一種簡單、有效的方法來解決這一問題,它將一個網站的基本結構放在幾個簡單的HTML文件中(模板),以后我們要做的只是將文本傳到服務器,讓程序按照模板自動生成網頁,從而使管理大型網站變得容易。

      所以,利用SHTML格式的頁面目的和 ASP 差不多,但是因為是 API 所以運轉速度更快,效率更高,比ASP快,比HTML慢,但由于可以使用服務器端包含,因此使頁面更新容易(特別是批量更新banner,版權等),想象一下吧,你有一段 HTML,要在中間穿插一些特殊的服務端腳本,比如插入其他 HTML 段落,你選擇 ASP 來完成這個任務,但是如果任務更繁重,需要更多的時間,比如 5 s,這個時候你不用 ASP 而用 SHTML,或許處理時間就只用 4 s 了.

    xhtml:
      HTML是一種基本的WEB網頁設計語言,XHTML是一個基于XML的置標語言,看起來與HTML有些相象,只有一些小的但重要的區別,XHTML就是一個扮演著類似HTML的角色的XML,所以,本質上說,XHTML是一個過渡技術,結合了XML(有幾分)的強大功能及HTML(大多數)的簡單特性。

      2000年底,國際W3C(World Wide Web Consortium)組織公布發行了XHTML 1.0版本。XHTML 1.0是一種在HTML 4.0基礎上優化和改進的的新語言,目的是基于XML應用。XHTML是一種增強了的HTML,它的可擴展性和靈活性將適應未來網絡應用更多的需求。下面是W3C的HTML工作組主席Steven Pemberton回答的關于XHTML的常見基礎問題。

      (1)XHTML解決HTML語言所存在的嚴重制約其發展的問題。HTML發展到今天存在三個主要缺點:不能適應現在越多的網絡設備和應用的需要,比如手機、PDA、信息家電都不能直接顯示HTML;由于HTML代碼不規范、臃腫,瀏覽器需要足夠智能和龐大才能夠正確顯示HTML;數據與表現混雜,這樣你的頁面要改變顯示,就必須重新制作HTML。因此HTML需要發展才能解決這個問題,于是W3C又制定了XHTML,XHTML是HTML向XML過度的一個橋梁。

      (2)XML是web發展的趨勢,所以人們急切的希望加入XML的潮流中。XHTML是當前替代HTML4標記語言的標準,使用XHTML 1.0,只要你小心遵守一些簡單規則,就可以設計出既適合XML系統,又適合當前大部分HTML瀏覽器的頁面。這個意思就是說,你可以立刻設計使用XML,而不需要等到人們都使用支持XML的瀏覽器。這個指導方針可以使web平滑的過渡到XML。

      (3)使用XHTML的另一個優勢是:它非常嚴密。當前網絡上的HTML的糟糕情況讓人震驚,早期的瀏覽器接受私有的HTML標簽,所以人們在頁面設計完畢后必須使用各種瀏覽器來檢測頁面,看是否兼容,往往會有許多莫名其妙的差異,人們不得不修改設計以便適應不同的瀏覽器。

      (4)XHTML是能與其它基于XML的標記語言、應用程序及協議進行良好的交互工作。

      (5)XHTML是Web標準家族的一部分,能很好在無線設備等其它用戶代理上。

      (6)在網站設計方面,XHTML可助你去掉表現層代碼的惡習,幫助你養成標記校驗來測試頁面工作的習慣。


    ASP,JSP,PHP有什么區別和共同點?

    回答(一)
    不同:
      1.web服務器不一樣,就是運行環境不一樣。一般ASP用IIS,PHP用APACHE
      2.語法不一樣,ASP是VBS/JS,JSP和JAVA語法相似,PHP語法和C,JAVA相似
      3.運行方式不一樣,ASP,PHP是解釋型的,JSP是可編譯型的
    相同:
      1.都是服務器端嵌入式腳本語言
      2.能實現CGI所能實現的大部分東西。

    回答(二)
      asp是vb
      jsp是java
      php是c/c++

    回答(三)

    ASP、JSP與PHP的比較


      目前,最常用的三種動態網頁語言有ASP(Active Server Pages),JSP(Java Server Pages),PHP (Hypertext Preprocessor)。

    簡介
      ASP全名Active Server Pages,是一個WEB服務器端的開發環境, 利用它可以產生和運行動態的、交互的、高性能的WEB服務應用程序。ASP采用腳本語言VB Script(Java script)作為自己的開發語言。

      PHP是一種跨平臺的服務器端的嵌入式腳本語言. 它大量地借用C,Java和Perl語言的語法, 并耦合PHP自己的特性,使WEB開發者能夠快速地寫出動態生成頁面.它支持目前絕大多數數據庫。還有一點,PHP是完全免費的,不用花錢,你可以從PHP官方站點(http://www.php.net)自由下載。而且你可以不受限制地獲得源碼,甚至可以從中加進你自己需要的特色。

      JSP 是Sun公司推出的新一代站點開發語言,它完全解決了目前ASP,PHP的一個通病--腳本級執行(據說PHP4 也已經在Zend 的支持下,實現編譯運行).Sun 公司借助自己在Java 上的不凡造詣,將Java 從Java 應用程序 和 Java Applet 之外,又有新的碩果,就是Jsp--Java Server Page。Jsp 可以在Serverlet和JavaBean的支持下,完成功能強大的站點程序。

      三者都提供在 HTML 代碼中混合某種程序代碼、由語言引擎解釋執行程序代碼的能力。但JSP代碼被編譯成 Servlet 并由 Java 虛擬機解釋執行,這種編譯操作僅在對 JSP 頁面的第一次請求時發生。在 ASP 、PHP、JSP 環境下, HTML 代碼主要負責描述信息的顯示樣式,而程序代碼則用來描述處理邏輯。普通的 HTML 頁面只依賴于 Web 服務器,而 ASP 、PHP、JSP 頁面需要附加的語言引擎分析和執行程序代碼。程序代碼的執行結果被重新嵌入到HTML 代碼中,然后一起發送給瀏覽器。 ASP 、PHP、 JSP三者都是面向 Web 服務器的技術,客戶端瀏覽器不需要任何附加的軟件支持。

    技術特點

    ASP:

      1. 使用 VBScript 、 JScript 等簡單易懂的腳本語言,結合 HTML 代碼,即可快速地完成網站的應用程序。

      2. 無須 compile 編譯,容易編寫,可在服務器端直接執行。

      3. 使用普通的文本編輯器,如 Windows 的記事本,即可進行編輯設計。

      4. 與瀏覽器無關 (Browser Independence), 用戶端只要使用可執行 HTML 碼的瀏覽器,即可瀏覽 Active Server Pages 所設計的網頁內容。 Active Server Pages 所使用的腳本語言 (VBScript 、 Jscript) 均在 WEB 服務器端執行,用戶端的瀏覽器不需要能夠執行這些腳本語言。
      5.Active Server Pages 能與任何 ActiveX scripting 語言相容。除了可使用 VBScript 或 JScript 語言來設計外,還通過 plug-in 的方式,使用由第三方所提供的其他腳本語言,譬如 REXX 、 Perl 、 Tcl 等。腳本引擎是處理腳本程序的 COM(Component Object Model) 物件。

      6. 可使用服務器端的腳本來產生客戶端的腳本。

      7.ActiveX Server Components(ActiveX 服務器元件 ) 具有無限可擴充性。可以使用 Visual Basic 、 Java 、 Visual C++ 、 COBOL 等編程語言來編寫你所需要的ActiveX Server Component 。

    PHP:

    1.數據庫連接
      PHP可以編譯成具有與許多數據庫相連接的函數。PHP與MySQL是現在絕佳的組合。你還可以自己編寫外圍的函數取間接存取數據庫。通過這樣的途徑當你更換使用的數據庫時,可以輕松地更改編碼以適應這樣的變。PHPLIB就是最常用的可以提供一般事務需要的一系列基庫。但PHP提供的數據庫接口支持彼此不統一,比如對Oracle,  MySQL, Sybase的接口,彼此都不一樣。這也是PHP的一個弱點。

    2.面向對象編程
      PHP提供了類和對象。基于web的編程工作非常需要面向對象編程能力。PHP支持構造器、提取類等。

    JSP:

    1.將內容的生成和顯示進行分離

      使用JSP技術,Web頁面開發人員可以使用HTML或者XML標識來設計和格式化最終頁面。使用JSP標識或者小腳本來生成頁面上的動態內容。生成內容的邏輯被封裝在標識和JavaBeans組件中,并且捆綁在小腳本中,所有的腳本在服務器端運行。如果核心邏輯被封裝在標識和Beans中,那么其他人,如Web管理人員和頁面設計者,能夠編輯和使用JSP頁面,而不影響內容的生成。

      在服務器端,JSP引擎解釋JSP標識和小腳本,生成所請求的內容(例如,通過訪問JavaBeans組件,使用JDBCTM技術訪問數據庫,或者包含文件),并且將結果以HTML(或者XML)頁面的形式發送回瀏覽器。這有助于作者保護自己的代碼,而又保證任何基于HTML的Web瀏覽器的完全可用性。

    2.強調可重用的組件

      絕大多數JSP頁面依賴于可重用的,跨平臺的組件(JavaBeans或者Enterprise JavaBeansTM組件)來執行應用程序所要求的更為復雜的處理。開發人員能夠共享和交換執行普通操作的組件,或者使得這些組件為更多的使用者或者客戶團體所使用。基于組件的方法加速了總體開發過程,并且使得各種組織在他們現有的技能和優化結果的開發努力中得到平衡。

    3.采用標識簡化頁面開發

      Web頁面開發人員不會都是熟悉腳本語言的編程人員。JavaServer Page技術封裝了許多功能,這些功能是在易用的、與JSP相關的XML標識中進行動態內容生成所需要的。標準的JSP標識能夠訪問和實例化JavaBeans組件,設置或者檢索組件屬性,下載Applet,以及執行用其他方法更難于編碼和耗時的功能。

      通過開發定制化標識庫,JSP技術是可以擴展的。今后,第三方開發人員和其他人員可以為常用功能創建自己的標識庫。這使得Web頁面開發人員能夠使用熟悉的工具和如同標識一樣的執行特定功能的構件來工作。

      JSP技術很容易整合到多種應用體系結構中,以利用現存的工具和技巧,并且擴展到能夠支持企業級的分布式應用。作為采用Java技術家族的一部分,以及Java 2(企業版體系結構)的一個組成部分,JSP技術能夠支持高度復雜的基于Web的應用。

      由于JSP頁面的內置腳本語言是基于Java編程語言的,而且所有的JSP頁面都被編譯成為Java Servlet,JSP頁面就具有Java技術的所有好處,包括健壯的存儲管理和安全性。

      作為Java平臺的一部分,JSP擁有Java編程語言“一次編寫,各處運行”的特點。隨著越來越多的供應商將JSP支持添加到他們的產品中,您可以使用自己所選擇的服務器和工具,更改工具或服務器并不影響當前的應用。

    應用范圍

      ASP是Microsoft開發的動態網頁語言,也繼承了微軟產品的一貫傳統——只能運行于微軟的服務器產品,IIS (Internet Information Server) (windows NT)和PWS(Personal Web Server)(windows 98)上。Unix下也有ChiliSoft的插件來支持ASP,但是ASP本身的功能有限,必須通過ASP+COM的組合來擴充,Unix下的COM實現起來非常困難。

      PHP3可在Windows,Unix,Linux的Web服務器上正常運行,還支持IIS,Apache等通用Web服務器,用戶更換平臺時,無需變換PHP3代碼,可即拿即用.

      JSP同PHP3類似,幾乎可以運行于所有平臺。如Win NT,Linux,Unix. NT下IIS通過一個插件,例如JRUN或者ServletExec,就能支持JSP。著名的Web服務器Apache已經能夠支持JSP。由于Apache廣泛應用在NT、Unix和Linux上,因此JSP有更廣泛的運行平臺。雖然現在NT操作系統占了很大的市場份額,但是在服務器方面Unix的優勢仍然很大,而新崛起的Linux更是來勢不小。從一個平臺移植到另外一個平臺,JSP和JavaBean甚至不用重新編譯,因為Java字節碼都是標準的與平臺無關的。

    性能比較

      有人做過試驗,對這三種語言分別做循環性能測試及存取Oracle數據庫測試。

      在循環性能測試中,JSP只用了令人吃驚的四秒鐘就結束了20000*20000的循環。而ASP、PHP測試的是2000*2000循環(少一個數量級),卻分別用了63秒和84秒。(參考PHPLIB)。

      數據庫測試中,三者分別對 Oracle 8 進行 1000 次 Insert,Update,Select,和Delete: Jsp 需要 13 秒,Php 需要 69 秒,ASP則 需要 73 秒。

    前景分析

      目前在國內PHP與ASP應用最為廣泛。而JSP由于是一種較新的技術,國內采用的較少。但在國外,JSP已經是比較流行的一種技術,尤其是電子商務類的網站,多采用JSP。

      采用PHP的網站如新浪網(sina)、中國人(Chinaren)等,但由于PHP本身存在的一些缺點,使得它不適合應用于大型電子商務站點,而更適合一些小型的商業站點。

      首先,PHP缺乏規模支持。其次,缺乏多層結構支持。對于大負荷站點,解決方法只有一個:分布計算。數據庫、應用邏輯層、表示邏輯層彼此分開,而且同層也可以根據流量分開,組成二維陣列。而PHP則缺乏這種支持。還有上面提到過的一點,PHP提供的數據庫接口支持不統一,這就使得它不適合運用在電子商務中。

      ASP和JSP則沒有以上缺陷,ASP可以通過Microsoft Windowsd的COM/DCOM獲得ActiveX規模支持,通過DCOM和Transcation Server獲得結構支持;JSP可以通過SUN Java的Java Class和EJB獲得規模支持,通過EJB/CORBA以及眾多廠商的Application Server獲得結構支持。

      三者中,JSP應該是未來發展的趨勢。世界上一些大的電子商務解決方案提供商都采用JSP/Servlet。比較出名的如IBM的E-business,它的核心是采用JSP/Servlet的WebSphere;西方另外一個非常著名的電子商務軟件提供商,Intershop。它原來的產品Intershop1 2, 3, 4占據了主要的電子商務軟件份額。它們都是通過CGI來提供支持 的。但去年10月后它推出了Enfinity,一個采用JSP/Servlet的電子商務Application Server,而且聲言不再開發傳統軟件。

    總之

      ASP,PHP,JSP三者都有相當數量的支持者,由此也可以看出三者各有所長。正在學習或使用動態頁面的朋友可根據三者的特點選擇一種適合自己的語言。

     

    asp jsp cgi php之間的區別和優點

    發布者:Iease 發布時間:2006-7-22

      就我個人的意見,PHP只適合做小型的網站開發,大型的站點就很困難了(能做,但是很痛苦!)

    擴充性:
      1、PHP用光了自己的一堆函數以后,要擴充似乎是很困難的。據我一位玩PHP和c比較好的朋友說,“可以擴充,要用c來寫,然后編譯進PHP里面去”。請問,用PHP的朋友中有多少能達到這個水平的?
      2、ASP。如果你認為asp只是那幾個response/request等對象,那你錯了。
      個人認為,ASP只是一種技術,如果沒有MS的com/com+,asp就什么都沒有了。說得過份一點,asp本身連一個賦值語句,連一個if都沒有!
      正是基于此,ASP擁有很強的擴充性。你不熟悉vbs,你可以用jscript,你可以用perlscript,你可以通過安裝xscript來使用你熟悉的腳本語言。你會vb/delphi/vc/bcb..嗎?那你可以寫自己的組件,然后用asp來使用它。
      3、java :同asp一樣,java通過不斷增多的(公司發布的或是自己編譯的)class來擴展自已。而且jsp與asp相比有一個大的優點:jsp是基于java的,擁有強大的程序語法和天然的平臺無關性。

    執行效率:
      1、PHP是基于解釋型的。
      “因為不用編譯而且高階,所以這類語言的程序效率通常很差,又因為原始程序代碼暴露在外,所以拿它來寫寫工具程序自己用可以,但是拿來開發軟件產品比較不恰當(除非你不在乎原始碼外流)。”(此段引用蔡學墉文章“你該學什么程序語言?”)。
      雖然PHP可以通過使用第三方的zend(我對PHP不是太熟悉)來彌補這個缺陷,但是似乎Zend是收費的,而且使用第三方的東西已經不是PHP本身的討論了。
      2、ASP
      asp發展較早,因此早期的asp1.0、asp2.0、asp3.0都是基于解釋的,有同PHP相同的問題。不過自MS的.net以后,asp.net在第一次加載時進行編譯,并加載于內存中,因此第一次以后的執行效率已經是相當快速了。
      3、JSP。
      java本身就是屬于編譯的語言,目前的jsp服務器產品大多是做JIT編譯的,JSP在第一次加載時被編譯,因而與PHP相比在執行效率上有明顯的提高。

    posted @ 2008-09-17 11:43 xyz 閱讀(1685) | 評論 (0)編輯 收藏
    來源:http://jeplove.blog.zj.com/blog/d-146416.html
    1.下載DWR Version 2
    https://dwr.dev.java.net/files/documents/2427/47504/dwr.jar

    2.安裝DWR,把dwr.jar放到WEB-INF/lib下

    web.xml中加入DWRServlet & ActionServlet
    其中<load-on-startup>的部分要特別注意,ActionServlet要先初始化,所以數字要比較小.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
      <servlet>    
    <servlet-name>action</servlet-name>    
    <servlet-class>org.apache.struts.action.ActionServlet
    </servlet-class>    
    <init-param>      
    <param-name>config</param-name>      
    <param-value>/WEB-INF/struts-config.xml</param-value>    
    </init-param>    
    <init-param>      
    <param-name>debug</param-name>      
    <param-value>2</param-value>    
    </init-param>    
    <load-on-startup>2</load-on-startup>  
    </servlet>  
    <servlet-mapping>    
    <servlet-name>action</servlet-name>    
    <url-pattern>*.do</url-pattern>  
    </servlet-mapping>  
    <servlet>    
    <servlet-name>dwr-invoker</servlet-name>    
    <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>    
    <init-param>      
    <param-name>debug</param-name>      
    <param-value>true</param-value>    
    </init-param>    
    <load-on-startup>10</load-on-startup>  
    </servlet>  
    <servlet-mapping>   
     <servlet-name>dwr-invoker</servlet-name>    
    <url-pattern>/dwr/*</url-pattern>  
    </servlet-mapping>



    dwr.xml中加入struts的設定,其中formBean的參數的value值,會對應到struts-config.xml中<form-beans>的設定
    1
    2
    3
    4
    5
    6
    7
    8
      <dwr>  
    <allow>    
    <create creator="struts" javascript="testFrm">      
    <param name="formBean" value="testActionForm"/>    
    </create>  
    </allow>  
    </dwr>  


    struts-config.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      <struts-config>  
    <form-beans>    
    <form-bean name="testActionForm" type="test.struts.testActionForm" />  
    </form-beans>  
    <action-mappings>    
    <action name="testActionForm" path="/testAction" scope="session" type="test.struts.testAction" validate="false">      
    <forward name="display" path="/display.jsp" />    
    </action>  
    </action-mappings>  
    <message-resources parameter="ApplicationResources" />
    </struts-config>


    testActionForm.java,getDate()會透過dwr,取得現在最新的日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package test.struts; 
    import org.apache.struts.action.*;
    import java.util.*; 
    public class testActionForm extends ActionForm {     
    private String strDate;     
    public void setStrDate(String strDate) {        
    this.strDate = strDate;    
    }     
    public String getStrDate() {        
    return strDate;    
    }    
     //dwr    public String getDate() {        
    Date date = new Date();        
    return date.toString();   
     } 
    }


    testAction.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package test.struts; 
    import org.apache.struts.action.ActionMapping;
    import org.apache.struts.action.ActionForm;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.struts.action.ActionForward;
    import org.apache.struts.action.Action;
    import org.apache.struts.action.*; 
    public class testAction extends Action {    
    	public ActionForward execute(ActionMapping mapping, ActionForm form,                                 
    HttpServletRequest request,                                 
    HttpServletResponse response) {         
    testActionForm actionForm = (testActionForm) form;       
     System.out.println(actionForm.getStrDate());        
    return mapping.findForward("display");    
    }
    }

    date.jsp,在form的部分,請用struts 的 tag library,我把<html:text property="strDate" size="30" >改成<input type="text" name="strDate">後,無法正常的接受到值.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <%@ page contentType="text/html; charset=Big5" %>
    <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
    <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
    <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
    <html><head>
    <title>title</title>  
    <script type='text/javascript' src='dwr/interface/testFrm.js'></script>  
    <script type='text/javascript' src='dwr/engine.js'></script>  
    <script type='text/javascript' src='dwr/util.js'></script>
    </head>
    <SCRIPT LANGUAGE="JavaScript" type=""
    function refreshDate() {   
     testFrm.getDate(populateDate)
    ;} 
    function populateDate(data){   
    DWRUtil.setValue('strDate', data);
    } 
    </script> 
    <body> 
    <html:form action="testAction.do">
    date:<html:text property="strDate" size="30" ></html:text> 
    <input type="button" onclick="refreshDate();" value="更新日期"/><br/>   
    <html:submit>送出  </html:submit>
    </html:form></body></html>


    display.jsp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <%@ page contentType="text/html; charset=Big5" %>
    <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
    %@page import="test.struts.*"%
    <html>
    <head>
    <title>test</title>
    </head><body bgcolor="#ffffff"><h1>您送出的日期:<br>
    <bean:write name="testActionForm" property="strDate"/></h1>
    </body>
    </html>
     
    posted @ 2008-09-12 11:21 xyz 閱讀(1151) | 評論 (3)編輯 收藏
    來源:forest077

    如何使用Eclipse導入并運行源碼(原創哦,如有轉載,請注明作者forest077)
    OfwFypR  
      網上關于Eclipse配置和開發入門程序的文章很多,可是要么很粗淺,要么很高深,卻很少看到講解如何把別人的源碼導入到自己的Eclipse環境中編譯運行的描述。做為初學者,能夠學習網上一些優秀源碼是提高的必由之路,可是Eclipse卻不象VC和Delphi那樣容易上手,對于很多初學者來說,它似乎還是太難了點。在找不到很好的關于Eclipse入門教程的情況下,為了能運行網上下載的Java源碼,我頗費了一些時間尋找如何正確的導入源碼并運行的方法,不敢獨美,特貢獻出來與初學者共享。 pV!kC$]`@  
      運行環境: Y,EXoMs46  
        Java EE 5.0 OLX1{a  
        Eclipse 3.1 中文版 fL]MQUazf@  
      源代碼用例(都是Java Application): 5)`J6fs}  
        仿真Windows記事本 .E$  
        連連看 @Elps8~+b  
      上述的兩個源代碼在賽迪網上可以找到。關于JDK的配置、安裝和Eclipse的安裝本文不再贅述,讀者可以很容易地找到相關的資料。本文只講使用Eclipse來導入源代碼的方法。 WgdGwabFo  
      首先確保你的工作空間已經創建好,在我的機器上工作空間所在的目錄是“e:\workspace”。源代碼存放路徑假設為“我的文檔\cai\Java\一個仿windows的記事本”和“我的文檔\cai\Java\連連看\kyodai”。 OwF]?t=F|  
      下面開始介紹導入源碼的方法。 f51^7|W4#  
      ◎選擇菜單“文件/新建/項目”,選擇“Java項目”,彈出“創建Java項目”對話框。 lf 8ja6M  
      ◎在“創建Java項目”中輸入“項目名”,項目名可以為任意名字,不必和main類的名字一樣。這里假設為“Notepad”。 U 6icH I  
        在“內容”中有兩個單選按鈕,視你的需要來使用。其中“在工作空間中創建新項目”會在你的工作空間中創建一個新目錄,目錄名與項目名一致;“從現有資源創建項目”可以在源碼所在目錄中直接編譯運行,生成的class文件也會存放在源碼目錄中。  @c2'   
      ~dL[qP  
      下面先講“在工作空間中創建新項目”的方法。 ,$_1,e   
      ◎在“內容”中單選“在工作空間中創建新項目”,點擊“完成”按鈕。此時在e:\workspace下生成Notepad目錄。 )z8.W$l  
      ◎下面需要導入源碼到工作空間去。選擇菜單“文件/導入”,選擇“文件系統”。彈出“文件系統”對話框。 NNFl;^X  
      ◎在“從目錄”中選擇源碼存放目錄“我的文檔\cai\Java\一個仿windows的記事本”,勾選上所有的.java源碼,其他的垃圾不要選上。下面又有兩個選擇:對話框下方有兩個選項,若選擇“創建完整的文件夾結構”,就會按源碼所存放的路徑完整地在工作空間中創建目錄,創建完畢會由于main方法類的路徑不對而在包資源管理器內的圖標上顯示叉叉,無法編譯成功;若選擇“只創建選擇的文件夾”,則會在工作空間創建的目錄下導入所有的文件而不會保留原目錄,此時可以編譯成功。 _GPk^))  
      ◎若選擇“創建完整的文件夾結構”,導入完成后,選擇菜單“項目/屬性/Java構建路徑/源代碼”,點擊“添加文件夾”,把子目錄逐級點開,直到源代碼所在這級目錄為止,勾選上該目錄。彈出的提示對話框點擊確定即可。注意上級目錄不要勾選,否則會報錯。這樣這種方法導入的源碼也可以編譯運行了。 y90cZ0Y5  
      ◎注意若源代碼中含有子目錄,在main程序中會有import ...的語句,此時要注意import后面的目錄級別,在選擇Java構建路徑時要勾選上import指明的上級目錄名。例如,連連看代碼中有子目錄topbar,在main程序中有import kyodai.topbar語句,那么就要勾選到“我的文檔\cai\Java\連連看\”這級目錄,而非源碼所在的“我的文檔\cai\Java\連連看\kyodai”目錄。 }C. ?$i_  
      ◎在連連看源碼中,作者已經把所有源碼都打包成了一個Jar,此時只需要添加該Jar包而不需要導入其他源碼就可以運行了(但不能調試,因為Jar包中不含源碼)。方法是創建完新項目后,選擇菜單“項目/屬性/Java構建路徑”,點擊“庫”頁,點擊“添加外部JAR”按鈕,選擇源碼自帶的Jar包即可運行。 ?f[*aQ%  
      KwZ:"=3hk  
      下面介紹“從現有資源創建項目”的方法。 n^c3\F~xg  
      ◎在“創建Java項目”對話框中,點擊“下一步”按鈕,彈出“Java設置”對話框。 x|Gr/ 1  
      ◎在“Java設置”對話框中選擇“庫”頁,選擇“添加JAR”,若找不到隨源碼提供的Jar包,就選擇“添加外部JAR”。一般如果Jar存放的目錄正確,在“添加JAR”中是可以找到該條目的。雙擊出現的Jar包即可添加進去。若不需要額外的庫支持,則點擊“完成”。 [Z/gQ RR|  
      w~Oy9.Br  
      這樣,用上面兩種方法創建的項目就可以編譯運行了。下面就介紹運行的方法。 sz.Ow*zx  
      ◎選擇菜單“Run/運行”,彈出“創建、管理和運行配置”對話框。 GQcz \  
      ◎根據源碼的種類在左邊的列表中進行選擇。我們用的兩個例子都是Java應用程序,所以雙擊“Java應用程序”,在對話框右邊可以輸入運行的配置。 3#1U2uKW&  
      ◎如果新建了項目還沒有運行過,那么右邊的“項目”欄缺省值即為剛創建的項目。在“名稱”欄中輸入運行配置的名稱,建議與項目名稱一致(也可以不一致),不能與其他的運行配置重名。 L&Rxv2G  
      ◎點擊“Main類”欄右方的“搜索”按鈕,一般只有一個main類,在彈出的對話框中雙擊下面那個欄目的main類即可。如果需要以其他的main方法做為程序入口,可以勾選上“Main類”欄下方的兩個復選框,選擇其他的入口。 V3GyK"4\U9  
      ◎如果需要增加特殊的環境變量,例如有的源碼可能需要添加classpath環境變量,則可以在“環境”頁中添加。 '#Fu@zb  
      ◎運行配置中的內容也會同樣反映在調試配置中,運行和調試使用相同的配置。 @ vcOG&+j  
      c_JQIv\@  
      創建了一堆新項目后,包資源管理器中會有一堆亂七八糟項目,有些是你需要的,有些是早已廢棄不用的,你可以刪除那些不用的項目,方法是右鍵點擊該項目,選擇“刪除”。這里要提醒讀者一下的是,刪除對話框有兩個選項,問你是否刪除該項目目錄下的內容,缺省是“不刪除內容”,如果選擇刪除,那么那個目錄就整個被刪除掉了,如果你這個目錄下的東西還有用,那你只好哭了。 .ag-Y4y  
      刪除掉沒用的項目后,運行/調試對話框中多余的配置也可以刪除,方法是右鍵點擊不用的配置名,選擇刪除。 H0QCprVN%S  
      nkBA.AS  
      好了,這是我初學Eclipse的一些心得,希望能對廣大想要使用Eclipse又擔心它煩瑣的初學者有些幫助。
    posted @ 2008-07-04 16:26 xyz 閱讀(9188) | 評論 (1)編輯 收藏
    作者:xyz

    xml在java語言中的三大作用:
    1)對小批量數據的格式化存儲
    2)數據的傳遞,包括從服務器向IE瀏覽器傳遞
    3)用戶構造java web程序的展現技術
    ===========================
    XML的存儲
    1)xml 配置文件
    如,自定義一個實現有關數據庫連接的XML文件
    然后寫一個讀取配置文件的java程序,可以利用DOM,SAX等API實現XML的讀取和解析。

    2)xml數據庫
        DOM 接口對XML文件的地位與JDBC相對于關系數據庫的地位相當,都是數據操作的統一接口。可以通過DOM接口,將XML文件中的數據套上特定界面模板顯示在IE瀏覽器中。

    XML的生成
    1)通過標記完成XML數據發布(提取數據庫數據,并將所提取數據按照字符串拼接形式發布成XML)
    2)通過程序語句完成XML數據發布(思路核心首先在內存中將指定數據生成document對象,然后再將比Document對象保存到硬盤上,從而實現XML文件的創建)這種創建方式要比字符串拼接方式穩定而高效。

    網頁數據展現
    1)傳統數據展現技術
        jsp asp html
    2)XML服務器數據展現

    3)XML客戶端數據展現
    將XML數據文件和相應的XSL展現文件同時發送到客戶瀏覽器上,借助客戶瀏覽上的XSLT引擎,將XML文件轉變為HTML文件顯示

    由于這種設計方法實現了數據和樣式的徹底分離,因此程序的運行效率和維護效率有根本性提高。
    2008年6月12日16:57:39
    posted @ 2008-06-12 16:58 xyz 閱讀(205) | 評論 (0)編輯 收藏

    來源:http://macrochen.blogdriver.com/macrochen/1207263.html

    (一)環境說明
    (1)服務器有4臺,一臺安裝apache,三臺安裝tomcat
    (2)apache2.0.55、tomcat5.5.15、jk2.0.4、jdk1.5.6或jdk1.4.2
    (3)ip配置,一臺安裝apache的ip為192.168.0.88,三臺安裝tomcat的服務器ip分別為192.168.0.1/2/4
    (二)安裝過程
    (1)在三臺要安裝tomcat的服務器上先安裝jdk
    (2)配置jdk的安裝路徑,在環境變量path中加入jdk的bin路徑,新建環境變量JAVA_HOME指向jdk的安裝路徑
    (3)在三臺要安裝tomcat的服務器上分別安裝tomcat,調試三個tomcat到能夠正常啟動
    (4)tomcat的默認WEB服務端口是8080,默認的模式是單獨服務,我的三個tomcat的WEB服務端口修改為7080/8888/9999
    修改位置為tomcat的安裝目錄下的conf/server.xml
    修改前的配置為
        <Connector port="8080" maxHttpHeaderSize="8192"
                   maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
                   enableLookups="false" redirectPort="8443" acceptCount="100"
                   connectionTimeout="20000" disableUploadTimeout="true" />
    修改后的配置為
        <Connector port="7080" maxHttpHeaderSize="8192"
                   maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
                   enableLookups="false" redirectPort="8443" acceptCount="100"
                   connectionTimeout="20000" disableUploadTimeout="true" />
    依次修改每個tomcat的監聽端口(7080/8888/9999)

    (5)分別測試每個tomcat的啟動是否正常
    http://192.168.0.1:7080
    http://192.168.0.2:8888
    http://192.168.0.4:9999
    (三)負載均衡配置過程
    (1)在那臺要安裝apache的服務器上安裝apache2.0.55,我的安裝路徑為默認C:\Program Files\Apache Group\Apache2
    (2)安裝后測試apache能否正常啟動,調試到能夠正常啟動http://192.168.0.88
    (3)下載jk2.0.4后解壓縮文件
    (4)將解壓縮后的目錄中的modules目錄中的mod_jk2.so文件復制到apache的安裝目錄下的modules目錄中,我的為C:\Program Files\Apache Group\Apache2\modules
    (5)修改apache的安裝目錄中的conf目錄的配置文件httpd.conf,在文件中加LoadModule模塊配置信息的最后加上一句LoadModule jk2_module modules/mod_jk2.so
    (6)分別修改三個tomcat的配置文件conf/server.xml,修改內容如下
    修改前
        <!-- An Engine represents the entry point (within Catalina) that processes
             every request.  The Engine implementation for Tomcat stand alone
             analyzes the HTTP headers included with the request, and passes them
             on to the appropriate Host (virtual host). -->

        <!-- You should set jvmRoute to support load-balancing via AJP ie :
        <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1">        
        -->
            
        <!-- Define the top level container in our container hierarchy -->
        <Engine name="Catalina" defaultHost="localhost">
    修改后
        <!-- An Engine represents the entry point (within Catalina) that processes
             every request.  The Engine implementation for Tomcat stand alone
             analyzes the HTTP headers included with the request, and passes them
             on to the appropriate Host (virtual host). -->

        <!-- You should set jvmRoute to support load-balancing via AJP ie :-->
        <Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1">        
        
            
        <!-- Define the top level container in our container hierarchy
        <Engine name="Catalina" defaultHost="localhost">
        -->
    將其中的jvmRoute="jvm1"分別修改為jvmRoute="tomcat1"和jvmRoute="tomcat2"和jvmRoute="tomcat3"

    (7)然后重啟三個tomcat,調試能夠正常啟動。
    (8)在apache的安裝目錄中的conf目錄下創建文件workers2.propertie,寫入文件內容如下

    # fine the communication channel
    [channel.socket:192.168.0.1:8009]
    info=Ajp13 forwarding over socket
    #配置第一個服務器
    tomcatId=tomcat1 #要和tomcat的配置文件server.xml中的jvmRoute="tomcat1"名稱一致
    debug=0
    lb_factor=1 #負載平衡因子,數字越大請求被分配的幾率越高

    # Define the communication channel
    [channel.socket:192.168.0.2:8009]
    info=Ajp13 forwarding over socket
    tomcatId=tomcat2
    debug=0
    lb_factor=1

    # Define the communication channel
    [channel.socket:192.168.0.4:8009]
    info=Ajp13 forwarding over socket
    tomcatId=tomcat3
    debug=0
    lb_factor=1

    [status:]
    info=Status worker, displays runtime information.  

    [uri:/jkstatus.jsp]
    info=Display status information and checks the config file for changes.
    group=status:

    [uri:/*]
    info=Map the whole webapp
    debug=0
    (9)在三個tomcat的安裝目錄中的webapps建立相同的應用,我和應用目錄名為TomcatDemo,在三個應用目錄中建立相同 WEB-INF目錄和頁面index.jsp,index.jsp的頁面內容如下
    <%@ page contentType="text/html; charset=GBK" %>
    <%@ page import="java.util.*" %>
    <html><head><title>Cluster App Test</title></head>
    <body>
    Server Info:
    <%
    out.println(request.getLocalAddr() + " : " + request.getLocalPort()+"<br>");%>
    <%
      out.println("<br> ID " + session.getId()+"<br>");

      // 如果有新的 Session 屬性設置
      String dataName = request.getParameter("dataName");
      if (dataName != null && dataName.length() > 0) {
         String dataValue = request.getParameter("dataValue");
         session.setAttribute(dataName, dataValue);
      }

      out.print("<b>Session 列表</b>");

      Enumeration e = session.getAttributeNames();
      while (e.hasMoreElements()) {
         String name = (String)e.nextElement();
         String value = session.getAttribute(name).toString();
         out.println( name + " = " + value+"<br>");
             System.out.println( name + " = " + value);
       }
    %>
      <form action="index.jsp" method="POST">
        名稱:<input type=text size=20 name="dataName">
         <br>
        值:<input type=text size=20 name="dataValue">
         <br>
        <input type=submit>
       </form>
    </body>
    </html>
    (10)重啟apache服務器和三個tomcat服務器,到此負載 均衡已配置完成。測試負載均衡先測試apache,訪問http://192.168.0.88/jkstatus.jsp
    能否正常訪問,并查詢其中的內容,有三個tomcat的相關配置信息和負載說明,訪問http://192.168.0.88/TomcatDemo/index.jsp看能夠運行,
    能運行,則已建立負載均衡。
    (四)tomcat集群配置
    (1)負載均衡配置的條件下配置tomcat集群
    (2)分別修改三個tomcat的配置文件conf/server.xml,修改內容如下
    修改前
            <!--
            <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                     managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                     expireSessionsOnShutdown="false"
                     useDirtyFlag="true"
                     notifyListenersOnReplication="true">

                <Membership
                    className="org.apache.catalina.cluster.mcast.McastService"
                    mcastAddr="228.0.0.4"
                    mcastPort="45564"
                    mcastFrequency="500"
                    mcastDropTime="3000"/>

                <Receiver
                    className="org.apache.catalina.cluster.tcp.ReplicationListener"
                    tcpListenAddress="auto"
                    tcpListenPort="4001"
                    tcpSelectorTimeout="100"
                    tcpThreadCount="6"/>

                <Sender
                    className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                    replicationMode="pooled"
                    ackTimeout="5000"/>

                <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                       filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
                      
                <Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"
                          tempDir="/tmp/war-temp/"
                          deployDir="/tmp/war-deploy/"
                          watchDir="/tmp/war-listen/"
                          watchEnabled="false"/>
                          
                <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
            </Cluster>
            -->  
    修改后
            <!-- modify by whh -->
            <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                     managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                     expireSessionsOnShutdown="false"
                     useDirtyFlag="true"
                     notifyListenersOnReplication="true">

                <Membership
                    className="org.apache.catalina.cluster.mcast.McastService"
                    mcastAddr="228.0.0.4"
                    mcastPort="45564"
                    mcastFrequency="500"
                    mcastDropTime="3000"/>

                <Receiver
                    className="org.apache.catalina.cluster.tcp.ReplicationListener"
                    tcpListenAddress="auto"
                    tcpListenPort="4001"
                    tcpSelectorTimeout="100"
                    tcpThreadCount="6"/>

                <Sender
                    className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                    replicationMode="pooled"
                    ackTimeout="5000"/>

                <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                       filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
                      
                <Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"
                          tempDir="/tmp/war-temp/"
                          deployDir="/tmp/war-deploy/"
                          watchDir="/tmp/war-listen/"
                          watchEnabled="false"/>
                          
                <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
            </Cluster>
           <!-- modify by whh -->      
    將集群配置選項的注釋放開即可,如上。
    (3)重啟三個tomcat。到此tomcat的集群已配置完成。

    (五)應用配置
    對于要進行負載和集群的的tomcat目錄下的webapps中的應用中的WEB-INF中的web.xml文件要添加如下一句配置
    <distributable/>
    配置前
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
      <display-name>TomcatDemo</display-name>
    </web-app>
    配置后
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
      <display-name>TomcatDemo</display-name>
       <distributable/>
    </web-app>

    posted @ 2008-05-13 19:25 xyz 閱讀(544) | 評論 (0)編輯 收藏

    來源:http://blog.csdn.net/ladofwind/archive/2006/08/29/1138484.aspx

      在單一的服務器上執行WEB應用程序有一些重大的問題,當網站成功建成并開始接受大量請求時,單一服務器終究無法滿足需要處理的負荷量,所以就有點顯得有點力不從心了。另外一個常見的問題是會產生單點故障,如果該服務器壞掉,那么網站就立刻無法運作了。不論是因為要有較佳的擴充性還是容錯能力,我們都會想在一臺以上的服務器計算機上執行WEB應用程序。所以,這時候我們就需要用到集群這一門技術了。

             在進入集群系統架構探討之前,先定義一些專門術語:
    1. 集群(Cluster):是一組獨立的計算機系統構成一個松耦合的多處理器系統,它們之間通過網絡實現進程間的通信。應用程序可以通過網絡共享內存進行消息傳送,實現分布式計算機。 
    2. 負載均衡(Load Balance):先得從集群講起,集群就是一組連在一起的計算機,從外部看它是一個系統,各節點可以是不同的操作系統或不同硬件構成的計算機。如一個提供Web服務的集群,對外界來看是一個大Web服務器。不過集群的節點也可以單獨提供服務。
    3. 特點:在現有網絡結構之上,負載均衡提供了一種廉價有效的方法擴展服務器帶寬和增加吞吐量,加強網絡數據處理能力,提高網絡的靈活性和可用性。集群系統(Cluster)主要解決下面幾個問題: 
    高可靠性(HA):利用集群管理軟件,當主服務器故障時,備份服務器能夠自動接管主服務器的工作,并及時切換過去,以實現對用戶的不間斷服務。
    高性能計算(HP):即充分利用集群中的每一臺計算機的資源,實現復雜運算的并行處理,通常用于科學計算領域,比如基因分析,化學分析等。 
    負載平衡:即把負載壓力根據某種算法合理分配到集群中的每一臺計算機上,以減輕主服務器的壓力,降低對主服務器的硬件和軟件要求。

    目前比較常用的負載均衡技術主要有: 
      1. 基于DNS的負載均衡 
      通過DNS服務中的隨機名字解析來實現負載均衡,在DNS服務器中,可以為多個不同的地址配置同一個名字,而最終查詢這個名字的客戶機將在解析這個名字時得到其中一個地址。因此,對于同一個名字,不同的客戶機會得到不同的地址,他們也就訪問不同地址上的Web服務器,從而達到負載均衡的目的。 

      2. 反向代理負載均衡 (如Apache+JK2+Tomcat這種組合)
      使用代理服務器可以將請求轉發給內部的Web服務器,讓代理服務器將請求均勻地轉發給多臺內部Web服務器之一上,從而達到負載均衡的目的。這種代理方式與普通的代理方式有所不同,標準代理方式是客戶使用代理訪問多個外部Web服務器,而這種代理方式是多個客戶使用它訪問內部Web服務器,因此也被稱為反向代理模式。

      3. 基于NAT(Network Address Translation)的負載均衡技術 (如Linux Virtual Server,簡稱LVS)
      網絡地址轉換為在內部地址和外部地址之間進行轉換,以便具備內部地址的計算機能訪問外部網絡,而當外部網絡中的計算機訪問地址轉換網關擁有的某一外部地址時,地址轉換網關能將其轉發到一個映射的內部地址上。因此如果地址轉換網關能將每個連接均勻轉換為不同的內部服務器地址,此后外部網絡中的計算機就各自與自己轉換得到的地址上服務器進行通信,從而達到負載分擔的目的。

    介紹完上面的集群技術之后,下面就基于Tomcat的集群架構方案進行說明:

    上面是采用了Apache httpd作為web服務器的,即作為Tomcat的前端處理器,根據具體情況而定,有些情況下是不需要Apache httpd作為 web 服務器的,如系統展現沒有靜態頁面那就不需要Apache httpd,那時可以直接使用Tomcat作為web 服務器來使用。使用Apache httpd主要是它在處理靜態頁面方面的能力比Tomcat強多了。
    1、 用戶的網頁瀏覽器做完本地 DNS和企業授權的DNS之的請求/響應后,這時候企業授權的DNS(即21cn BOSS DNS)會給用戶本地的DNS服務器提供一個NAT請求分配器(即網關)IP。


    2、 NAT分配器,它會根據特定的分配算法,來決定要將連接交給哪一臺內部 Apache httpd來處理請求。大多數的NAT請求分配器提供了容錯能力:根據偵測各種WEB服務器的失效狀況,停止將請求分配給已經宕掉的服務器。并且有些分配器還可以監測到WEB服務器機器的負載情況,并將請求分配給負載最輕的服務器等等。Linux Virtual Server是一個基于Linux操作系統上執行的VS-NAT開源軟件套件,而且它有豐富的功能和良好的說明文件。商業硬件解決方案 Foundry Networks的ServerIron是目前業界公認最佳的請求分配器之一。


    3、 Apache httpd + Mod_JK2在這里是作為負載均衡器,那為什么要做集群呢?如果集群系統要具備容錯能力,以便在任何單一的硬件或軟件組件失效時還能100%可用,那么集群系統必須沒有單點故障之憂。所以,不能只架設一臺有mod_jk2的Apache httpd,因為如果 httpd或mod_jk2失效了,將不會再有請求被會送交到任何一個Tomcat 實例。這種情況下,Apache httpd就是瓶勁,特別在訪問量大的網站。


    4、 Mod_JK2負載均衡與故障復原,決定把Apache httpd當成web服務器,而且使用mod_jk2將請求傳送給Tomcat,則可以使用mod_jk2的負載均衡與容錯功能。在集群系統中,帶有mod_jk2的Apache httpd可以做的事情包括:
    A、 將請求分配至一或多個Tomcat實例上
    你可以在mod_jk2的workers.properties文件中,設定許多Tomcat實例,并賦于每個實例一個lb_factor值,以作為請求分配的加權因子。


    B、 偵測Tomcat實例是否失敗
    當Tomcat實例的連接器服務不再響應時,mod_jk2會及時偵測到,并停止將請求送給它。其他的Tomcat實例則會接受失效實例的負載。


    C、 偵測Tomcat實例在失效后的何時恢復
    因連接器服務失效,而停止將請求分配給Tomcat實例之后,mod_jk2會周期性地檢查是否已恢復使用性,并自動將其加入現行的Tomcat實例池中。


    5、 Tomcat中的集群原理是通過組播的方式進行節點的查找并使用TCP連接進行會話的復制。這里提示一下就是,對每個請求的處理,Tomcat都會進行會話復制,復制后的會話將會慢慢變得龐大。


    6、 Mod_jk2同時支持會話親和和會話復制。在tomcat 5中如何實現會話親和和會話復制?把server.xml中的<cluster/>標簽去掉就實現會話親和,把<cluster/>標簽加上就實現會話復制。


    7、 會話親和:就是表示來自同會話的所有請求都由相同的Tomcat 實例來處理,這種情況下,如果Tomcat實例或所執行的服務器機器失效,也會喪失Servlet的會話數據。即使在集群系統中執行更多的Tomcat實例,也永遠不會復制會話數據。這樣是提高集群性能的一種方案,但不具備有容錯能力了。


    8、 使用會話復制,則當一個Tomcat實例宕掉時,由于至少還有另一個Tomcat實例保有一份會話狀態數據,因而數據不會喪失。但性能會有所降低。 

    posted @ 2008-05-13 19:23 xyz 閱讀(403) | 評論 (0)編輯 收藏
    僅列出標題  

    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    留言簿

    隨筆檔案(1)

    文章分類(44)

    文章檔案(46)

    收藏夾(1)

    Adobe

    AOP

    API

    appServer

    BI

    c

    • c-free
    • codeblocks
    • codelite
    • CodeLite IDE 是一個強大的開源,跨平臺的 C/C++整合開發環境. 支持包括 Windows、Linux 和 Mac 系統下運行
    • codelite官網
    • dev-c++
    • Dev-C++是一個C&C++開發工具,它是一款自由軟件,遵守GPL協議。
    • GCC
    • GCC 原名為 GNU C 語言編譯器(GNU C Compiler),因為它原本只能處理 C語言。GCC 很快地擴展,變得可處理 C++。之后也變得可處理 Fortran、Pascal、Objective-C、Java, 以及 Ada 與其他語言。

    Cache

    CMS

    DB

    eclipse

    FreeMarker

    hibernate

    html5

    ibatis

    java

    jquery

    js

    json

    Linux

    Log

    mail server

    mobile

    mysql

    oauth

    openID

    other

    PHP

    portal

    report

    Scheduler

    schema

    Security

    SOA

    spring

    struts

    UI原型設計

    w3c

    Wap

    webservice

    xml

    供應鏈管理

    博客鏈接

    好網站

    工作流

    開源網

    招聘

    插件下載

    操作系統

    構建可伸縮的系統

    構建工具

    測試

    • IETest
    • IE官網
    • OpenSTA
    • Siege
    • Siege是一個壓力測試和評測工具,設計用于WEB開發這評估應用在壓力下的承受能力

    游戲

    源碼托管

    經營

    資源

    金融/財務

    搜索

    •  

    最新評論

    主站蜘蛛池模板: 免费精品视频在线| j8又粗又长又硬又爽免费视频| 亚洲色无码专区在线观看| 中国国产高清免费av片| 菠萝菠萝蜜在线免费视频| 亚洲av无码国产精品色午夜字幕| h视频在线免费看| 日韩免费人妻AV无码专区蜜桃| 黄色网址在线免费| 最近中文字幕大全免费版在线 | 中文毛片无遮挡高清免费| 日本激情猛烈在线看免费观看 | 久久精品国产亚洲av日韩| 亚洲一区二区在线视频| 91精品国产亚洲爽啪在线观看| 日韩免费无码一区二区视频| 成人午夜性A级毛片免费| 国产传媒在线观看视频免费观看| 国产高清免费观看| 亚洲高清视频一视频二视频三| 鲁丝片一区二区三区免费| 久久久免费的精品| 一级毛片不卡免费看老司机| 亚洲中文字幕日本无线码| 亚洲一区二区三区在线观看网站| 亚洲国产综合无码一区 | 男人都懂www深夜免费网站| 最近更新免费中文字幕大全| 少妇无码一区二区三区免费| 亚洲免费在线视频播放| 成人午夜视频免费| 日韩中文字幕免费视频| 69堂人成无码免费视频果冻传媒| 成人免费视频小说| 亚洲第一区精品观看| 最新亚洲成av人免费看| 国产aa免费视频| 伊人久久大香线蕉亚洲| 久久久久亚洲AV无码观看| 99久久国产亚洲综合精品| 亚洲av成人无码久久精品|