翻譯:為之漫筆
鏈接:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html
英文原版:http://jibbering.com/faq/faq_notes/closures.html
- Closure
- 所謂“閉包”,指的是一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分。
閉包是 ECMAScript (JavaScript)最強(qiáng)大的特性之一,但用好閉包的前提是必須理解閉包。閉包的創(chuàng)建相對(duì)容易,人們甚至?xí)诓唤?jīng)意間創(chuàng)建閉包,但這些無(wú)意創(chuàng)建的閉包卻存在潛在的危害,尤其是在比較常見的瀏覽器環(huán)境下。如果想要揚(yáng)長(zhǎng)避短地使用閉包這一特性,則必須了解它們的工作機(jī)制。而閉包工作機(jī)制的實(shí)現(xiàn)很大程度上有賴于標(biāo)識(shí)符(或者說(shuō)對(duì)象屬性)解析過(guò)程中作用域的角色。
關(guān)于閉包,最簡(jiǎn)單的描述就是 ECMAScript 允許使用內(nèi)部函數(shù)--即函數(shù)定義和函數(shù)表達(dá)式位于另一個(gè)函數(shù)的函數(shù)體內(nèi)。而且,這些內(nèi)部函數(shù)可以訪問(wèn)它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)和聲明的其他內(nèi)部函數(shù)。當(dāng)其中一個(gè)這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時(shí),就會(huì)形成閉包。也就是說(shuō),內(nèi)部函數(shù)會(huì)在外部函數(shù)返回后被執(zhí)行。而當(dāng)這個(gè)內(nèi)部函數(shù)執(zhí)行時(shí),它仍然必需訪問(wèn)其外部函數(shù)的局部變量、參數(shù)以及其他內(nèi)部函數(shù)。這些局部變量、參數(shù)和函數(shù)聲明(最初時(shí))的值是外部函數(shù)返回時(shí)的值,但也會(huì)受到內(nèi)部函數(shù)的影響。
遺憾的是,要適當(dāng)?shù)乩斫忾]包就必須理解閉包背后運(yùn)行的機(jī)制,以及許多相關(guān)的技術(shù)細(xì)節(jié)。雖然本文的前半部分并沒有涉及 ECMA 262 規(guī)范指定的某些算法,但仍然有許多無(wú)法回避或簡(jiǎn)化的內(nèi)容。對(duì)于個(gè)別熟悉對(duì)象屬性名解析的人來(lái)說(shuō),可以跳過(guò)相關(guān)的內(nèi)容,但是除非你對(duì)閉包也非常熟悉,否則最好是不要跳過(guò)下面幾節(jié)。
ECMAScript 認(rèn)可兩類對(duì)象:原生(Native)對(duì)象和宿主(Host)對(duì)象,其中宿主對(duì)象包含一個(gè)被稱為內(nèi)置對(duì)象的原生對(duì)象的子類(ECMA 262 3rd Ed Section 4.3)。原生對(duì)象屬于語(yǔ)言,而宿主對(duì)象由環(huán)境提供,比如說(shuō)可能是文檔對(duì)象、DOM 等類似的對(duì)象。
原生對(duì)象具有松散和動(dòng)態(tài)的命名屬性(對(duì)于某些實(shí)現(xiàn)的內(nèi)置對(duì)象子類別而言,動(dòng)態(tài)性是受限的--但這不是太大的問(wèn)題)。對(duì)象的命名屬性用于保存值,該值可以是指向另一個(gè)對(duì)象(Objects)的引用(在這個(gè)意義上說(shuō),函數(shù)也是對(duì)象),也可以是一些基本的數(shù)據(jù)類型,比如:String、Number、Boolean、Null 或 Undefined。其中比較特殊的是 Undefined 類型,因?yàn)榭梢越o對(duì)象的屬性指定一個(gè) Undefined 類型的值,而不會(huì)刪除對(duì)象的相應(yīng)屬性。而且,該屬性只是保存著 undefined
值。
下面簡(jiǎn)要介紹一下如何設(shè)置和讀取對(duì)象的屬性值,并最大程度地體現(xiàn)相應(yīng)的內(nèi)部細(xì)節(jié)。
對(duì)象的命名屬性可以通過(guò)為該命名屬性賦值來(lái)創(chuàng)建,或重新賦值。即,對(duì)于:
var objectRef = new Object(); //創(chuàng)建一個(gè)普通的 javascript 對(duì)象。
可以通過(guò)下面語(yǔ)句來(lái)創(chuàng)建名為 “testNumber” 的屬性:
objectRef.testNumber = 5;
/* - 或- */
objectRef["testNumber"] = 5;
在賦值之前,對(duì)象中沒有“testNumber” 屬性,但在賦值后,則創(chuàng)建一個(gè)屬性。之后的任何賦值語(yǔ)句都不需要再創(chuàng)建這個(gè)屬性,而只會(huì)重新設(shè)置它的值:
objectRef.testNumber = 8;
/* - 或- */
objectRef["testNumber"] = 8;
稍后我們會(huì)介紹,Javascript 對(duì)象都有原型(prototypes)屬性,而這些原型本身也是對(duì)象,因而也可以帶有命名的屬性。但是,原型對(duì)象命名屬性的作用并不體現(xiàn)在賦值階段。同樣,在將值賦給其命名屬性時(shí),如果對(duì)象沒有該屬性則會(huì)創(chuàng)建該命名屬性,否則會(huì)重設(shè)該屬性的值。
當(dāng)讀取對(duì)象的屬性值時(shí),原型對(duì)象的作用便體現(xiàn)出來(lái)。如果對(duì)象的原型中包含屬性訪問(wèn)器(property accessor)所使用的屬性名,那么該屬性的值就會(huì)返回:
/* 為命名屬性賦值。如果在賦值前對(duì)象沒有相應(yīng)的屬性,那么賦值后就會(huì)得到一個(gè):*/ objectRef.testNumber = 8; /* 從屬性中讀取值 */ var val = objectRef.testNumber; /* 現(xiàn)在, - val - 中保存著剛賦給對(duì)象命名屬性的值 8*/
而且,由于所有對(duì)象都有原型,而原型本身也是對(duì)象,所以原型也可能有原型,這樣就構(gòu)成了所謂的原型鏈。原型鏈終止于鏈中原型為 null 的對(duì)象。Object
構(gòu)造函數(shù)的默認(rèn)原型就有一個(gè) null 原型,因此:
var objectRef = new Object(); //創(chuàng)建一個(gè)普通的 JavaScript 對(duì)象。
創(chuàng)建了一個(gè)原型為 Object.prototype
的對(duì)象,而該原型自身則擁有一個(gè)值為 null 的原型。也就是說(shuō),objectRef
的原型鏈中只包含一個(gè)對(duì)象-- Object.prototype
。但對(duì)于下面的代碼而言:
/* 創(chuàng)建 - MyObject1 - 類型對(duì)象的函數(shù)*/ function MyObject1(formalParameter){ /* 給創(chuàng)建的對(duì)象添加一個(gè)名為 - testNumber - 的屬性并將傳遞給構(gòu)造函數(shù)的第一個(gè)參數(shù)指定為該屬性的值:*/ this.testNumber = formalParameter; } /* 創(chuàng)建 - MyObject2 - 類型對(duì)象的函數(shù)*/ function MyObject2(formalParameter){ /* 給創(chuàng)建的對(duì)象添加一個(gè)名為 - testString - 的屬性并將傳遞給構(gòu)造函數(shù)的第一個(gè)參數(shù)指定為該屬性的值:*/ this.testString = formalParameter; } /* 接下來(lái)的操作用 MyObject1 類的實(shí)例替換了所有與 MyObject2 類的實(shí)例相關(guān)聯(lián)的原型。而且,為 MyObject1 構(gòu)造函數(shù)傳遞了參數(shù) - 8 - ,因而其 - testNumber - 屬性被賦予該值:*/ MyObject2.prototype = new MyObject1( 8 ); /* 最后,將一個(gè)字符串作為構(gòu)造函數(shù)的第一個(gè)參數(shù), 創(chuàng)建一個(gè) - MyObject2 - 的實(shí)例,并將指向該對(duì)象的 引用賦給變量 - objectRef - :*/ var objectRef = new MyObject2( "String_Value" );
被變量 objectRef
所引用的 MyObject2
的實(shí)例擁有一個(gè)原型鏈。該鏈中的第一個(gè)對(duì)象是在創(chuàng)建后被指定給 MyObject2
構(gòu)造函數(shù)的 prototype 屬性的 MyObject1
的一個(gè)實(shí)例。MyObject1
的實(shí)例也有一個(gè)原型,即與 Object.prototype
所引用的對(duì)象對(duì)應(yīng)的默認(rèn)的 Object 對(duì)象的原型。最后, Object.prototype
有一個(gè)值為 null 的原型,因此這條原型鏈到此結(jié)束。
當(dāng)某個(gè)屬性訪問(wèn)器嘗試讀取由 objectRef
所引用的對(duì)象的屬性值時(shí),整個(gè)原型鏈都會(huì)被搜索。在下面這種簡(jiǎn)單的情況下:
var val = objectRef.testString;
因?yàn)?objectRef
所引用的 MyObject2
的實(shí)例有一個(gè)名為“testString”的屬性,因此被設(shè)置為“String_Value”的該屬性的值被賦給了變量 val
。但是:
var val = objectRef.testNumber;
則不能從 MyObject2
實(shí)例自身中讀取到相應(yīng)的命名屬性值,因?yàn)樵搶?shí)例沒有這個(gè)屬性。然而,變量 val
的值仍然被設(shè)置為 8
,而不是未定義--這是因?yàn)樵谠搶?shí)例中查找相應(yīng)的命名屬性失敗后,解釋程序會(huì)繼續(xù)檢查其原型對(duì)象。而該實(shí)例的原型對(duì)象是 MyObject1
的實(shí)例,這個(gè)實(shí)例有一個(gè)名為“testNumber”的屬性并且值為 8
,所以這個(gè)屬性訪問(wèn)器最后會(huì)取得值 8
。而且,雖然 MyObject1
和 MyObject2
都沒有定義 toString
方法,但是當(dāng)屬性訪問(wèn)器通過(guò) objectRef
讀取 toString
屬性的值時(shí):
var val = objectRef.toString;
變量 val
也會(huì)被賦予一個(gè)函數(shù)的引用。這個(gè)函數(shù)就是在 Object.prototype
的 toString
屬性中所保存的函數(shù)。之所以會(huì)返回這個(gè)函數(shù),是因?yàn)榘l(fā)生了搜索objectRef
原型鏈的過(guò)程。當(dāng)在作為對(duì)象的 objectRef
中發(fā)現(xiàn)沒有“toString”屬性存在時(shí),會(huì)搜索其原型對(duì)象,而當(dāng)原型對(duì)象中不存在該屬性時(shí),則會(huì)繼續(xù)搜索原型的原型。而原型鏈中最終的原型是 Object.prototype
,這個(gè)對(duì)象確實(shí)有一個(gè) toString
方法,因此該方法的引用被返回。
最后:
var val = objectRef.madeUpProperty;
返回 undefined
,因?yàn)樵谒阉髟玩湹倪^(guò)程中,直至 Object.prototype
的原型--null,都沒有找到任何對(duì)象有名為“madeUpPeoperty”的屬性,因此最終返回 undefined
。
不論是在對(duì)象或?qū)ο蟮脑椭校x取命名屬性值的時(shí)候只返回首先找到的屬性值。而當(dāng)為對(duì)象的命名屬性賦值時(shí),如果對(duì)象自身不存在該屬性則創(chuàng)建相應(yīng)的屬性。
這意味著,如果執(zhí)行像 objectRef.testNumber = 3
這樣一條賦值語(yǔ)句,那么這個(gè) MyObject2
的實(shí)例自身也會(huì)創(chuàng)建一個(gè)名為“testNumber”的屬性,而之后任何讀取該命名屬性的嘗試都將獲得相同的新值。這時(shí)候,屬性訪問(wèn)器不會(huì)再進(jìn)一步搜索原型鏈,但 MyObject1
實(shí)例值為 8
的“testNumber”屬性并沒有被修改。給 objectRef
對(duì)象的賦值只是遮擋了其原型鏈中相應(yīng)的屬性。
注意:ECMAScript 為 Object 類型定義了一個(gè)內(nèi)部 [[prototype]]
屬性。這個(gè)屬性不能通過(guò)腳本直接訪問(wèn),但在屬性訪問(wèn)器解析過(guò)程中,則需要用到這個(gè)內(nèi)部 [[prototype]]
屬性所引用的對(duì)象鏈--即原型鏈。可以通過(guò)一個(gè)公共的 prototype
屬性,來(lái)對(duì)與內(nèi)部的 [[prototype]]
屬性對(duì)應(yīng)的原型對(duì)象進(jìn)行賦值或定義。這兩者之間的關(guān)系在 ECMA 262(3rd edition)中有詳細(xì)描述,但超出了本文要討論的范疇。
執(zhí)行環(huán)境是 ECMAScript 規(guī)范(ECMA 262 第 3 版)用于定義 ECMAScript 實(shí)現(xiàn)必要行為的一個(gè)抽象的概念。對(duì)如何實(shí)現(xiàn)執(zhí)行環(huán)境,規(guī)范沒有作規(guī)定。但由于執(zhí)行環(huán)境中包含引用規(guī)范所定義結(jié)構(gòu)的相關(guān)屬性,因此執(zhí)行環(huán)境中應(yīng)該保有(甚至實(shí)現(xiàn))帶有屬性的對(duì)象--即使屬性不是公共屬性。
所有 JavaScript 代碼都是在一個(gè)執(zhí)行環(huán)境中被執(zhí)行的。全局代碼(作為內(nèi)置的 JS 文件執(zhí)行的代碼,或者 HTML 頁(yè)面加載的代碼)是在我將稱之為“全局執(zhí)行環(huán)境”的執(zhí)行環(huán)境中執(zhí)行的,而對(duì)函數(shù)的每次調(diào)用(有可能是作為構(gòu)造函數(shù))同樣有關(guān)聯(lián)的執(zhí)行環(huán)境。通過(guò) eval
函數(shù)執(zhí)行的代碼也有截然不同的執(zhí)行環(huán)境,但因?yàn)?JavaScript 程序員在正常情況下一般不會(huì)使用 eval
,所以這里不作討論。有關(guān)執(zhí)行環(huán)境的詳細(xì)說(shuō)明請(qǐng)參閱 ECMA 262(第 3 版)第 10.2 節(jié)。
當(dāng)調(diào)用一個(gè) JavaScript 函數(shù)時(shí),該函數(shù)就會(huì)進(jìn)入相應(yīng)的執(zhí)行環(huán)境。如果又調(diào)用了另外一個(gè)函數(shù)(或者遞歸地調(diào)用同一個(gè)函數(shù)),則又會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并且在函數(shù)調(diào)用期間執(zhí)行過(guò)程都處于該環(huán)境中。當(dāng)調(diào)用的函數(shù)返回后,執(zhí)行過(guò)程會(huì)返回原始執(zhí)行環(huán)境。因而,運(yùn)行中的 JavaScript 代碼就構(gòu)成了一個(gè)執(zhí)行環(huán)境棧。
在創(chuàng)建執(zhí)行環(huán)境的過(guò)程中,會(huì)按照定義的先后順序完成一系列操作。首先,在一個(gè)函數(shù)的執(zhí)行環(huán)境中,會(huì)創(chuàng)建一個(gè)“活動(dòng)”對(duì)象。活動(dòng)對(duì)象是規(guī)范中規(guī)定的另外一種機(jī)制。之所以稱之為對(duì)象,是因?yàn)樗鼡碛锌稍L問(wèn)的命名屬性,但是它又不像正常對(duì)象那樣具有原型(至少?zèng)]有預(yù)定義的原型),而且不能通過(guò) JavaScript 代碼直接引用活動(dòng)對(duì)象。
為函數(shù)調(diào)用創(chuàng)建執(zhí)行環(huán)境的下一步是創(chuàng)建一個(gè) arguments
對(duì)象,這是一個(gè)類似數(shù)組的對(duì)象,它以整數(shù)索引的數(shù)組成員一一對(duì)應(yīng)地保存著調(diào)用函數(shù)時(shí)所傳遞的參數(shù)。這個(gè)對(duì)象也有 length
和 callee
屬性(這兩個(gè)屬性與我們討論的內(nèi)容無(wú)關(guān),詳見規(guī)范)。然后,會(huì)為活動(dòng)對(duì)象創(chuàng)建一個(gè)名為“arguments”的屬性,該屬性引用前面創(chuàng)建的 arguments
對(duì)象。
接著,為執(zhí)行環(huán)境分配作用域。作用域由對(duì)象列表(鏈)組成。每個(gè)函數(shù)對(duì)象都有一個(gè)內(nèi)部的 [[scope]]
屬性(該屬性我們稍后會(huì)詳細(xì)介紹),這個(gè)屬性也由對(duì)象列表(鏈)組成。指定給一個(gè)函數(shù)調(diào)用執(zhí)行環(huán)境的作用域,由該函數(shù)對(duì)象的 [[scope]]
屬性所引用的對(duì)象列表(鏈)組成,同時(shí),活動(dòng)對(duì)象被添加到該對(duì)象列表的頂部(鏈的前端)。
之后會(huì)發(fā)生由 ECMA 262 中所謂“可變”對(duì)象完成的“變量實(shí)例化”的過(guò)程。只不過(guò)此時(shí)使用活動(dòng)對(duì)象作為可變對(duì)象(這里很重要,請(qǐng)注意:它們是同一個(gè)對(duì)象)。此時(shí)會(huì)將函數(shù)的形式參數(shù)創(chuàng)建為可變對(duì)象命名屬性,如果調(diào)用函數(shù)時(shí)傳遞的參數(shù)與形式參數(shù)一致,則將相應(yīng)參數(shù)的值賦給這些命名屬性(否則,會(huì)給命名屬性賦 undefined
值)。對(duì)于定義的內(nèi)部函數(shù),會(huì)以其聲明時(shí)所用名稱為可變對(duì)象創(chuàng)建同名屬性,而相應(yīng)的內(nèi)部函數(shù)則被創(chuàng)建為函數(shù)對(duì)象并指定給該屬性。變量實(shí)例化的最后一步是將在函數(shù)內(nèi)部聲明的所有局部變量創(chuàng)建為可變對(duì)象的命名屬性。
根據(jù)聲明的局部變量創(chuàng)建的可變對(duì)象的屬性在變量實(shí)例化過(guò)程會(huì)被賦予 undefined
值。在執(zhí)行函數(shù)體內(nèi)的代碼、并計(jì)算相應(yīng)的賦值表達(dá)式之前不會(huì)對(duì)局部變量執(zhí)行真正的實(shí)例化。
事實(shí)上,擁有 arguments
屬性的活動(dòng)對(duì)象和擁有與函數(shù)局部變量對(duì)應(yīng)的命名屬性的可變對(duì)象是同一個(gè)對(duì)象。因此,可以將標(biāo)識(shí)符 arguments
作為函數(shù)的局部變量來(lái)看待。
最后,在this可以被使用之前,還必須先對(duì)其賦值。如果賦的值是一個(gè)對(duì)象的引用,則 this.m 訪問(wèn)的便是該對(duì)象上的 m。如果(內(nèi)部)賦的值是 null,則this就指向全局對(duì)象。 (此段由 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. )
創(chuàng)建全局執(zhí)行環(huán)境的過(guò)程會(huì)稍有不同,因?yàn)樗鼪]有參數(shù),所以不需要通過(guò)定義的活動(dòng)對(duì)象來(lái)引用這些參數(shù)。但全局執(zhí)行環(huán)境也需要一個(gè)作用域,而它的作用域鏈實(shí)際上只由一個(gè)對(duì)象--全局對(duì)象--組成。全局執(zhí)行環(huán)境也會(huì)有變量實(shí)例化的過(guò)程,它的內(nèi)部函數(shù)就是涉及大部分 JavaScript 代碼的、常規(guī)的頂級(jí)函數(shù)聲明。而且,在變量實(shí)例化過(guò)程中全局對(duì)象就是可變對(duì)象,這就是為什么全局性聲明的函數(shù)是全局對(duì)象屬性的原因。全局性聲明的變量同樣如此。
全局執(zhí)行環(huán)境也會(huì)使用 this
對(duì)象來(lái)引用全局對(duì)象。
調(diào)用函數(shù)時(shí)創(chuàng)建的執(zhí)行環(huán)境會(huì)包含一個(gè)作用域鏈,這個(gè)作用域鏈?zhǔn)峭ㄟ^(guò)將該執(zhí)行環(huán)境的活動(dòng)(可變)對(duì)象添加到保存于所調(diào)用函數(shù)對(duì)象的 [[scope]]
屬性中的作用域鏈前端而構(gòu)成的。所以,理解函數(shù)對(duì)象內(nèi)部的 [[scope]]
屬性的定義過(guò)程至關(guān)重要。
在 ECMAScript 中,函數(shù)也是對(duì)象。函數(shù)對(duì)象在變量實(shí)例化過(guò)程中會(huì)根據(jù)函數(shù)聲明來(lái)創(chuàng)建,或者是在計(jì)算函數(shù)表達(dá)式或調(diào)用 Function
構(gòu)造函數(shù)時(shí)創(chuàng)建。
通過(guò)調(diào)用 Function
構(gòu)造函數(shù)創(chuàng)建的函數(shù)對(duì)象,其內(nèi)部的 [[scope]]
屬性引用的作用域鏈中始終只包含全局對(duì)象。
通過(guò)函數(shù)聲明或函數(shù)表達(dá)式創(chuàng)建的函數(shù)對(duì)象,其內(nèi)部的 [[scope]]
屬性引用的則是創(chuàng)建它們的執(zhí)行環(huán)境的作用域鏈。
在最簡(jiǎn)單的情況下,比如聲明如下全局函數(shù):-
function exampleFunction(formalParameter){
... // 函數(shù)體內(nèi)的代碼
}
當(dāng)為創(chuàng)建全局執(zhí)行環(huán)境而進(jìn)行變量實(shí)例化時(shí),會(huì)根據(jù)上面的函數(shù)聲明創(chuàng)建相應(yīng)的函數(shù)對(duì)象。因?yàn)槿謭?zhí)行環(huán)境的作用域鏈中只包含全局對(duì)象,所以它就給自己創(chuàng)建的、并以名為“exampleFunction”的屬性引用的這個(gè)函數(shù)對(duì)象的內(nèi)部 [[scope]]
屬性,賦予了只包含全局對(duì)象的作用域鏈。
當(dāng)在全局環(huán)境中計(jì)算函數(shù)表達(dá)式時(shí),也會(huì)發(fā)生類似的指定作用域鏈的過(guò)程:-
var exampleFuncRef = function(){
... // 函數(shù)體代碼
}
在這種情況下,不同的是在全局執(zhí)行環(huán)境的變量實(shí)例化過(guò)程中,會(huì)先為全局對(duì)象創(chuàng)建一個(gè)命名屬性。而在計(jì)算賦值語(yǔ)句之前,暫時(shí)不會(huì)創(chuàng)建函數(shù)對(duì)象,也不會(huì)將該函數(shù)對(duì)象的引用指定給全局對(duì)象的命名屬性。但是,最終還是會(huì)在全局執(zhí)行環(huán)境中創(chuàng)建這個(gè)函數(shù)對(duì)象(當(dāng)計(jì)算函數(shù)表達(dá)式時(shí)。譯者注),而為這個(gè)創(chuàng)建的函數(shù)對(duì)象的 [[scope]]
屬性指定的作用域鏈中仍然只包含全局對(duì)象。
內(nèi)部的函數(shù)聲明或表達(dá)式會(huì)導(dǎo)致在包含它們的外部函數(shù)的執(zhí)行環(huán)境中創(chuàng)建相應(yīng)的函數(shù)對(duì)象,因此這些函數(shù)對(duì)象的作用域鏈會(huì)稍微復(fù)雜一些。在下面的代碼中,先定義了一個(gè)帶有內(nèi)部函數(shù)聲明的外部函數(shù),然后調(diào)用外部函數(shù):
function exampleOuterFunction(formalParameter){ function exampleInnerFuncitonDec(){ ... // 內(nèi)部函數(shù)體代碼 } ... // 其余的外部函數(shù)體代碼 } exampleOuterFunction( 5 );
與外部函數(shù)聲明對(duì)應(yīng)的函數(shù)對(duì)象會(huì)在全局執(zhí)行環(huán)境的變量實(shí)例化過(guò)程中被創(chuàng)建。因此,外部函數(shù)對(duì)象的 [[scope]]
屬性中會(huì)包含一個(gè)只有全局對(duì)象的“單項(xiàng)目”作用域鏈。
當(dāng)在全局執(zhí)行環(huán)境中調(diào)用 exampleOuterFunction
函數(shù)時(shí),會(huì)為該函數(shù)調(diào)用創(chuàng)建一個(gè)新的執(zhí)行環(huán)境和一個(gè)活動(dòng)(可變)對(duì)象。這個(gè)新執(zhí)行環(huán)境的作用域就由新的活動(dòng)對(duì)象后跟外部函數(shù)對(duì)象的 [[scope]]
屬性所引用的作用域鏈(只有全局對(duì)象)構(gòu)成。在新執(zhí)行環(huán)境的變量實(shí)例化過(guò)程中,會(huì)創(chuàng)建一個(gè)與內(nèi)部函數(shù)聲明對(duì)應(yīng)的函數(shù)對(duì)象,而同時(shí)會(huì)給這個(gè)函數(shù)對(duì)象的 [[scope]]
屬性指定創(chuàng)建該函數(shù)對(duì)象的執(zhí)行環(huán)境(即新執(zhí)行環(huán)境。譯者注)的作用域值--即一個(gè)包含活動(dòng)對(duì)象后跟全局對(duì)象的作用域鏈。
到目前為止,所有過(guò)程都是自動(dòng)、或者由源代碼的結(jié)構(gòu)所控制的。但我們發(fā)現(xiàn),執(zhí)行環(huán)境的作用域鏈定義了執(zhí)行環(huán)境所創(chuàng)建的函數(shù)對(duì)象的 [[scope]]
屬性,而函數(shù)對(duì)象的 [[scope]]
屬性則定義了它的執(zhí)行環(huán)境的作用域(包括相應(yīng)的活動(dòng)對(duì)象)。不過(guò),ECMAScript 也提供了用于修改作用域鏈 with
語(yǔ)句。
with
語(yǔ)句會(huì)計(jì)算一個(gè)表達(dá)式,如果該表達(dá)式是一個(gè)對(duì)象,那么就將這個(gè)對(duì)象添加到當(dāng)前執(zhí)行環(huán)境的作用域鏈中(在活動(dòng)<可變>對(duì)象之前)。然后,執(zhí)行 with
語(yǔ)句(它自身也可能是一個(gè)語(yǔ)句塊)中的其他語(yǔ)句。之后,又恢復(fù)到調(diào)用它之前的執(zhí)行環(huán)境的作用域鏈中。
with
語(yǔ)句不會(huì)影響在變量實(shí)例化過(guò)程中根據(jù)函數(shù)聲明創(chuàng)建函數(shù)對(duì)象。但是,可以在一個(gè) with
語(yǔ)句內(nèi)部對(duì)函數(shù)表達(dá)式求值:-
/* 創(chuàng)建全局變量 - y - 它引用一個(gè)對(duì)象:- */ var y = {x:5}; // 帶有一個(gè)屬性 - x - 的對(duì)象直接量 function exampleFuncWith(){ var z; /* 將全局對(duì)象 - y - 引用的對(duì)象添加到作用域鏈的前端:- */ with(y){ /* 對(duì)函數(shù)表達(dá)式求值,以創(chuàng)建函數(shù)對(duì)象并將該函數(shù)對(duì)象的引用指定給局部變量 - z - :- */ z = function(){ ... // 內(nèi)部函數(shù)表達(dá)式中的代碼; } } ... } /* 執(zhí)行 - exampleFuncWith - 函數(shù):- */ exampleFuncWith();
在調(diào)用 exampleFuncWith
函數(shù)所創(chuàng)建的執(zhí)行環(huán)境中包含一個(gè)由其活動(dòng)對(duì)象后跟全局對(duì)象構(gòu)成的作用域鏈。而在執(zhí)行 with
語(yǔ)句時(shí),又會(huì)把全局變量 y
引用的對(duì)象添加到這個(gè)作用域鏈的前端。在對(duì)其中的函數(shù)表達(dá)式求值的過(guò)程中,所創(chuàng)建函數(shù)對(duì)象的 [[scope]]
屬性與創(chuàng)建它的執(zhí)行環(huán)境的作用域保持一致--即,該屬性會(huì)引用一個(gè)由對(duì)象 y
后跟調(diào)用外部函數(shù)時(shí)所創(chuàng)建執(zhí)行環(huán)境的活動(dòng)對(duì)象,后跟全局對(duì)象的作用域鏈。
當(dāng)與 with
語(yǔ)句相關(guān)的語(yǔ)句塊執(zhí)行結(jié)束時(shí),執(zhí)行環(huán)境的作用域得以恢復(fù)(y
會(huì)被移除),但是已經(jīng)創(chuàng)建的函數(shù)對(duì)象(z
。譯者注)的 [[scope]]
屬性所引用的作用域鏈中位于最前面的仍然是對(duì)象 y
。
標(biāo)識(shí)符是沿作用域鏈逆向解析的。ECMA 262 將 this
歸類為關(guān)鍵字而不是標(biāo)識(shí)符,并非不合理。因?yàn)榻馕?this
值時(shí)始終要根據(jù)使用它的執(zhí)行環(huán)境來(lái)判斷,而與作用域鏈無(wú)關(guān)。
標(biāo)識(shí)符解析從作用域鏈中的第一個(gè)對(duì)象開始。檢查該對(duì)象中是否包含與標(biāo)識(shí)符對(duì)應(yīng)的屬性名。因?yàn)樽饔糜蜴準(zhǔn)且粭l對(duì)象鏈,所以這個(gè)檢查過(guò)程也會(huì)包含相應(yīng)對(duì)象的原型鏈(如果有)。如果沒有在作用域鏈的第一個(gè)對(duì)象中發(fā)現(xiàn)相應(yīng)的值,解析過(guò)程會(huì)繼續(xù)搜索下一個(gè)對(duì)象。這樣依次類推直至找到作用域鏈中包含以標(biāo)識(shí)符為屬性名的對(duì)象為止,也有可能在作用域鏈的所有對(duì)象中都沒有發(fā)現(xiàn)該標(biāo)識(shí)符。
當(dāng)基于對(duì)象使用屬性訪問(wèn)器時(shí),也會(huì)發(fā)生與上面相同的標(biāo)識(shí)符解析過(guò)程。當(dāng)屬性訪問(wèn)器中有相應(yīng)的屬性可以替換某個(gè)對(duì)象時(shí),這個(gè)屬性就成為表示該對(duì)象的標(biāo)識(shí)符,該對(duì)象在作用域鏈中的位置進(jìn)而被確定。全局對(duì)象始終都位于作用域鏈的尾端。
因?yàn)榕c函數(shù)調(diào)用相關(guān)的執(zhí)行環(huán)境將會(huì)把活動(dòng)(可變)對(duì)象添加到作用域鏈的前端,所以在函數(shù)體內(nèi)使用的標(biāo)識(shí)符會(huì)首先檢查自己是否與形式參數(shù)、內(nèi)部函數(shù)聲明的名稱或局部變量一致。這些都可以由活動(dòng)(可變)對(duì)象的命名屬性來(lái)確定。
ECMAScript 要求使用自動(dòng)垃圾收集機(jī)制。但規(guī)范中并沒有詳細(xì)說(shuō)明相關(guān)的細(xì)節(jié),而是留給了實(shí)現(xiàn)來(lái)決定。但據(jù)了解,相當(dāng)一部分實(shí)現(xiàn)對(duì)它們的垃圾收集操作只賦予了很低的優(yōu)先級(jí)。但是,大致的思想都是相同的,即如果對(duì)象不再“可引用(由于不存在對(duì)它的引用,使執(zhí)行代碼無(wú)法再訪問(wèn)到它)”時(shí),該對(duì)象就成為垃圾收集的目標(biāo)。因而,在將來(lái)的某個(gè)時(shí)刻會(huì)將這個(gè)對(duì)象銷毀并將它所占用的一切資源釋放,以便操作系統(tǒng)重新利用。
正常情況下,當(dāng)退出一個(gè)執(zhí)行環(huán)境時(shí)就會(huì)滿足類似的條件。此時(shí),作用域鏈結(jié)構(gòu)中的活動(dòng)(可變)對(duì)象以及在該執(zhí)行環(huán)境中創(chuàng)建的任何對(duì)象--包括函數(shù)對(duì)象,都不再“可引用”,因此將成為垃圾收集的目標(biāo)。
閉包是通過(guò)在對(duì)一個(gè)函數(shù)調(diào)用的執(zhí)行環(huán)境中返回一個(gè)函數(shù)對(duì)象構(gòu)成的。比如,在對(duì)函數(shù)調(diào)用的過(guò)程中,將一個(gè)對(duì)內(nèi)部函數(shù)對(duì)象的引用指定給另一個(gè)對(duì)象的屬性。或者,直接將這樣一個(gè)(內(nèi)部)函數(shù)對(duì)象的引用指定給一個(gè)全局變量、或者一個(gè)全局性對(duì)象的屬性,或者一個(gè)作為參數(shù)以引用方式傳遞給外部函數(shù)的對(duì)象。例如:-
function exampleClosureForm(arg1, arg2){
var localVar = 8;
function exampleReturned(innerArg){
return ((arg1 + arg2)/(innerArg + localVar));
}
/* 返回一個(gè)定義為 exampleReturned 的內(nèi)部函數(shù)的引用 -:- */
return exampleReturned;
}
var globalVar = exampleClosureForm(2, 4);
這種情況下,在調(diào)用外部函數(shù) exampleClosureForm
的執(zhí)行環(huán)境中所創(chuàng)建的函數(shù)對(duì)象就不會(huì)被當(dāng)作垃圾收集,因?yàn)樵摵瘮?shù)對(duì)象被一個(gè)全局變量所引用,而且仍然是可以訪問(wèn)的,甚至可以通過(guò) globalVar(n)
來(lái)執(zhí)行。
的確,情況比正常的時(shí)候要復(fù)雜一些。因?yàn)楝F(xiàn)在這個(gè)被變量 globalVar
引用的內(nèi)部函數(shù)對(duì)象的 [[scope]]
屬性所引用的作用域鏈中,包含著屬于創(chuàng)建該內(nèi)部函數(shù)對(duì)象的執(zhí)行環(huán)境的活動(dòng)對(duì)象(和全局對(duì)象)。由于在執(zhí)行被 globalVar
引用的函數(shù)對(duì)象時(shí),每次都要把該函數(shù)對(duì)象的 [[scope]]
屬性所引用的整個(gè)作用域鏈添加到創(chuàng)建的(內(nèi)部函數(shù)的)執(zhí)行環(huán)境的作用域中(即此時(shí)的作用域中包括:內(nèi)部執(zhí)行環(huán)境的活動(dòng)對(duì)象、外部執(zhí)行環(huán)境的活動(dòng)對(duì)象、全局對(duì)象。譯者注), 所以這個(gè)(外部執(zhí)行環(huán)境的)活動(dòng)對(duì)象不會(huì)被當(dāng)作垃圾收集。
閉包因此而構(gòu)成。此時(shí),內(nèi)部函數(shù)對(duì)象擁有自由的變量,而位于該函數(shù)作用域鏈中的活動(dòng)(可變)對(duì)象則成為與變量綁定的環(huán)境。
由于活動(dòng)(可變)對(duì)象受限于內(nèi)部函數(shù)對(duì)象(現(xiàn)在被 globalVar
變量引用)的 [[scope]]
屬性中作用域鏈的引用,所以活動(dòng)對(duì)象連同它的變量聲明--即屬性的值,都會(huì)被保留。而在對(duì)內(nèi)部函數(shù)調(diào)用的執(zhí)行環(huán)境中進(jìn)行作用域解析時(shí),將會(huì)把與活動(dòng)(可變)對(duì)象的命名屬性一致的標(biāo)識(shí)符作為該對(duì)象的屬性來(lái)解析。活動(dòng)對(duì)象的這些屬性值即使是在創(chuàng)建它的執(zhí)行環(huán)境退出后,仍然可以被讀取和設(shè)置。
在上面的例子中,當(dāng)外部函數(shù)返回(退出它的執(zhí)行環(huán)境)時(shí),其活動(dòng)(可變)對(duì)象的變量聲明中記錄了形式參數(shù)、內(nèi)部函數(shù)定義以及局部變量的值。arg1
屬性的值為 2
,而 arg2
屬性的值為 4
,localVar
的值是 8
,還有一個(gè) exampleReturned
屬性,它引用由外部函數(shù)返回的內(nèi)部函數(shù)對(duì)象。(為方便起見,我們將在后面的討論中,稱這個(gè)活動(dòng)<可變>對(duì)象為 "ActOuter1")。
如果再次調(diào)用 exampleClosureForm
函數(shù),如:-
var secondGlobalVar = exampleClosureForm(12, 3);
- 則會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境和一個(gè)新的活動(dòng)對(duì)象。而且,會(huì)返回一個(gè)新的函數(shù)對(duì)象,該函數(shù)對(duì)象的 [[scope]]
屬性引用的作用域鏈與前一次不同,因?yàn)檫@一次的作用域鏈中包含著第二個(gè)執(zhí)行環(huán)境的活動(dòng)對(duì)象,而這個(gè)活動(dòng)對(duì)象的屬性 arg1
值為 12
而屬性 arg2
值為 3
。(為方便起見,我們將在后面的討論中,稱這個(gè)活動(dòng)<可變>對(duì)象為 "ActOuter2")。
通過(guò)第二次執(zhí)行 exampleClosureForm
函數(shù),第二個(gè)、也是截然不同的閉包誕生了。
通過(guò)執(zhí)行 exampleClosureForm
創(chuàng)建的兩個(gè)函數(shù)對(duì)象分別被指定給了全局變量 globalVar
和 secondGlobalVar
,并返回了表達(dá)式 ((arg1 + arg2)/(innerArg + localVar))
。該表達(dá)式對(duì)其中的四個(gè)標(biāo)識(shí)符應(yīng)用了不同的操作符。如何確定這些標(biāo)識(shí)符的值是體現(xiàn)閉包價(jià)值的關(guān)鍵所在。
我們來(lái)看一看,在執(zhí)行由 globalVar
引用的函數(shù)對(duì)象--如 globalVar(2)
--時(shí)的情形。此時(shí),會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境和相應(yīng)的活動(dòng)對(duì)象(我們將稱之為“ActInner1”),并把該活動(dòng)對(duì)象添加到執(zhí)行的函數(shù)對(duì)象的 [[scope]]
屬性所引用的作用域鏈的前端。ActInner1 會(huì)帶有一個(gè)屬性 innerArg
,根據(jù)傳遞的形式參數(shù),其值被指定為 2
。這個(gè)新執(zhí)行環(huán)境的作用域鏈變成: ActInner1->ActOuter1->全局對(duì)象.
為了返回表達(dá)式 ((arg1 + arg2)/(innerArg + localVar))
的值,要沿著作用域鏈進(jìn)行標(biāo)識(shí)符解析。表達(dá)式中標(biāo)識(shí)符的值將通過(guò)依次查找作用域鏈中每個(gè)對(duì)象(與標(biāo)識(shí)符名稱一致)的屬性來(lái)確定。
作用域鏈中的第一個(gè)對(duì)象是 ActInner1,它有一個(gè)名為 innerArg
的屬性,值是 2
。所有其他三個(gè)標(biāo)識(shí)符在 ActOuter1 中都有對(duì)應(yīng)的屬性:arg1
是 2
,arg2
是 4
而 localVar
是 8
。最后,函數(shù)調(diào)用返回 ((2 + 2)/(2 + 8))
。
現(xiàn)在再來(lái)看一看由 secondGlobalVar
引用的同一個(gè)函數(shù)對(duì)象的執(zhí)行情況,比如 secondGlobalVar(5)
。我們把這次創(chuàng)建的新執(zhí)行環(huán)境的活動(dòng)對(duì)象稱為 “ActInner2”,相應(yīng)的作用域鏈就變成了:ActInner2->ActOuter2->全局對(duì)象。ActInner2 返回 innerArg
的值 5
,而 ActOuter2 分別返回 arg1
、arg2
和 localVar
的值 12
、3
和 8
。函數(shù)調(diào)用返回的值就是 ((12 + 3)/(5 + 8))
。
如果再執(zhí)行一次 secondGlobalVar
,則又會(huì)有一個(gè)新活動(dòng)對(duì)象被添加到作用域鏈的前端,但 ActOuter2 仍然是鏈中的第二個(gè)對(duì)象,而他的命名屬性會(huì)再次用于完成標(biāo)識(shí)符 arg1
、arg2
和 localVar
的解析。
這就是 ECMAScript 的內(nèi)部函數(shù)獲取、維持和訪問(wèn)創(chuàng)建他們的執(zhí)行環(huán)境的形式參數(shù)、聲明的內(nèi)部函數(shù)以及局部變量的過(guò)程。這個(gè)過(guò)程說(shuō)明了構(gòu)成閉包以后,內(nèi)部的函數(shù)對(duì)象在其存續(xù)過(guò)程中,如何維持對(duì)這些值的引用、如何對(duì)這些值進(jìn)行讀取的機(jī)制。即,創(chuàng)建內(nèi)部函數(shù)對(duì)象的執(zhí)行環(huán)境的活動(dòng)(可變)對(duì)象,會(huì)保留在該函數(shù)對(duì)象的 [[scope]]
屬性所引用的作用域鏈中。直到所有對(duì)這個(gè)內(nèi)部函數(shù)的引用被釋放,這個(gè)函數(shù)對(duì)象才會(huì)成為垃圾收集的目標(biāo)(連同它的作用域鏈中任何不再需要的對(duì)象)。
內(nèi)部函數(shù)自身也可能有內(nèi)部函數(shù)。在通過(guò)函數(shù)執(zhí)行返回內(nèi)部函數(shù)構(gòu)成閉包以后,相應(yīng)的閉包自身也可能會(huì)返回內(nèi)部函數(shù)從而構(gòu)成它們自己的閉包。每次作用域鏈嵌套,都會(huì)增加由創(chuàng)建內(nèi)部函數(shù)對(duì)象的執(zhí)行環(huán)境引發(fā)的新活動(dòng)對(duì)象。ECMAScript 規(guī)范要求作用域鏈?zhǔn)桥R時(shí)性的,但對(duì)作用域鏈的長(zhǎng)度卻沒有加以限制。在具體實(shí)現(xiàn)中,可能會(huì)存在實(shí)際的限制,但還沒有發(fā)現(xiàn)有具體限制數(shù)量的報(bào)告。目前來(lái)看,嵌套的內(nèi)部函數(shù)所擁有的潛能,仍然超出了使用它們的人的想像能力。
對(duì)這個(gè)問(wèn)題的回答可能會(huì)令你驚訝--閉包什么都可以做。據(jù)我所知,閉包使得 ECMAScript 能夠模仿任何事物,因此局限性在于設(shè)計(jì)和實(shí)現(xiàn)要模仿事物的能力。只是從字面上看可能會(huì)覺得這么說(shuō)很深?yuàn)W,下面我們就來(lái)看一些更有實(shí)際意義的例子。
閉包的一個(gè)常見用法是在執(zhí)行函數(shù)之前為要執(zhí)行的函數(shù)提供參數(shù)。例如:將函數(shù)作為 setTimout
函數(shù)的第一個(gè)參數(shù),這在 Web 瀏覽器的環(huán)境下是非常常見的一種應(yīng)用。
setTimeout
用于有計(jì)劃地執(zhí)行一個(gè)函數(shù)(或者一串 JavaScript 代碼,不是在本例中),要執(zhí)行的函數(shù)是其第一個(gè)參數(shù),其第二個(gè)參數(shù)是以毫秒表示的執(zhí)行間隔。也就是說(shuō),當(dāng)在一段代碼中使用 setTimeout
時(shí),要將一個(gè)函數(shù)的引用作為它的第一個(gè)參數(shù),而將以毫秒表示的時(shí)間值作為第二個(gè)參數(shù)。但是,傳遞函數(shù)引用的同時(shí)無(wú)法為計(jì)劃執(zhí)行的函數(shù)提供參數(shù)。
然而,可以在代碼中調(diào)用另外一個(gè)函數(shù),由它返回一個(gè)對(duì)內(nèi)部函數(shù)的引用,再把這個(gè)對(duì)內(nèi)部函數(shù)對(duì)象的引用傳遞給 setTimeout
函數(shù)。執(zhí)行這個(gè)內(nèi)部函數(shù)時(shí)要使用的參數(shù)在調(diào)用返回它的外部函數(shù)時(shí)傳遞。這樣,setTimeout
在執(zhí)行這個(gè)內(nèi)部函數(shù)時(shí),不用傳遞參數(shù),但該內(nèi)部函數(shù)仍然能夠訪問(wèn)在調(diào)用返回它的外部函數(shù)時(shí)傳遞的參數(shù):
function callLater(paramA, paramB, paramC){ /* 返回一個(gè)由函數(shù)表達(dá)式創(chuàng)建的匿名內(nèi)部函數(shù)的引用:- */ return (function(){ /* 這個(gè)內(nèi)部函數(shù)將通過(guò) - setTimeout - 執(zhí)行, 而且當(dāng)它執(zhí)行時(shí)它會(huì)讀取并按照傳遞給 外部函數(shù)的參數(shù)行事: */ paramA[paramB] = paramC; }); } ... /* 調(diào)用這個(gè)函數(shù)將返回一個(gè)在其執(zhí)行環(huán)境中創(chuàng)建的內(nèi)部函數(shù)對(duì)象的引用。 傳遞的參數(shù)最終將作為外部函數(shù)的參數(shù)被內(nèi)部函數(shù)使用。 返回的對(duì)內(nèi)部函數(shù)的引用被賦給一個(gè)全局變量:- */ var functRef = callLater(elStyle, "display", "none"); /* 調(diào)用 setTimeout 函數(shù),將賦給變量 - functRef - 的內(nèi)部函數(shù)的引用作為傳遞的第一個(gè)參數(shù):- */ hideMenu=setTimeout(functRef, 500);
許多時(shí)候我們需要將一個(gè)函數(shù)對(duì)象暫時(shí)掛到一個(gè)引用上留待后面執(zhí)行,因?yàn)椴坏鹊綀?zhí)行的時(shí)候是很難知道其具體參數(shù)的,而先前將它賦給那個(gè)引用的時(shí)候更是壓根不知道的。 (此段由 pangba 劉未鵬 翻譯)
(luyy朋友的翻譯_2008-7-7更新)很多時(shí)候需要將一個(gè)函數(shù)引用進(jìn)行賦值,以便在將來(lái)某個(gè)時(shí)候執(zhí)行該函數(shù),在執(zhí)行這些函數(shù)時(shí)給函數(shù)提供參數(shù)將會(huì)是有用處的,但這些參數(shù)在執(zhí)行時(shí)不容易獲得,他們只有在上面賦值給時(shí)才能確定。
(原文備考: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.)
一個(gè)相關(guān)的例子是,用 JavaScript 對(duì)象來(lái)封裝與特定 DOM 元素的交互。這個(gè) JavaScript 對(duì)象具有 doOnClick
、doMouseOver
和 doMouseOut
方法,并且當(dāng)用戶在該特定的 DOM 元素中觸發(fā)了相應(yīng)的事件時(shí)要執(zhí)行這些方法。不過(guò),可能會(huì)創(chuàng)建與不同的 DOM 元素關(guān)聯(lián)的任意數(shù)量的 JavaScript 對(duì)象,而且每個(gè)對(duì)象實(shí)例并不知道實(shí)例化它們的代碼將會(huì)如何操縱它們(即注冊(cè)事件處理函數(shù)與定義相應(yīng)的事件處理函數(shù)分離。譯者注)。這些對(duì)象實(shí)例并不知道如何在全局環(huán)境中引用它們自身,因?yàn)樗鼈儾恢缹?huì)指定哪個(gè)全局變量(如果有)引用它們的實(shí)例。
因而問(wèn)題可以歸結(jié)為執(zhí)行一個(gè)與特定的 JavaScript 對(duì)象關(guān)聯(lián)的事件處理函數(shù),并且要知道調(diào)用該對(duì)象的哪個(gè)方法。
下面這個(gè)例子使用了一個(gè)基于閉包構(gòu)建的一般化的函數(shù)(此句多謝未鵬指點(diǎn)),該函數(shù)會(huì)將對(duì)象實(shí)例與 DOM 元素事件關(guān)聯(lián)起來(lái),安排執(zhí)行事件處理程序時(shí)調(diào)用對(duì)象實(shí)例的指定方法,給象的指定方法傳遞的參數(shù)是事件對(duì)象和與元素關(guān)聯(lián)的引用,該函數(shù)返回執(zhí)行相應(yīng)方法后的返回值。
/* 一個(gè)關(guān)聯(lián)對(duì)象實(shí)例和事件處理器的函數(shù)。 它返回的內(nèi)部函數(shù)被用作事件處理器。對(duì)象實(shí)例以 - obj - 參數(shù)表示, 而在該對(duì)象實(shí)例中調(diào)用的方法名則以 - methodName - (字符串)參數(shù)表示。 */ function associateObjWithEvent(obj, methodName){ /* 下面這個(gè)返回的內(nèi)部函數(shù)將作為一個(gè) DOM 元素的事件處理器*/ return (function(e){ /* 在支持標(biāo)準(zhǔn) DOM 規(guī)范的瀏覽器中,事件對(duì)象會(huì)被解析為參數(shù) - e - , 若沒有正常解析,則使用 IE 的事件對(duì)象來(lái)規(guī)范化事件對(duì)象。 */ e = e||window.event; /* 事件處理器通過(guò)保存在字符串 - methodName - 中的方法名調(diào)用了對(duì)象 - obj - 的一個(gè)方法。并傳遞已經(jīng)規(guī)范化的事件對(duì)象和觸發(fā)事件處理器的元素 的引用 - this - (之所以 this 有效是因?yàn)檫@個(gè)內(nèi)部函數(shù)是作為該元素的方法執(zhí)行的) */ return obj[methodName](e, this); }); } /* 這個(gè)構(gòu)造函數(shù)用于創(chuàng)建將自身與 DOM 元素關(guān)聯(lián)的對(duì)象, DOM 元素的 ID 作為構(gòu)造函數(shù)的字符串參數(shù)。 所創(chuàng)建的對(duì)象會(huì)在相應(yīng)的元素觸發(fā) onclick、 onmouseover 或 onmouseout 事件時(shí), 調(diào)用相應(yīng)的方法。 */ function DhtmlObject(elementId){ /* 調(diào)用一個(gè)返回 DOM 元素(如果沒找到返回 null)引用的函數(shù), 必需的參數(shù)是 ID。 將返回的值賦給局部變量 - el -。 */ var el = getElementWithId(elementId); /* - el - 值會(huì)在內(nèi)部通過(guò)類型轉(zhuǎn)換變?yōu)椴紶栔担员?- if - 語(yǔ)句加以判斷。 因此,如果它引用一個(gè)對(duì)象結(jié)果將返回 true,如果是 null 則返回 false。 下面的代碼塊只有當(dāng) - el - 變量返回一個(gè) DOM 元素時(shí)才會(huì)被執(zhí)行。 */ if(el){ /* 為給元素的事件處理器指定一個(gè)函數(shù),該對(duì)象調(diào)用了 - associateObjWithEvent - 函數(shù)。 同時(shí)對(duì)象將自身(通過(guò) - this - 關(guān)鍵字)作為調(diào)用方法的對(duì)象, 并提供了調(diào)用的方法名稱。 - associateObjWithEvent - 函數(shù)會(huì)返回 一個(gè)內(nèi)部函數(shù),該內(nèi)部函數(shù)被指定為 DOM 元素的事件處理器。 在響應(yīng)事件時(shí),執(zhí)行這個(gè)內(nèi)部函數(shù)就會(huì)調(diào)用必要的方法。 */ 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
的任何實(shí)例都會(huì)將自身與相應(yīng)的 DOM 元素關(guān)聯(lián)起來(lái),而這些 DOM 元素不必知道其他代碼如何操縱它們(即當(dāng)觸發(fā)相應(yīng)事件時(shí),會(huì)執(zhí)行什么代碼。譯者注),也不必理會(huì)全局命名空間的影響以及與 DhtmlObject
的其他實(shí)例間存在沖突的危險(xiǎn)。
閉包可以用于創(chuàng)建額外的作用域,通過(guò)該作用域可以將相關(guān)的和具有依賴性的代碼組織起來(lái),以便將意外交互的風(fēng)險(xiǎn)降到最低。假設(shè)有一個(gè)用于構(gòu)建字符串的函數(shù),為了避免重復(fù)性的連接操作(和創(chuàng)建眾多的中間字符串),我們的愿望是使用一個(gè)數(shù)組按順序來(lái)存儲(chǔ)字符串的各個(gè)部分,然后再使用 Array.prototype.join
方法(以空字符串作為其參數(shù))輸出結(jié)果。這個(gè)數(shù)組將作為輸出的緩沖器,但是將數(shù)組作為函數(shù)的局部變量又會(huì)導(dǎo)致在每次調(diào)用函數(shù)時(shí)都重新創(chuàng)建一個(gè)新數(shù)組,這在每次調(diào)用函數(shù)時(shí)只重新指定數(shù)組中的可變內(nèi)容的情況下并不是必要的。
一種解決方案是將這個(gè)數(shù)組聲明為全局變量,這樣就可以重用這個(gè)數(shù)組,而不必每次都建立新數(shù)組。但這個(gè)方案的結(jié)果是,除了引用函數(shù)的全局變量會(huì)使用這個(gè)緩沖數(shù)組外,還會(huì)多出一個(gè)全局屬性引用數(shù)組自身。如此不僅使代碼變得不容易管理,而且,如果要在其他地方使用這個(gè)數(shù)組時(shí),開發(fā)者必須要再次定義函數(shù)和數(shù)組。這樣一來(lái),也使得代碼不容易與其他代碼整合,因?yàn)榇藭r(shí)不僅要保證所使用的函數(shù)名在全局命名空間中是唯一的,而且還要保證函數(shù)所依賴的數(shù)組在全局命名空間中也必須是唯一的。
而通過(guò)閉包可以使作為緩沖器的數(shù)組與依賴它的函數(shù)關(guān)聯(lián)起來(lái)(優(yōu)雅地打包),同時(shí)也能夠維持在全局命名空間外指定的緩沖數(shù)組的屬性名,免除了名稱沖突和意外交互的危險(xiǎn)。
其中的關(guān)鍵技巧在于通過(guò)執(zhí)行一個(gè)單行(in-line)函數(shù)表達(dá)式創(chuàng)建一個(gè)額外的執(zhí)行環(huán)境,而將該函數(shù)表達(dá)式返回的內(nèi)部函數(shù)作為在外部代碼中使用的函數(shù)。此時(shí),緩沖數(shù)組被定義為函數(shù)表達(dá)式的一個(gè)局部變量。這個(gè)函數(shù)表達(dá)式只需執(zhí)行一次,而數(shù)組也只需創(chuàng)建一次,就可以供依賴它的函數(shù)重復(fù)使用。
下面的代碼定義了一個(gè)函數(shù),這個(gè)函數(shù)用于返回一個(gè) HTML 字符串,其中大部分內(nèi)容都是常量,但這些常量字符序列中需要穿插一些可變的信息,而可變的信息由調(diào)用函數(shù)時(shí)傳遞的參數(shù)提供。
通過(guò)執(zhí)行單行函數(shù)表達(dá)式返回一個(gè)內(nèi)部函數(shù),并將返回的函數(shù)賦給一個(gè)全局變量,因此這個(gè)函數(shù)也可以稱為全局函數(shù)。而緩沖數(shù)組被定義為外部函數(shù)表達(dá)式的一個(gè)局部變量。它不會(huì)暴露在全局命名空間中,而且無(wú)論什么時(shí)候調(diào)用依賴它的函數(shù)都不需要重新創(chuàng)建這個(gè)數(shù)組。
/* 聲明一個(gè)全局變量 - getImgInPositionedDivHtml - 并將一次調(diào)用一個(gè)外部函數(shù)表達(dá)式返回的內(nèi)部函數(shù)賦給它。 這個(gè)內(nèi)部函數(shù)會(huì)返回一個(gè)用于表示絕對(duì)定位的 DIV 元素 包圍著一個(gè) IMG 元素 的 HTML 字符串,這樣一來(lái), 所有可變的屬性值都由調(diào)用該函數(shù)時(shí)的參數(shù)提供: */ var getImgInPositionedDivHtml = (function(){ /* 外部函數(shù)表達(dá)式的局部變量 - buffAr - 保存著緩沖數(shù)組。 這個(gè)數(shù)組只會(huì)被創(chuàng)建一次,生成的數(shù)組實(shí)例對(duì)內(nèi)部函數(shù)而言永遠(yuǎn)是可用的 因此,可供每次調(diào)用這個(gè)內(nèi)部函數(shù)時(shí)使用。 其中的空字符串用作數(shù)據(jù)占位符,相應(yīng)的數(shù)據(jù) 將由內(nèi)部函數(shù)插入到這個(gè)數(shù)組中: */ 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 調(diào)蓄 '\" alt=\"', '', //index 17, IMG alt 文本內(nèi)容 '\"><\/div>' ]; /* 返回作為對(duì)函數(shù)表達(dá)式求值后結(jié)果的內(nèi)部函數(shù)對(duì)象。 這個(gè)內(nèi)部函數(shù)就是每次調(diào)用執(zhí)行的函數(shù) - getImgInPositionedDivHtml( ... ) - */ return (function(url, id, width, height, top, left, altText){ /* 將不同的參數(shù)插入到緩沖數(shù)組相應(yīng)的位置: */ 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; /* 返回通過(guò)使用空字符串(相當(dāng)于將數(shù)組元素連接起來(lái)) 連接數(shù)組每個(gè)元素后形成的字符串: */ return buffAr.join(''); }); //:內(nèi)部函數(shù)表達(dá)式結(jié)束。 })(); /*^^- :單行外部函數(shù)表達(dá)式。*/
如果一個(gè)函數(shù)依賴于另一(或多)個(gè)其他函數(shù),而其他函數(shù)又沒有必要被其他代碼直接調(diào)用,那么可以運(yùn)用相同的技術(shù)來(lái)包裝這些函數(shù),而通過(guò)一個(gè)公開暴露的函數(shù)來(lái)調(diào)用它們。這樣,就將一個(gè)復(fù)雜的多函數(shù)處理過(guò)程封裝成了一個(gè)具有移植性的代碼單元。
有關(guān)閉包的一個(gè)可能是最廣為人知的應(yīng)用是 Douglas Crockford's technique for the emulation of private instance variables in ECMAScript objects。這種應(yīng)用方式可以擴(kuò)展到各種嵌套包含的可訪問(wèn)性(或可見性)的作用域結(jié)構(gòu),包括 the emulation of private static members for ECMAScript objects。
閉包可能的用途是無(wú)限的,可能理解其工作原理才是把握如何使用它的最好指南。
在創(chuàng)建可訪問(wèn)的內(nèi)部函數(shù)的函數(shù)體之外解析該內(nèi)部函數(shù)就會(huì)構(gòu)成閉包。這表明閉包很容易創(chuàng)建,但這樣一來(lái)可能會(huì)導(dǎo)致一種結(jié)果,即沒有認(rèn)識(shí)到閉包是一種語(yǔ)言特性的 JavaScript 作者,會(huì)按照內(nèi)部函數(shù)能完成多種任務(wù)的想法來(lái)使用內(nèi)部函數(shù)。但他們對(duì)使用內(nèi)部函數(shù)的結(jié)果并不明了,而且根本意識(shí)不到創(chuàng)建了閉包,或者那樣做意味著什么。
正如下一節(jié)談到 IE 中內(nèi)存泄漏問(wèn)題時(shí)所提及的,意外創(chuàng)建的閉包可能導(dǎo)致嚴(yán)重的負(fù)面效應(yīng),而且也會(huì)影響到代碼的性能。問(wèn)題不在于閉包本身,如果能夠真正做到謹(jǐn)慎地使用它們,反而會(huì)有助于創(chuàng)建高效的代碼。換句話說(shuō),使用內(nèi)部函數(shù)會(huì)影響到效率。
使用內(nèi)部函數(shù)最常見的一種情況就是將其作為 DOM 元素的事件處理器。例如,下面的代碼用于向一個(gè)鏈接元素添加 onclick 事件處理器:
/* 定義一個(gè)全局變量,通過(guò)下面的函數(shù)將它的值 作為查詢字符串的一部分添加到鏈接的 - href - 中: */ var quantaty = 5; /* 當(dāng)給這個(gè)函數(shù)傳遞一個(gè)鏈接(作為函數(shù)中的參數(shù) - linkRef -)時(shí), 會(huì)將一個(gè) onclick 事件處理器指定給該鏈接,該事件處理器 將全局變量 - quantaty - 的值作為字符串添加到鏈接的 - href - 屬性中,然后返回 true 使該鏈接在單擊后定位到由 - href - 屬性包含的查詢字符串指定的資源: */ function addGlobalQueryOnClick(linkRef){ /* 如果可以將參數(shù) - linkRef - 通過(guò)類型轉(zhuǎn)換為 ture (說(shuō)明它引用了一個(gè)對(duì)象): */ if(linkRef){ /* 對(duì)一個(gè)函數(shù)表達(dá)式求值,并將對(duì)該函數(shù)對(duì)象的引用 指定給這個(gè)鏈接元素的 onclick 事件處理器: */ linkRef.onclick = function(){ /* 這個(gè)內(nèi)部函數(shù)表達(dá)式將查詢字符串 添加到附加事件處理器的元素的 - href - 屬性中: */ this.href += ('?quantaty='+escape(quantaty)); return true; }; } }
無(wú)論什么時(shí)候調(diào)用 addGlobalQueryOnClick
函數(shù),都會(huì)創(chuàng)建一個(gè)新的內(nèi)部函數(shù)(通過(guò)賦值構(gòu)成了閉包)。從效率的角度上看,如果只是調(diào)用一兩次 addGlobalQueryOnClick
函數(shù)并沒有什么大的妨礙,但如果頻繁使用該函數(shù),就會(huì)導(dǎo)致創(chuàng)建許多截然不同的函數(shù)對(duì)象(每對(duì)內(nèi)部函數(shù)表達(dá)式求一次值,就會(huì)產(chǎn)生一個(gè)新的函數(shù)對(duì)象)。
上面例子中的代碼沒有關(guān)注內(nèi)部函數(shù)在創(chuàng)建它的函數(shù)外部可以訪問(wèn)(或者說(shuō)構(gòu)成了閉包)這一事實(shí)。實(shí)際上,同樣的效果可以通過(guò)另一種方式來(lái)完成。即單獨(dú)地定義一個(gè)用于事件處理器的函數(shù),然后將該函數(shù)的引用指定給元素的事件處理屬性。這樣,只需創(chuàng)建一個(gè)函數(shù)對(duì)象,而所有使用相同事件處理器的元素都可以共享對(duì)這個(gè)函數(shù)的引用:
/* 定義一個(gè)全局變量,通過(guò)下面的函數(shù)將它的值 作為查詢字符串的一部分添加到鏈接的 - href - 中: */ var quantaty = 5; /* 當(dāng)把一個(gè)鏈接(作為函數(shù)中的參數(shù) - linkRef -)傳遞給這個(gè)函數(shù)時(shí), 會(huì)給這個(gè)鏈接添加一個(gè) onclick 事件處理器,該事件處理器會(huì) 將全局變量 - quantaty - 的值作為查詢字符串的一部分添加到 鏈接的 - href - 中,然后返回 true,以便單擊鏈接時(shí)定位到由 作為 - href - 屬性值的查詢字符串所指定的資源: */ function addGlobalQueryOnClick(linkRef){ /* 如果 - linkRef - 參數(shù)能夠通過(guò)類型轉(zhuǎn)換為 true (說(shuō)明它引用了一個(gè)對(duì)象): */ if(linkRef){ /* 將一個(gè)對(duì)全局函數(shù)的引用指定給這個(gè)鏈接 的事件處理屬性,使函數(shù)成為鏈接元素的事件處理器: */ linkRef.onclick = forAddQueryOnClick; } } /* 聲明一個(gè)全局函數(shù),作為鏈接元素的事件處理器, 這個(gè)函數(shù)將一個(gè)全局變量的值作為要添加事件處理器的 鏈接元素的 - href - 值的一部分: */ function forAddQueryOnClick(){ this.href += ('?quantaty='+escape(quantaty)); return true; }
在上面例子的第一個(gè)版本中,內(nèi)部函數(shù)并沒有作為閉包發(fā)揮應(yīng)有的作用。在那種情況下,反而是不使用閉包更有效率,因?yàn)椴挥弥貜?fù)創(chuàng)建許多本質(zhì)上相同的函數(shù)對(duì)象。
類似地考量同樣適用于對(duì)象的構(gòu)造函數(shù)。與下面代碼中的構(gòu)造函數(shù)框架類似的代碼并不罕見:
function ExampleConst(param){ /* 通過(guò)對(duì)函數(shù)表達(dá)式求值創(chuàng)建對(duì)象的方法, 并將求值所得的函數(shù)對(duì)象的引用賦給要?jiǎng)?chuàng)建對(duì)象的屬性: */ this.method1 = function(){ ... // 方法體。 }; this.method2 = function(){ ... // 方法體。 }; this.method3 = function(){ ... // 方法體。 }; /* 把構(gòu)造函數(shù)的參數(shù)賦給對(duì)象的一個(gè)屬性: */ this.publicProp = param; }
每當(dāng)通過(guò) new ExampleConst(n)
使用這個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象時(shí),都會(huì)創(chuàng)建一組新的、作為對(duì)象方法的函數(shù)對(duì)象。因此,創(chuàng)建的對(duì)象實(shí)例越多,相應(yīng)的函數(shù)對(duì)象也就越多。
Douglas Crockford 提出的模仿 JavaScript 對(duì)象私有成員的技術(shù),就利用了將對(duì)內(nèi)部函數(shù)的引用指定給在構(gòu)造函數(shù)中構(gòu)造對(duì)象的公共屬性而形成的閉包。如果對(duì)象的方法沒有利用在構(gòu)造函數(shù)中形成的閉包,那么在實(shí)例化每個(gè)對(duì)象時(shí)創(chuàng)建的多個(gè)函數(shù)對(duì)象,會(huì)使實(shí)例化過(guò)程變慢,而且將有更多的資源被占用,以滿足創(chuàng)建更多函數(shù)對(duì)象的需要。
這那種情況下,只創(chuàng)建一次函數(shù)對(duì)象,并把它們指定給構(gòu)造函數(shù) prototype
的相應(yīng)屬性顯然更有效率。這樣一來(lái),它們就能被構(gòu)造函數(shù)創(chuàng)建的所有對(duì)象共享了:
function ExampleConst(param){ /* 將構(gòu)造函數(shù)的參數(shù)賦給對(duì)象的一個(gè)屬性: */ this.publicProp = param; } /* 通過(guò)對(duì)函數(shù)表達(dá)式求值,并將結(jié)果函數(shù)對(duì)象的引用 指定給構(gòu)造函數(shù)原型的相應(yīng)屬性來(lái)創(chuàng)建對(duì)象的方法: */ ExampleConst.prototype.method1 = function(){ ... // 方法體。 }; ExampleConst.prototype.method2 = function(){ ... // 方法體。 }; ExampleConst.prototype.method3 = function(){ ... // 方法體。 };
Internet Explorer Web 瀏覽器(在 IE 4 到 IE 6 中核實(shí))的垃圾收集系統(tǒng)中存在一個(gè)問(wèn)題,即如果 ECMAScript 和某些宿主對(duì)象構(gòu)成了 "循環(huán)引用",那么這些對(duì)象將不會(huì)被當(dāng)作垃圾收集。此時(shí)所謂的宿主對(duì)象指的是任何 DOM 節(jié)點(diǎn)(包括 document 對(duì)象及其后代元素)和 ActiveX 對(duì)象。如果在一個(gè)循環(huán)引用中包含了一或多個(gè)這樣的對(duì)象,那么這些對(duì)象直到瀏覽器關(guān)閉都不會(huì)被釋放,而它們所占用的內(nèi)存同樣在瀏覽器關(guān)閉之前都不會(huì)交回系統(tǒng)重用。
當(dāng)兩個(gè)或多個(gè)對(duì)象以首尾相連的方式相互引用時(shí),就構(gòu)成了循環(huán)引用。比如對(duì)象 1 的一個(gè)屬性引用了對(duì)象 2 ,對(duì)象 2 的一個(gè)屬性引用了對(duì)象 3,而對(duì)象 3 的一個(gè)屬性又引用了對(duì)象 1。對(duì)于純粹的 ECMAScript 對(duì)象而言,只要沒有其他對(duì)象引用對(duì)象 1、2、3,也就是說(shuō)它們只是相互之間的引用,那么仍然會(huì)被垃圾收集系統(tǒng)識(shí)別并處理。但是,在 Internet Explorer 中,如果循環(huán)引用中的任何對(duì)象是 DOM 節(jié)點(diǎn)或者 ActiveX 對(duì)象,垃圾收集系統(tǒng)則不會(huì)發(fā)現(xiàn)它們之間的循環(huán)關(guān)系與系統(tǒng)中的其他對(duì)象是隔離的并釋放它們。最終它們將被保留在內(nèi)存中,直到瀏覽器關(guān)閉。
閉包非常容易構(gòu)成循環(huán)引用。如果一個(gè)構(gòu)成閉包的函數(shù)對(duì)象被指定給,比如一個(gè) DOM 節(jié)點(diǎn)的事件處理器,而對(duì)該節(jié)點(diǎn)的引用又被指定給函數(shù)對(duì)象作用域中的一個(gè)活動(dòng)(或可變)對(duì)象,那么就存在一個(gè)循環(huán)引用。DOM_Node.onevent ->function_object.[[scope]] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。形成這樣一個(gè)循環(huán)引用是輕而易舉的,而且稍微瀏覽一下包含類似循環(huán)引用代碼的網(wǎng)站(通常會(huì)出現(xiàn)在網(wǎng)站的每個(gè)頁(yè)面中),就會(huì)消耗大量(甚至全部)系統(tǒng)內(nèi)存。
多加注意可以避免形成循環(huán)引用,而在無(wú)法避免時(shí),也可以使用補(bǔ)償?shù)姆椒ǎ热缡褂?IE 的 onunload 事件來(lái)來(lái)清空(null)事件處理函數(shù)的引用。時(shí)刻意識(shí)到這個(gè)問(wèn)題并理解閉包的工作機(jī)制是在 IE 中避免此類問(wèn)題的關(guān)鍵。
comp.lang.javascript FAQ notes T.O.C.
來(lái)源:http://hi.baidu.com/yangfan356/blog/item/78b98f3dedd6fcc29f3d62ab.html
解密.htm.html.shtm.shtml的區(qū)別與聯(lián)系
每一個(gè)網(wǎng)頁(yè)或者說(shuō)是web頁(yè)都有其固定的后綴名,不同的后綴名對(duì)應(yīng)著不同的文件格式和不同的規(guī)則、協(xié)議、用法,最常見的web頁(yè)的后綴名是.html和.htm,但這只是web頁(yè)最基本的兩種文件格式,今天我們來(lái)介紹一下web頁(yè)的其它一些文件格式。
首先,介紹一下html與htm:
關(guān)于HTML,HTML(HyperTextMark-upLanguage)即超文本標(biāo)記語(yǔ)言,是WWW的描述語(yǔ)言。設(shè)計(jì)HTML語(yǔ)言的目的是為了能把存放在一臺(tái)電腦中的文本或圖形與另一臺(tái)電腦中的文本或圖形方便地聯(lián)系在一起,形成有機(jī)的整體,人們不用考慮具體信息是在當(dāng)前電腦上還是在網(wǎng)絡(luò)的其它電腦上。我們只需使用鼠標(biāo)在某一文檔中點(diǎn)取一個(gè)圖標(biāo),Internet就會(huì)馬上轉(zhuǎn)到與此圖標(biāo)相關(guān)的內(nèi)容上去,而這些信息可能存放在網(wǎng)絡(luò)的另一臺(tái)電腦中。 HTML文本是由HTML命令組成的描述性文本,HTML命令可以說(shuō)明文字、圖形、動(dòng)畫、聲音、表格、鏈接等。HTML的結(jié)構(gòu)包括頭部(Head)、主體(Body)兩大部分,其中頭部描述瀏覽器所需的信息,而主體則包含所要說(shuō)明的具體內(nèi)容。
關(guān)于HTM,實(shí)際上HTM與HTML沒有本質(zhì)意義的區(qū)別,只是為了滿足DOS僅能識(shí)別8+3的文件名而已,因?yàn)橐恍├系南到y(tǒng)(win32)不能識(shí)別四位文件名,所以某些網(wǎng)頁(yè)服務(wù)器要求index.html最后一個(gè)l不能省略。MSIE能自動(dòng)識(shí)別和打開這些文件,但編寫網(wǎng)頁(yè)地址的時(shí)候必須是完全對(duì)應(yīng)的,也就是說(shuō)index.htm和index.html是兩個(gè)不同的文件,對(duì)應(yīng)著不同的地址。值得一提的是UNIX系統(tǒng)中對(duì)大小寫敏感,不吻合的話就可能報(bào)沒有文件或者找不到文件。
其次,介紹一下shtml和shtm:
關(guān)于shtml,shtml是一種基于SSI技術(shù)的文件,也就是Server Side Include--SSI 服務(wù)器端包含指令,一些Web Server如果有SSI功能的話就會(huì)對(duì)shtml文件特殊招待,服務(wù)器會(huì)先掃一次shtml文件看沒有特殊的SSI指令存在,如果有的話就按Web Server設(shè)定規(guī)則解釋SSI指令,解釋完后跟一般html一起調(diào)去客戶端。
關(guān)于shtm,shtm與shtml的關(guān)系和htm與html的關(guān)系大致相似,這里就不多說(shuō)了。
html或htm與shtml或shtm的關(guān)系是什么?
html或者h(yuǎn)tm是一種靜態(tài)的頁(yè)面格式,也就是說(shuō)不需要服務(wù)器解析其中的腳本,或者說(shuō)里面沒有服務(wù)器端執(zhí)行的腳本,而shtml或者shtm由于它基于SSI技術(shù),當(dāng)有服務(wù)器端可執(zhí)行腳本時(shí)被當(dāng)作一種動(dòng)態(tài)編程語(yǔ)言來(lái)看待,就如asp、jsp或者php一樣。當(dāng)shtml或者shtm中不包含服務(wù)器端可執(zhí)行腳本時(shí)其作用和html或者h(yuǎn)tm是一樣的。
什么是SHTML?與HTML的區(qū)別
問(wèn)起SHTML和HTML的區(qū)別,如果用一句話來(lái)解釋就是:SHTML 不是HTML而是一種服務(wù)器 API,shtml是服務(wù)器動(dòng)態(tài)產(chǎn)成的html。
雖然兩者都是超文本格式,但shtml是一種用于SSI技術(shù)的文件。也就是Server Side Include--SSI 服務(wù)器端包含指令。如果Web Server有SSI功能的話(大多數(shù)(尤其是基于Unix平臺(tái))的WEB服務(wù)器如Netscape Enterprise Server等均支持SSI命令)。 會(huì)對(duì)shtml文件特殊招待。 先掃一次shtml文件看沒有特殊的SSI指令現(xiàn)在。 有就按Web Server設(shè)定規(guī)則解釋SSI指令。 解釋完后跟一般html一起掉去客戶端。
shtml:
使用SSI(Server Side Include)的html文件擴(kuò)展名,SSI(Server Side Include),通常稱為"服務(wù)器端嵌入"或者叫"服務(wù)器端包含",是一種類似于ASP的基于服務(wù)器的網(wǎng)頁(yè)制作技術(shù)。
SSI工作原理:
將內(nèi)容發(fā)送到瀏覽器之前,可以使用“服務(wù)器端包含 (SSI)”指令將文本、圖形或應(yīng)用程序信息包含到網(wǎng)頁(yè)中。例如,可以使用 SSI 包含時(shí)間/日期戳、版權(quán)聲明或供客戶填寫并返回的表單。對(duì)于在多個(gè)文件中重復(fù)出現(xiàn)的文本或圖形,使用包含文件是一種簡(jiǎn)便的方法。將內(nèi)容存入一個(gè)包含文件中即可,而不必將內(nèi)容輸入所有文件。通過(guò)一個(gè)非常簡(jiǎn)單的語(yǔ)句即可調(diào)用包含文件,此語(yǔ)句指示 Web 服務(wù)器將內(nèi)容插入適當(dāng)網(wǎng)頁(yè)。而且,使用包含文件時(shí),對(duì)內(nèi)容的所有更改只需在一個(gè)地方就能完成。
因?yàn)榘?SSI 指令的文件要求特殊處理,所以必須為所有 SSI 文件賦予 SSI 文件擴(kuò)展名。默認(rèn)擴(kuò)展名是 .stm、.shtm 和 .shtml
Web 服務(wù)器在處理網(wǎng)頁(yè)的同時(shí)處理 SSI 指令。當(dāng) Web 服務(wù)器遇到 SSI 指令時(shí),直接將包含文件的內(nèi)容插入 HTML 網(wǎng)頁(yè)。如果“包含文件”中包含 SSI 指令,則同時(shí)插入此文件。除了用于包含文件的基本指令之外,還可以使用 SSI 指令插入文件的相關(guān)信息(如文件的大小)或者運(yùn)行應(yīng)用程序或 shell 命令。
網(wǎng)站維護(hù)常常碰到的一個(gè)問(wèn)題是,網(wǎng)站的結(jié)構(gòu)已經(jīng)固定,卻為了更新一點(diǎn)內(nèi)容而不得不重做一大批網(wǎng)頁(yè)。SSI提供了一種簡(jiǎn)單、有效的方法來(lái)解決這一問(wèn)題,它將一個(gè)網(wǎng)站的基本結(jié)構(gòu)放在幾個(gè)簡(jiǎn)單的HTML文件中(模板),以后我們要做的只是將文本傳到服務(wù)器,讓程序按照模板自動(dòng)生成網(wǎng)頁(yè),從而使管理大型網(wǎng)站變得容易。
所以,利用SHTML格式的頁(yè)面目的和 ASP 差不多,但是因?yàn)槭?API 所以運(yùn)轉(zhuǎn)速度更快,效率更高,比ASP快,比HTML慢,但由于可以使用服務(wù)器端包含,因此使頁(yè)面更新容易(特別是批量更新banner,版權(quán)等),想象一下吧,你有一段 HTML,要在中間穿插一些特殊的服務(wù)端腳本,比如插入其他 HTML 段落,你選擇 ASP 來(lái)完成這個(gè)任務(wù),但是如果任務(wù)更繁重,需要更多的時(shí)間,比如 5 s,這個(gè)時(shí)候你不用 ASP 而用 SHTML,或許處理時(shí)間就只用 4 s 了.
動(dòng)態(tài)網(wǎng)頁(yè)是與靜態(tài)網(wǎng)頁(yè)相對(duì)應(yīng)的,也就是說(shuō),網(wǎng)頁(yè) URL的后綴不是.htm、.html、.shtml、.xml等靜態(tài)網(wǎng)頁(yè)的常見形式,而是以.asp、.jsp、.php、.perl、.cgi等形式為后綴,并且在動(dòng)態(tài)網(wǎng)頁(yè)網(wǎng)址中有一個(gè)標(biāo)志性的符號(hào)“?”,如有這樣一個(gè)動(dòng)態(tài)網(wǎng)頁(yè)的地址為:
http://www.pagehome.cn/ip/index.asp?id=1
這就是一個(gè)典型的動(dòng)態(tài)網(wǎng)頁(yè)URL形式。
這里說(shuō)的動(dòng)態(tài)網(wǎng)頁(yè),與網(wǎng)頁(yè)上的各種動(dòng)畫、滾動(dòng)字幕等視覺上的“動(dòng)態(tài)效果”沒有直接關(guān)系,動(dòng)態(tài)網(wǎng)頁(yè)也可以是純文字內(nèi)容的,也可以是包含各種動(dòng)畫的內(nèi)容,這些只是網(wǎng)頁(yè)具體內(nèi)容的表現(xiàn)形式,無(wú)論網(wǎng)頁(yè)是否具有動(dòng)態(tài)效果,采用動(dòng)態(tài)網(wǎng)站技術(shù)生成的網(wǎng)頁(yè)都稱為動(dòng)態(tài)網(wǎng)頁(yè)。
從網(wǎng)站瀏覽者的角度來(lái)看,無(wú)論是動(dòng)態(tài)網(wǎng)頁(yè)還是靜態(tài)網(wǎng)頁(yè),都可以展示基本的文字和圖片信息,但從網(wǎng)站開發(fā)、管理、維護(hù)的角度來(lái)看就有很大的差別。網(wǎng)絡(luò)營(yíng)銷教學(xué)網(wǎng)站將動(dòng)態(tài)網(wǎng)頁(yè)的一般特點(diǎn)簡(jiǎn)要?dú)w納如下:
(1)動(dòng)態(tài)網(wǎng)頁(yè)以數(shù)據(jù)庫(kù)技術(shù)為基礎(chǔ),可以大大降低網(wǎng)站維護(hù)的工作量;
(2)采用動(dòng)態(tài)網(wǎng)頁(yè)技術(shù)的網(wǎng)站可以實(shí)現(xiàn)更多的功能,如用戶注冊(cè)、用戶登錄、在線調(diào)查、用戶管理、訂單管理等等;
(3)動(dòng)態(tài)網(wǎng)頁(yè)實(shí)際上并不是獨(dú)立存在于服務(wù)器上的網(wǎng)頁(yè)文件,只有當(dāng)用戶請(qǐng)求時(shí)服務(wù)器才返回一個(gè)完整的網(wǎng)頁(yè);
(4)動(dòng)態(tài)網(wǎng)頁(yè)中的“?”對(duì)搜索引擎檢索存在一定的問(wèn)題,搜索引擎一般不可能從一個(gè)網(wǎng)站的數(shù)據(jù)庫(kù)中訪問(wèn)全部網(wǎng)頁(yè),或者出于技術(shù)方面的考慮,搜索蜘蛛不去抓取網(wǎng)址中“?”后面的內(nèi)容,因此采用動(dòng)態(tài)網(wǎng)頁(yè)的網(wǎng)站在進(jìn)行搜索引擎推廣時(shí)需要做一定的技術(shù)處理才能適應(yīng)搜索引擎的要求。
dhtml:
確切地說(shuō),DHTML只是一種制作網(wǎng)頁(yè)的概念,實(shí)際上沒有一個(gè)組織或機(jī)構(gòu)推出過(guò)所謂的DHTML標(biāo)準(zhǔn)或技術(shù)規(guī)范之類的。DHTML不是一種技術(shù)、標(biāo)準(zhǔn)或規(guī)范,DHTML只是一種將目前已有的網(wǎng)頁(yè)技術(shù)、語(yǔ)言標(biāo)準(zhǔn)整和運(yùn)用,制作出能在下載后仍然能實(shí)時(shí)變換頁(yè)面元素效果的網(wǎng)頁(yè)的設(shè)計(jì)概念。
DHTML大致包含以下網(wǎng)頁(yè)技術(shù)、標(biāo)準(zhǔn)或規(guī)范:
HTML 4.0 :沒什么好說(shuō)的,網(wǎng)頁(yè)的基礎(chǔ)語(yǔ)言標(biāo)準(zhǔn)。
CSSL:注意!不是CSS,是CSSL,它是Clent-Side Scripting Language的縮寫,譯作“客戶端腳本語(yǔ)言”,主要有JavaScript(JS),VBScript(VBS),JScript。Netscape主要支持JS,IE主要支持JS,VBS和JScript。
DOM:Document Object Model的縮寫,譯作“文檔對(duì)象模型”,是W3C日前極力推廣的web技術(shù)標(biāo)準(zhǔn)之一,它將網(wǎng)頁(yè)中的內(nèi)容抽象成對(duì)象,每個(gè)對(duì)象擁有各自的屬性(Properties)、方法(Method)和事件(Events),這些都可以通過(guò)上面講到的CSSL來(lái)進(jìn)行控制。IE和NS的對(duì)象模型都是以W3C的公布的DOM為基準(zhǔn),加上自己的Extended Object(擴(kuò)展對(duì)象)來(lái)生成的。
CSS :這才是Cascading Style Sheets(層疊樣式表單)的縮寫,也是在論壇討論最多的技術(shù)規(guī)范,它是HTML的輔助設(shè)計(jì)規(guī)范,用來(lái)彌補(bǔ)HTML在排版上的所受的限制導(dǎo)致的不足,它是DOM的一部分。理論上說(shuō)通過(guò)CSSL動(dòng)態(tài)地改變CSS屬性可以做出任何你想要的頁(yè)面視覺效果。
所以,簡(jiǎn)單地說(shuō),要實(shí)現(xiàn)DHTML,就是以HTML為基礎(chǔ),運(yùn)用DOM將頁(yè)面元素對(duì)象化,利用CSSL控制這些對(duì)象的CSS屬性以達(dá)到網(wǎng)頁(yè)的動(dòng)態(tài)視覺效果。
shtml:
問(wèn)起SHTML和HTML的區(qū)別,如果用一句話來(lái)解釋就是:SHTML 不是HTML而是一種服務(wù)器 API,shtml是服務(wù)器動(dòng)態(tài)產(chǎn)成的html.
雖然兩者都是超文本格式,但shtml是一種用于SSI技術(shù)的文件。 也就是Server Side Include--SSI 服務(wù)器端包含指令。 如果Web Server有SSI功能的話(大多數(shù)(尤其是基于Unix平臺(tái))的WEB服務(wù)器如Netscape Enterprise Server等均支持SSI命令)。 會(huì)對(duì)shtml文件特殊招待。 先掃一次shtml文件看沒有特殊的SSI指令現(xiàn)在。 有就按Web Server設(shè)定規(guī)則解釋SSI指令。 解釋完后跟一般html一起掉去客戶端。
shtml:使用SSI(Server Side Include)的html文件擴(kuò)展名,SSI(Server Side Include),通常稱為"服務(wù)器端嵌入"或者叫"服務(wù)器端包含",是一種類似于ASP的基于服務(wù)器的網(wǎng)頁(yè)制作技術(shù)。
SSI工作原理:將內(nèi)容發(fā)送到瀏覽器之前,可以使用“服務(wù)器端包含 (SSI)”指令將文本、圖形或應(yīng)用程序信息包含到網(wǎng)頁(yè)中。例如,可以使用 SSI 包含時(shí)間/日期戳、版權(quán)聲明或供客戶填寫并返回的表單。對(duì)于在多個(gè)文件中重復(fù)出現(xiàn)的文本或圖形,使用包含文件是一種簡(jiǎn)便的方法。將內(nèi)容存入一個(gè)包含文件中即可,而不必將內(nèi)容輸入所有文件。通過(guò)一個(gè)非常簡(jiǎn)單的語(yǔ)句即可調(diào)用包含文件,此語(yǔ)句指示 Web 服務(wù)器將內(nèi)容插入適當(dāng)網(wǎng)頁(yè)。而且,使用包含文件時(shí),對(duì)內(nèi)容的所有更改只需在一個(gè)地方就能完成。
因?yàn)榘?SSI 指令的文件要求特殊處理,所以必須為所有 SSI 文件賦予 SSI 文件擴(kuò)展名。默認(rèn)擴(kuò)展名是 .stm、.shtm 和 .shtml
Web 服務(wù)器在處理網(wǎng)頁(yè)的同時(shí)處理 SSI 指令。當(dāng) Web 服務(wù)器遇到 SSI 指令時(shí),直接將包含文件的內(nèi)容插入 HTML 網(wǎng)頁(yè)。如果“包含文件”中包含 SSI 指令,則同時(shí)插入此文件。除了用于包含文件的基本指令之外,還可以使用 SSI 指令插入文件的相關(guān)信息(如文件的大小)或者運(yùn)行應(yīng)用程序或 shell 命令。
網(wǎng)站維護(hù)常常碰到的一個(gè)問(wèn)題是,網(wǎng)站的結(jié)構(gòu)已經(jīng)固定,卻為了更新一點(diǎn)內(nèi)容而不得不重做一大批網(wǎng)頁(yè)。SSI提供了一種簡(jiǎn)單、有效的方法來(lái)解決這一問(wèn)題,它將一個(gè)網(wǎng)站的基本結(jié)構(gòu)放在幾個(gè)簡(jiǎn)單的HTML文件中(模板),以后我們要做的只是將文本傳到服務(wù)器,讓程序按照模板自動(dòng)生成網(wǎng)頁(yè),從而使管理大型網(wǎng)站變得容易。
所以,利用SHTML格式的頁(yè)面目的和 ASP 差不多,但是因?yàn)槭?API 所以運(yùn)轉(zhuǎn)速度更快,效率更高,比ASP快,比HTML慢,但由于可以使用服務(wù)器端包含,因此使頁(yè)面更新容易(特別是批量更新banner,版權(quán)等),想象一下吧,你有一段 HTML,要在中間穿插一些特殊的服務(wù)端腳本,比如插入其他 HTML 段落,你選擇 ASP 來(lái)完成這個(gè)任務(wù),但是如果任務(wù)更繁重,需要更多的時(shí)間,比如 5 s,這個(gè)時(shí)候你不用 ASP 而用 SHTML,或許處理時(shí)間就只用 4 s 了.
xhtml:
HTML是一種基本的WEB網(wǎng)頁(yè)設(shè)計(jì)語(yǔ)言,XHTML是一個(gè)基于XML的置標(biāo)語(yǔ)言,看起來(lái)與HTML有些相象,只有一些小的但重要的區(qū)別,XHTML就是一個(gè)扮演著類似HTML的角色的XML,所以,本質(zhì)上說(shuō),XHTML是一個(gè)過(guò)渡技術(shù),結(jié)合了XML(有幾分)的強(qiáng)大功能及HTML(大多數(shù))的簡(jiǎn)單特性。
2000年底,國(guó)際W3C(World Wide Web Consortium)組織公布發(fā)行了XHTML 1.0版本。XHTML 1.0是一種在HTML 4.0基礎(chǔ)上優(yōu)化和改進(jìn)的的新語(yǔ)言,目的是基于XML應(yīng)用。XHTML是一種增強(qiáng)了的HTML,它的可擴(kuò)展性和靈活性將適應(yīng)未來(lái)網(wǎng)絡(luò)應(yīng)用更多的需求。下面是W3C的HTML工作組主席Steven Pemberton回答的關(guān)于XHTML的常見基礎(chǔ)問(wèn)題。
(1)XHTML解決HTML語(yǔ)言所存在的嚴(yán)重制約其發(fā)展的問(wèn)題。HTML發(fā)展到今天存在三個(gè)主要缺點(diǎn):不能適應(yīng)現(xiàn)在越多的網(wǎng)絡(luò)設(shè)備和應(yīng)用的需要,比如手機(jī)、PDA、信息家電都不能直接顯示HTML;由于HTML代碼不規(guī)范、臃腫,瀏覽器需要足夠智能和龐大才能夠正確顯示HTML;數(shù)據(jù)與表現(xiàn)混雜,這樣你的頁(yè)面要改變顯示,就必須重新制作HTML。因此HTML需要發(fā)展才能解決這個(gè)問(wèn)題,于是W3C又制定了XHTML,XHTML是HTML向XML過(guò)度的一個(gè)橋梁。
(2)XML是web發(fā)展的趨勢(shì),所以人們急切的希望加入XML的潮流中。XHTML是當(dāng)前替代HTML4標(biāo)記語(yǔ)言的標(biāo)準(zhǔn),使用XHTML 1.0,只要你小心遵守一些簡(jiǎn)單規(guī)則,就可以設(shè)計(jì)出既適合XML系統(tǒng),又適合當(dāng)前大部分HTML瀏覽器的頁(yè)面。這個(gè)意思就是說(shuō),你可以立刻設(shè)計(jì)使用XML,而不需要等到人們都使用支持XML的瀏覽器。這個(gè)指導(dǎo)方針可以使web平滑的過(guò)渡到XML。
(3)使用XHTML的另一個(gè)優(yōu)勢(shì)是:它非常嚴(yán)密。當(dāng)前網(wǎng)絡(luò)上的HTML的糟糕情況讓人震驚,早期的瀏覽器接受私有的HTML標(biāo)簽,所以人們?cè)陧?yè)面設(shè)計(jì)完畢后必須使用各種瀏覽器來(lái)檢測(cè)頁(yè)面,看是否兼容,往往會(huì)有許多莫名其妙的差異,人們不得不修改設(shè)計(jì)以便適應(yīng)不同的瀏覽器。
(4)XHTML是能與其它基于XML的標(biāo)記語(yǔ)言、應(yīng)用程序及協(xié)議進(jìn)行良好的交互工作。
(5)XHTML是Web標(biāo)準(zhǔn)家族的一部分,能很好在無(wú)線設(shè)備等其它用戶代理上。
(6)在網(wǎng)站設(shè)計(jì)方面,XHTML可助你去掉表現(xiàn)層代碼的惡習(xí),幫助你養(yǎng)成標(biāo)記校驗(yàn)來(lái)測(cè)試頁(yè)面工作的習(xí)慣。
ASP,JSP,PHP有什么區(qū)別和共同點(diǎn)?
回答(一)
不同:
1.web服務(wù)器不一樣,就是運(yùn)行環(huán)境不一樣。一般ASP用IIS,PHP用APACHE
2.語(yǔ)法不一樣,ASP是VBS/JS,JSP和JAVA語(yǔ)法相似,PHP語(yǔ)法和C,JAVA相似
3.運(yùn)行方式不一樣,ASP,PHP是解釋型的,JSP是可編譯型的
相同:
1.都是服務(wù)器端嵌入式腳本語(yǔ)言
2.能實(shí)現(xiàn)CGI所能實(shí)現(xiàn)的大部分東西。
回答(二)
asp是vb
jsp是java
php是c/c++
回答(三)
ASP、JSP與PHP的比較
目前,最常用的三種動(dòng)態(tài)網(wǎng)頁(yè)語(yǔ)言有ASP(Active Server Pages),JSP(Java Server Pages),PHP (Hypertext Preprocessor)。
簡(jiǎn)介
ASP全名Active Server Pages,是一個(gè)WEB服務(wù)器端的開發(fā)環(huán)境, 利用它可以產(chǎn)生和運(yùn)行動(dòng)態(tài)的、交互的、高性能的WEB服務(wù)應(yīng)用程序。ASP采用腳本語(yǔ)言VB Script(Java script)作為自己的開發(fā)語(yǔ)言。
PHP是一種跨平臺(tái)的服務(wù)器端的嵌入式腳本語(yǔ)言. 它大量地借用C,Java和Perl語(yǔ)言的語(yǔ)法, 并耦合PHP自己的特性,使WEB開發(fā)者能夠快速地寫出動(dòng)態(tài)生成頁(yè)面.它支持目前絕大多數(shù)數(shù)據(jù)庫(kù)。還有一點(diǎn),PHP是完全免費(fèi)的,不用花錢,你可以從PHP官方站點(diǎn)(http://www.php.net)自由下載。而且你可以不受限制地獲得源碼,甚至可以從中加進(jìn)你自己需要的特色。
JSP 是Sun公司推出的新一代站點(diǎn)開發(fā)語(yǔ)言,它完全解決了目前ASP,PHP的一個(gè)通病--腳本級(jí)執(zhí)行(據(jù)說(shuō)PHP4 也已經(jīng)在Zend 的支持下,實(shí)現(xiàn)編譯運(yùn)行).Sun 公司借助自己在Java 上的不凡造詣,將Java 從Java 應(yīng)用程序 和 Java Applet 之外,又有新的碩果,就是Jsp--Java Server Page。Jsp 可以在Serverlet和JavaBean的支持下,完成功能強(qiáng)大的站點(diǎn)程序。
三者都提供在 HTML 代碼中混合某種程序代碼、由語(yǔ)言引擎解釋執(zhí)行程序代碼的能力。但JSP代碼被編譯成 Servlet 并由 Java 虛擬機(jī)解釋執(zhí)行,這種編譯操作僅在對(duì) JSP 頁(yè)面的第一次請(qǐng)求時(shí)發(fā)生。在 ASP 、PHP、JSP 環(huán)境下, HTML 代碼主要負(fù)責(zé)描述信息的顯示樣式,而程序代碼則用來(lái)描述處理邏輯。普通的 HTML 頁(yè)面只依賴于 Web 服務(wù)器,而 ASP 、PHP、JSP 頁(yè)面需要附加的語(yǔ)言引擎分析和執(zhí)行程序代碼。程序代碼的執(zhí)行結(jié)果被重新嵌入到HTML 代碼中,然后一起發(fā)送給瀏覽器。 ASP 、PHP、 JSP三者都是面向 Web 服務(wù)器的技術(shù),客戶端瀏覽器不需要任何附加的軟件支持。
技術(shù)特點(diǎn)
ASP:
1. 使用 VBScript 、 JScript 等簡(jiǎn)單易懂的腳本語(yǔ)言,結(jié)合 HTML 代碼,即可快速地完成網(wǎng)站的應(yīng)用程序。
2. 無(wú)須 compile 編譯,容易編寫,可在服務(wù)器端直接執(zhí)行。
3. 使用普通的文本編輯器,如 Windows 的記事本,即可進(jìn)行編輯設(shè)計(jì)。
4. 與瀏覽器無(wú)關(guān) (Browser Independence), 用戶端只要使用可執(zhí)行 HTML 碼的瀏覽器,即可瀏覽 Active Server Pages 所設(shè)計(jì)的網(wǎng)頁(yè)內(nèi)容。 Active Server Pages 所使用的腳本語(yǔ)言 (VBScript 、 Jscript) 均在 WEB 服務(wù)器端執(zhí)行,用戶端的瀏覽器不需要能夠執(zhí)行這些腳本語(yǔ)言。
5.Active Server Pages 能與任何 ActiveX scripting 語(yǔ)言相容。除了可使用 VBScript 或 JScript 語(yǔ)言來(lái)設(shè)計(jì)外,還通過(guò) plug-in 的方式,使用由第三方所提供的其他腳本語(yǔ)言,譬如 REXX 、 Perl 、 Tcl 等。腳本引擎是處理腳本程序的 COM(Component Object Model) 物件。
6. 可使用服務(wù)器端的腳本來(lái)產(chǎn)生客戶端的腳本。
7.ActiveX Server Components(ActiveX 服務(wù)器元件 ) 具有無(wú)限可擴(kuò)充性。可以使用 Visual Basic 、 Java 、 Visual C++ 、 COBOL 等編程語(yǔ)言來(lái)編寫你所需要的ActiveX Server Component 。
PHP:
1.?dāng)?shù)據(jù)庫(kù)連接
PHP可以編譯成具有與許多數(shù)據(jù)庫(kù)相連接的函數(shù)。PHP與MySQL是現(xiàn)在絕佳的組合。你還可以自己編寫外圍的函數(shù)取間接存取數(shù)據(jù)庫(kù)。通過(guò)這樣的途徑當(dāng)你更換使用的數(shù)據(jù)庫(kù)時(shí),可以輕松地更改編碼以適應(yīng)這樣的變。PHPLIB就是最常用的可以提供一般事務(wù)需要的一系列基庫(kù)。但PHP提供的數(shù)據(jù)庫(kù)接口支持彼此不統(tǒng)一,比如對(duì)Oracle, MySQL, Sybase的接口,彼此都不一樣。這也是PHP的一個(gè)弱點(diǎn)。
2.面向?qū)ο缶幊?
PHP提供了類和對(duì)象。基于web的編程工作非常需要面向?qū)ο缶幊棠芰ΑHP支持構(gòu)造器、提取類等。
JSP:
1.將內(nèi)容的生成和顯示進(jìn)行分離
使用JSP技術(shù),Web頁(yè)面開發(fā)人員可以使用HTML或者XML標(biāo)識(shí)來(lái)設(shè)計(jì)和格式化最終頁(yè)面。使用JSP標(biāo)識(shí)或者小腳本來(lái)生成頁(yè)面上的動(dòng)態(tài)內(nèi)容。生成內(nèi)容的邏輯被封裝在標(biāo)識(shí)和JavaBeans組件中,并且捆綁在小腳本中,所有的腳本在服務(wù)器端運(yùn)行。如果核心邏輯被封裝在標(biāo)識(shí)和Beans中,那么其他人,如Web管理人員和頁(yè)面設(shè)計(jì)者,能夠編輯和使用JSP頁(yè)面,而不影響內(nèi)容的生成。
在服務(wù)器端,JSP引擎解釋JSP標(biāo)識(shí)和小腳本,生成所請(qǐng)求的內(nèi)容(例如,通過(guò)訪問(wèn)JavaBeans組件,使用JDBCTM技術(shù)訪問(wèn)數(shù)據(jù)庫(kù),或者包含文件),并且將結(jié)果以HTML(或者XML)頁(yè)面的形式發(fā)送回瀏覽器。這有助于作者保護(hù)自己的代碼,而又保證任何基于HTML的Web瀏覽器的完全可用性。
2.強(qiáng)調(diào)可重用的組件
絕大多數(shù)JSP頁(yè)面依賴于可重用的,跨平臺(tái)的組件(JavaBeans或者Enterprise JavaBeansTM組件)來(lái)執(zhí)行應(yīng)用程序所要求的更為復(fù)雜的處理。開發(fā)人員能夠共享和交換執(zhí)行普通操作的組件,或者使得這些組件為更多的使用者或者客戶團(tuán)體所使用。基于組件的方法加速了總體開發(fā)過(guò)程,并且使得各種組織在他們現(xiàn)有的技能和優(yōu)化結(jié)果的開發(fā)努力中得到平衡。
3.采用標(biāo)識(shí)簡(jiǎn)化頁(yè)面開發(fā)
Web頁(yè)面開發(fā)人員不會(huì)都是熟悉腳本語(yǔ)言的編程人員。JavaServer Page技術(shù)封裝了許多功能,這些功能是在易用的、與JSP相關(guān)的XML標(biāo)識(shí)中進(jìn)行動(dòng)態(tài)內(nèi)容生成所需要的。標(biāo)準(zhǔn)的JSP標(biāo)識(shí)能夠訪問(wèn)和實(shí)例化JavaBeans組件,設(shè)置或者檢索組件屬性,下載Applet,以及執(zhí)行用其他方法更難于編碼和耗時(shí)的功能。
通過(guò)開發(fā)定制化標(biāo)識(shí)庫(kù),JSP技術(shù)是可以擴(kuò)展的。今后,第三方開發(fā)人員和其他人員可以為常用功能創(chuàng)建自己的標(biāo)識(shí)庫(kù)。這使得Web頁(yè)面開發(fā)人員能夠使用熟悉的工具和如同標(biāo)識(shí)一樣的執(zhí)行特定功能的構(gòu)件來(lái)工作。
JSP技術(shù)很容易整合到多種應(yīng)用體系結(jié)構(gòu)中,以利用現(xiàn)存的工具和技巧,并且擴(kuò)展到能夠支持企業(yè)級(jí)的分布式應(yīng)用。作為采用Java技術(shù)家族的一部分,以及Java 2(企業(yè)版體系結(jié)構(gòu))的一個(gè)組成部分,JSP技術(shù)能夠支持高度復(fù)雜的基于Web的應(yīng)用。
由于JSP頁(yè)面的內(nèi)置腳本語(yǔ)言是基于Java編程語(yǔ)言的,而且所有的JSP頁(yè)面都被編譯成為Java Servlet,JSP頁(yè)面就具有Java技術(shù)的所有好處,包括健壯的存儲(chǔ)管理和安全性。
作為Java平臺(tái)的一部分,JSP擁有Java編程語(yǔ)言“一次編寫,各處運(yùn)行”的特點(diǎn)。隨著越來(lái)越多的供應(yīng)商將JSP支持添加到他們的產(chǎn)品中,您可以使用自己所選擇的服務(wù)器和工具,更改工具或服務(wù)器并不影響當(dāng)前的應(yīng)用。
應(yīng)用范圍
ASP是Microsoft開發(fā)的動(dòng)態(tài)網(wǎng)頁(yè)語(yǔ)言,也繼承了微軟產(chǎn)品的一貫傳統(tǒng)——只能運(yùn)行于微軟的服務(wù)器產(chǎn)品,IIS (Internet Information Server) (windows NT)和PWS(Personal Web Server)(windows 98)上。Unix下也有ChiliSoft的插件來(lái)支持ASP,但是ASP本身的功能有限,必須通過(guò)ASP+COM的組合來(lái)擴(kuò)充,Unix下的COM實(shí)現(xiàn)起來(lái)非常困難。
PHP3可在Windows,Unix,Linux的Web服務(wù)器上正常運(yùn)行,還支持IIS,Apache等通用Web服務(wù)器,用戶更換平臺(tái)時(shí),無(wú)需變換PHP3代碼,可即拿即用.
JSP同PHP3類似,幾乎可以運(yùn)行于所有平臺(tái)。如Win NT,Linux,Unix. NT下IIS通過(guò)一個(gè)插件,例如JRUN或者ServletExec,就能支持JSP。著名的Web服務(wù)器Apache已經(jīng)能夠支持JSP。由于Apache廣泛應(yīng)用在NT、Unix和Linux上,因此JSP有更廣泛的運(yùn)行平臺(tái)。雖然現(xiàn)在NT操作系統(tǒng)占了很大的市場(chǎng)份額,但是在服務(wù)器方面Unix的優(yōu)勢(shì)仍然很大,而新崛起的Linux更是來(lái)勢(shì)不小。從一個(gè)平臺(tái)移植到另外一個(gè)平臺(tái),JSP和JavaBean甚至不用重新編譯,因?yàn)镴ava字節(jié)碼都是標(biāo)準(zhǔn)的與平臺(tái)無(wú)關(guān)的。
性能比較
有人做過(guò)試驗(yàn),對(duì)這三種語(yǔ)言分別做循環(huán)性能測(cè)試及存取Oracle數(shù)據(jù)庫(kù)測(cè)試。
在循環(huán)性能測(cè)試中,JSP只用了令人吃驚的四秒鐘就結(jié)束了20000*20000的循環(huán)。而ASP、PHP測(cè)試的是2000*2000循環(huán)(少一個(gè)數(shù)量級(jí)),卻分別用了63秒和84秒。(參考PHPLIB)。
數(shù)據(jù)庫(kù)測(cè)試中,三者分別對(duì) Oracle 8 進(jìn)行 1000 次 Insert,Update,Select,和Delete: Jsp 需要 13 秒,Php 需要 69 秒,ASP則 需要 73 秒。
前景分析
目前在國(guó)內(nèi)PHP與ASP應(yīng)用最為廣泛。而JSP由于是一種較新的技術(shù),國(guó)內(nèi)采用的較少。但在國(guó)外,JSP已經(jīng)是比較流行的一種技術(shù),尤其是電子商務(wù)類的網(wǎng)站,多采用JSP。
采用PHP的網(wǎng)站如新浪網(wǎng)(sina)、中國(guó)人(Chinaren)等,但由于PHP本身存在的一些缺點(diǎn),使得它不適合應(yīng)用于大型電子商務(wù)站點(diǎn),而更適合一些小型的商業(yè)站點(diǎn)。
首先,PHP缺乏規(guī)模支持。其次,缺乏多層結(jié)構(gòu)支持。對(duì)于大負(fù)荷站點(diǎn),解決方法只有一個(gè):分布計(jì)算。數(shù)據(jù)庫(kù)、應(yīng)用邏輯層、表示邏輯層彼此分開,而且同層也可以根據(jù)流量分開,組成二維陣列。而PHP則缺乏這種支持。還有上面提到過(guò)的一點(diǎn),PHP提供的數(shù)據(jù)庫(kù)接口支持不統(tǒng)一,這就使得它不適合運(yùn)用在電子商務(wù)中。
ASP和JSP則沒有以上缺陷,ASP可以通過(guò)Microsoft Windowsd的COM/DCOM獲得ActiveX規(guī)模支持,通過(guò)DCOM和Transcation Server獲得結(jié)構(gòu)支持;JSP可以通過(guò)SUN Java的Java Class和EJB獲得規(guī)模支持,通過(guò)EJB/CORBA以及眾多廠商的Application Server獲得結(jié)構(gòu)支持。
三者中,JSP應(yīng)該是未來(lái)發(fā)展的趨勢(shì)。世界上一些大的電子商務(wù)解決方案提供商都采用JSP/Servlet。比較出名的如IBM的E-business,它的核心是采用JSP/Servlet的WebSphere;西方另外一個(gè)非常著名的電子商務(wù)軟件提供商,Intershop。它原來(lái)的產(chǎn)品Intershop1 2, 3, 4占據(jù)了主要的電子商務(wù)軟件份額。它們都是通過(guò)CGI來(lái)提供支持 的。但去年10月后它推出了Enfinity,一個(gè)采用JSP/Servlet的電子商務(wù)Application Server,而且聲言不再開發(fā)傳統(tǒng)軟件。
總之
ASP,PHP,JSP三者都有相當(dāng)數(shù)量的支持者,由此也可以看出三者各有所長(zhǎng)。正在學(xué)習(xí)或使用動(dòng)態(tài)頁(yè)面的朋友可根據(jù)三者的特點(diǎn)選擇一種適合自己的語(yǔ)言。
asp jsp cgi php之間的區(qū)別和優(yōu)點(diǎn)
發(fā)布者:Iease 發(fā)布時(shí)間:2006-7-22
就我個(gè)人的意見,PHP只適合做小型的網(wǎng)站開發(fā),大型的站點(diǎn)就很困難了(能做,但是很痛苦!)
擴(kuò)充性:
1、PHP用光了自己的一堆函數(shù)以后,要擴(kuò)充似乎是很困難的。據(jù)我一位玩PHP和c比較好的朋友說(shuō),“可以擴(kuò)充,要用c來(lái)寫,然后編譯進(jìn)PHP里面去”。請(qǐng)問(wèn),用PHP的朋友中有多少能達(dá)到這個(gè)水平的?
2、ASP。如果你認(rèn)為asp只是那幾個(gè)response/request等對(duì)象,那你錯(cuò)了。
個(gè)人認(rèn)為,ASP只是一種技術(shù),如果沒有MS的com/com+,asp就什么都沒有了。說(shuō)得過(guò)份一點(diǎn),asp本身連一個(gè)賦值語(yǔ)句,連一個(gè)if都沒有!
正是基于此,ASP擁有很強(qiáng)的擴(kuò)充性。你不熟悉vbs,你可以用jscript,你可以用perlscript,你可以通過(guò)安裝xscript來(lái)使用你熟悉的腳本語(yǔ)言。你會(huì)vb/delphi/vc/bcb..嗎?那你可以寫自己的組件,然后用asp來(lái)使用它。
3、java :同asp一樣,java通過(guò)不斷增多的(公司發(fā)布的或是自己編譯的)class來(lái)擴(kuò)展自已。而且jsp與asp相比有一個(gè)大的優(yōu)點(diǎn):jsp是基于java的,擁有強(qiáng)大的程序語(yǔ)法和天然的平臺(tái)無(wú)關(guān)性。
執(zhí)行效率:
1、PHP是基于解釋型的。
“因?yàn)椴挥镁幾g而且高階,所以這類語(yǔ)言的程序效率通常很差,又因?yàn)樵汲绦虼a暴露在外,所以拿它來(lái)寫寫工具程序自己用可以,但是拿來(lái)開發(fā)軟件產(chǎn)品比較不恰當(dāng)(除非你不在乎原始碼外流)。”(此段引用蔡學(xué)墉文章“你該學(xué)什么程序語(yǔ)言?”)。
雖然PHP可以通過(guò)使用第三方的zend(我對(duì)PHP不是太熟悉)來(lái)彌補(bǔ)這個(gè)缺陷,但是似乎Zend是收費(fèi)的,而且使用第三方的東西已經(jīng)不是PHP本身的討論了。
2、ASP
asp發(fā)展較早,因此早期的asp1.0、asp2.0、asp3.0都是基于解釋的,有同PHP相同的問(wèn)題。不過(guò)自MS的.net以后,asp.net在第一次加載時(shí)進(jìn)行編譯,并加載于內(nèi)存中,因此第一次以后的執(zhí)行效率已經(jīng)是相當(dāng)快速了。
3、JSP。
java本身就是屬于編譯的語(yǔ)言,目前的jsp服務(wù)器產(chǎn)品大多是做JIT編譯的,JSP在第一次加載時(shí)被編譯,因而與PHP相比在執(zhí)行效率上有明顯的提高。
1 |
<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> |
1 |
<dwr> <allow> <create creator="struts" javascript="testFrm"> <param name="formBean" value="testActionForm"/> </create> </allow> </dwr> |
1 |
<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> |
1 |
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(); } } |
1 |
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"); } } |
1 |
<%@ 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> |
1 |
<%@ 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> |
來(lái)源:http://macrochen.blogdriver.com/macrochen/1207263.html
(一)環(huán)境說(shuō)明
(1)服務(wù)器有4臺(tái),一臺(tái)安裝apache,三臺(tái)安裝tomcat
(2)apache2.0.55、tomcat5.5.15、jk2.0.4、jdk1.5.6或jdk1.4.2
(3)ip配置,一臺(tái)安裝apache的ip為192.168.0.88,三臺(tái)安裝tomcat的服務(wù)器ip分別為192.168.0.1/2/4
(二)安裝過(guò)程
(1)在三臺(tái)要安裝tomcat的服務(wù)器上先安裝jdk
(2)配置jdk的安裝路徑,在環(huán)境變量path中加入jdk的bin路徑,新建環(huán)境變量JAVA_HOME指向jdk的安裝路徑
(3)在三臺(tái)要安裝tomcat的服務(wù)器上分別安裝tomcat,調(diào)試三個(gè)tomcat到能夠正常啟動(dòng)
(4)tomcat的默認(rèn)WEB服務(wù)端口是8080,默認(rèn)的模式是單獨(dú)服務(wù),我的三個(gè)tomcat的WEB服務(wù)端口修改為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" />
依次修改每個(gè)tomcat的監(jiān)聽端口(7080/8888/9999)
(5)分別測(cè)試每個(gè)tomcat的啟動(dòng)是否正常
http://192.168.0.1:7080
http://192.168.0.2:8888
http://192.168.0.4:9999
(三)負(fù)載均衡配置過(guò)程
(1)在那臺(tái)要安裝apache的服務(wù)器上安裝apache2.0.55,我的安裝路徑為默認(rèn)C:\Program Files\Apache Group\Apache2
(2)安裝后測(cè)試apache能否正常啟動(dòng),調(diào)試到能夠正常啟動(dòng)http://192.168.0.88
(3)下載jk2.0.4后解壓縮文件
(4)將解壓縮后的目錄中的modules目錄中的mod_jk2.so文件復(fù)制到apache的安裝目錄下的modules目錄中,我的為C:\Program Files\Apache Group\Apache2\modules
(5)修改apache的安裝目錄中的conf目錄的配置文件httpd.conf,在文件中加LoadModule模塊配置信息的最后加上一句LoadModule jk2_module modules/mod_jk2.so
(6)分別修改三個(gè)tomcat的配置文件conf/server.xml,修改內(nèi)容如下
修改前
<!-- 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)然后重啟三個(gè)tomcat,調(diào)試能夠正常啟動(dòng)。
(8)在apache的安裝目錄中的conf目錄下創(chuàng)建文件workers2.propertie,寫入文件內(nèi)容如下
# fine the communication channel
[channel.socket:192.168.0.1:8009]
info=Ajp13 forwarding over socket
#配置第一個(gè)服務(wù)器
tomcatId=tomcat1 #要和tomcat的配置文件server.xml中的jvmRoute="tomcat1"名稱一致
debug=0
lb_factor=1 #負(fù)載平衡因子,數(shù)字越大請(qǐng)求被分配的幾率越高
# 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)在三個(gè)tomcat的安裝目錄中的webapps建立相同的應(yīng)用,我和應(yīng)用目錄名為TomcatDemo,在三個(gè)應(yīng)用目錄中建立相同 WEB-INF目錄和頁(yè)面index.jsp,index.jsp的頁(yè)面內(nèi)容如下
<%@ 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 屬性設(shè)置
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服務(wù)器和三個(gè)tomcat服務(wù)器,到此負(fù)載 均衡已配置完成。測(cè)試負(fù)載均衡先測(cè)試apache,訪問(wèn)http://192.168.0.88/jkstatus.jsp
能否正常訪問(wèn),并查詢其中的內(nèi)容,有三個(gè)tomcat的相關(guān)配置信息和負(fù)載說(shuō)明,訪問(wèn)http://192.168.0.88/TomcatDemo/index.jsp看能夠運(yùn)行,
能運(yùn)行,則已建立負(fù)載均衡。
(四)tomcat集群配置
(1)負(fù)載均衡配置的條件下配置tomcat集群
(2)分別修改三個(gè)tomcat的配置文件conf/server.xml,修改內(nèi)容如下
修改前
<!--
<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 -->
將集群配置選項(xiàng)的注釋放開即可,如上。
(3)重啟三個(gè)tomcat。到此tomcat的集群已配置完成。
(五)應(yīng)用配置
對(duì)于要進(jìn)行負(fù)載和集群的的tomcat目錄下的webapps中的應(yīng)用中的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>
來(lái)源:http://blog.csdn.net/ladofwind/archive/2006/08/29/1138484.aspx
在單一的服務(wù)器上執(zhí)行WEB應(yīng)用程序有一些重大的問(wèn)題,當(dāng)網(wǎng)站成功建成并開始接受大量請(qǐng)求時(shí),單一服務(wù)器終究無(wú)法滿足需要處理的負(fù)荷量,所以就有點(diǎn)顯得有點(diǎn)力不從心了。另外一個(gè)常見的問(wèn)題是會(huì)產(chǎn)生單點(diǎn)故障,如果該服務(wù)器壞掉,那么網(wǎng)站就立刻無(wú)法運(yùn)作了。不論是因?yàn)橐休^佳的擴(kuò)充性還是容錯(cuò)能力,我們都會(huì)想在一臺(tái)以上的服務(wù)器計(jì)算機(jī)上執(zhí)行WEB應(yīng)用程序。所以,這時(shí)候我們就需要用到集群這一門技術(shù)了。
在進(jìn)入集群系統(tǒng)架構(gòu)探討之前,先定義一些專門術(shù)語(yǔ):
1. 集群(Cluster):是一組獨(dú)立的計(jì)算機(jī)系統(tǒng)構(gòu)成一個(gè)松耦合的多處理器系統(tǒng),它們之間通過(guò)網(wǎng)絡(luò)實(shí)現(xiàn)進(jìn)程間的通信。應(yīng)用程序可以通過(guò)網(wǎng)絡(luò)共享內(nèi)存進(jìn)行消息傳送,實(shí)現(xiàn)分布式計(jì)算機(jī)。
2. 負(fù)載均衡(Load Balance):先得從集群講起,集群就是一組連在一起的計(jì)算機(jī),從外部看它是一個(gè)系統(tǒng),各節(jié)點(diǎn)可以是不同的操作系統(tǒng)或不同硬件構(gòu)成的計(jì)算機(jī)。如一個(gè)提供Web服務(wù)的集群,對(duì)外界來(lái)看是一個(gè)大Web服務(wù)器。不過(guò)集群的節(jié)點(diǎn)也可以單獨(dú)提供服務(wù)。
3. 特點(diǎn):在現(xiàn)有網(wǎng)絡(luò)結(jié)構(gòu)之上,負(fù)載均衡提供了一種廉價(jià)有效的方法擴(kuò)展服務(wù)器帶寬和增加吞吐量,加強(qiáng)網(wǎng)絡(luò)數(shù)據(jù)處理能力,提高網(wǎng)絡(luò)的靈活性和可用性。集群系統(tǒng)(Cluster)主要解決下面幾個(gè)問(wèn)題:
高可靠性(HA):利用集群管理軟件,當(dāng)主服務(wù)器故障時(shí),備份服務(wù)器能夠自動(dòng)接管主服務(wù)器的工作,并及時(shí)切換過(guò)去,以實(shí)現(xiàn)對(duì)用戶的不間斷服務(wù)。
高性能計(jì)算(HP):即充分利用集群中的每一臺(tái)計(jì)算機(jī)的資源,實(shí)現(xiàn)復(fù)雜運(yùn)算的并行處理,通常用于科學(xué)計(jì)算領(lǐng)域,比如基因分析,化學(xué)分析等。
負(fù)載平衡:即把負(fù)載壓力根據(jù)某種算法合理分配到集群中的每一臺(tái)計(jì)算機(jī)上,以減輕主服務(wù)器的壓力,降低對(duì)主服務(wù)器的硬件和軟件要求。
目前比較常用的負(fù)載均衡技術(shù)主要有:
1. 基于DNS的負(fù)載均衡
通過(guò)DNS服務(wù)中的隨機(jī)名字解析來(lái)實(shí)現(xiàn)負(fù)載均衡,在DNS服務(wù)器中,可以為多個(gè)不同的地址配置同一個(gè)名字,而最終查詢這個(gè)名字的客戶機(jī)將在解析這個(gè)名字時(shí)得到其中一個(gè)地址。因此,對(duì)于同一個(gè)名字,不同的客戶機(jī)會(huì)得到不同的地址,他們也就訪問(wèn)不同地址上的Web服務(wù)器,從而達(dá)到負(fù)載均衡的目的。
2. 反向代理負(fù)載均衡 (如Apache+JK2+Tomcat這種組合)
使用代理服務(wù)器可以將請(qǐng)求轉(zhuǎn)發(fā)給內(nèi)部的Web服務(wù)器,讓代理服務(wù)器將請(qǐng)求均勻地轉(zhuǎn)發(fā)給多臺(tái)內(nèi)部Web服務(wù)器之一上,從而達(dá)到負(fù)載均衡的目的。這種代理方式與普通的代理方式有所不同,標(biāo)準(zhǔn)代理方式是客戶使用代理訪問(wèn)多個(gè)外部Web服務(wù)器,而這種代理方式是多個(gè)客戶使用它訪問(wèn)內(nèi)部Web服務(wù)器,因此也被稱為反向代理模式。
3. 基于NAT(Network Address Translation)的負(fù)載均衡技術(shù) (如Linux Virtual Server,簡(jiǎn)稱LVS)
網(wǎng)絡(luò)地址轉(zhuǎn)換為在內(nèi)部地址和外部地址之間進(jìn)行轉(zhuǎn)換,以便具備內(nèi)部地址的計(jì)算機(jī)能訪問(wèn)外部網(wǎng)絡(luò),而當(dāng)外部網(wǎng)絡(luò)中的計(jì)算機(jī)訪問(wèn)地址轉(zhuǎn)換網(wǎng)關(guān)擁有的某一外部地址時(shí),地址轉(zhuǎn)換網(wǎng)關(guān)能將其轉(zhuǎn)發(fā)到一個(gè)映射的內(nèi)部地址上。因此如果地址轉(zhuǎn)換網(wǎng)關(guān)能將每個(gè)連接均勻轉(zhuǎn)換為不同的內(nèi)部服務(wù)器地址,此后外部網(wǎng)絡(luò)中的計(jì)算機(jī)就各自與自己轉(zhuǎn)換得到的地址上服務(wù)器進(jìn)行通信,從而達(dá)到負(fù)載分擔(dān)的目的。
介紹完上面的集群技術(shù)之后,下面就基于Tomcat的集群架構(gòu)方案進(jìn)行說(shuō)明:
上面是采用了Apache httpd作為web服務(wù)器的,即作為Tomcat的前端處理器,根據(jù)具體情況而定,有些情況下是不需要Apache httpd作為 web 服務(wù)器的,如系統(tǒng)展現(xiàn)沒有靜態(tài)頁(yè)面那就不需要Apache httpd,那時(shí)可以直接使用Tomcat作為web 服務(wù)器來(lái)使用。使用Apache httpd主要是它在處理靜態(tài)頁(yè)面方面的能力比Tomcat強(qiáng)多了。
1、 用戶的網(wǎng)頁(yè)瀏覽器做完本地 DNS和企業(yè)授權(quán)的DNS之的請(qǐng)求/響應(yīng)后,這時(shí)候企業(yè)授權(quán)的DNS(即21cn BOSS DNS)會(huì)給用戶本地的DNS服務(wù)器提供一個(gè)NAT請(qǐng)求分配器(即網(wǎng)關(guān))IP。
2、 NAT分配器,它會(huì)根據(jù)特定的分配算法,來(lái)決定要將連接交給哪一臺(tái)內(nèi)部 Apache httpd來(lái)處理請(qǐng)求。大多數(shù)的NAT請(qǐng)求分配器提供了容錯(cuò)能力:根據(jù)偵測(cè)各種WEB服務(wù)器的失效狀況,停止將請(qǐng)求分配給已經(jīng)宕掉的服務(wù)器。并且有些分配器還可以監(jiān)測(cè)到WEB服務(wù)器機(jī)器的負(fù)載情況,并將請(qǐng)求分配給負(fù)載最輕的服務(wù)器等等。Linux Virtual Server是一個(gè)基于Linux操作系統(tǒng)上執(zhí)行的VS-NAT開源軟件套件,而且它有豐富的功能和良好的說(shuō)明文件。商業(yè)硬件解決方案 Foundry Networks的ServerIron是目前業(yè)界公認(rèn)最佳的請(qǐng)求分配器之一。
3、 Apache httpd + Mod_JK2在這里是作為負(fù)載均衡器,那為什么要做集群呢?如果集群系統(tǒng)要具備容錯(cuò)能力,以便在任何單一的硬件或軟件組件失效時(shí)還能100%可用,那么集群系統(tǒng)必須沒有單點(diǎn)故障之憂。所以,不能只架設(shè)一臺(tái)有mod_jk2的Apache httpd,因?yàn)槿绻?nbsp;httpd或mod_jk2失效了,將不會(huì)再有請(qǐng)求被會(huì)送交到任何一個(gè)Tomcat 實(shí)例。這種情況下,Apache httpd就是瓶勁,特別在訪問(wèn)量大的網(wǎng)站。
4、 Mod_JK2負(fù)載均衡與故障復(fù)原,決定把Apache httpd當(dāng)成web服務(wù)器,而且使用mod_jk2將請(qǐng)求傳送給Tomcat,則可以使用mod_jk2的負(fù)載均衡與容錯(cuò)功能。在集群系統(tǒng)中,帶有mod_jk2的Apache httpd可以做的事情包括:
A、 將請(qǐng)求分配至一或多個(gè)Tomcat實(shí)例上
你可以在mod_jk2的workers.properties文件中,設(shè)定許多Tomcat實(shí)例,并賦于每個(gè)實(shí)例一個(gè)lb_factor值,以作為請(qǐng)求分配的加權(quán)因子。
B、 偵測(cè)Tomcat實(shí)例是否失敗
當(dāng)Tomcat實(shí)例的連接器服務(wù)不再響應(yīng)時(shí),mod_jk2會(huì)及時(shí)偵測(cè)到,并停止將請(qǐng)求送給它。其他的Tomcat實(shí)例則會(huì)接受失效實(shí)例的負(fù)載。
C、 偵測(cè)Tomcat實(shí)例在失效后的何時(shí)恢復(fù)
因連接器服務(wù)失效,而停止將請(qǐng)求分配給Tomcat實(shí)例之后,mod_jk2會(huì)周期性地檢查是否已恢復(fù)使用性,并自動(dòng)將其加入現(xiàn)行的Tomcat實(shí)例池中。
5、 Tomcat中的集群原理是通過(guò)組播的方式進(jìn)行節(jié)點(diǎn)的查找并使用TCP連接進(jìn)行會(huì)話的復(fù)制。這里提示一下就是,對(duì)每個(gè)請(qǐng)求的處理,Tomcat都會(huì)進(jìn)行會(huì)話復(fù)制,復(fù)制后的會(huì)話將會(huì)慢慢變得龐大。
6、 Mod_jk2同時(shí)支持會(huì)話親和和會(huì)話復(fù)制。在tomcat 5中如何實(shí)現(xiàn)會(huì)話親和和會(huì)話復(fù)制?把server.xml中的<cluster/>標(biāo)簽去掉就實(shí)現(xiàn)會(huì)話親和,把<cluster/>標(biāo)簽加上就實(shí)現(xiàn)會(huì)話復(fù)制。
7、 會(huì)話親和:就是表示來(lái)自同會(huì)話的所有請(qǐng)求都由相同的Tomcat 實(shí)例來(lái)處理,這種情況下,如果Tomcat實(shí)例或所執(zhí)行的服務(wù)器機(jī)器失效,也會(huì)喪失Servlet的會(huì)話數(shù)據(jù)。即使在集群系統(tǒng)中執(zhí)行更多的Tomcat實(shí)例,也永遠(yuǎn)不會(huì)復(fù)制會(huì)話數(shù)據(jù)。這樣是提高集群性能的一種方案,但不具備有容錯(cuò)能力了。
8、 使用會(huì)話復(fù)制,則當(dāng)一個(gè)Tomcat實(shí)例宕掉時(shí),由于至少還有另一個(gè)Tomcat實(shí)例保有一份會(huì)話狀態(tài)數(shù)據(jù),因而數(shù)據(jù)不會(huì)喪失。但性能會(huì)有所降低。
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
25 | 26 | 27 | 28 | 29 | 30 | 31 | |||
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 | 1 | 2 | 3 | 4 | 5 |