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

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

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

    憨厚生

    ----Java's Slave----
    ***Java's Host***

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      165 隨筆 :: 17 文章 :: 90 評論 :: 0 Trackbacks

    #

    轉(zhuǎn) http://www.cn-cuckoo.com/wordpress/wp-content/uploads/2007/08/JavaScriptClosures.html

    Javascript 閉包

    翻譯:為之漫筆
    鏈接:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html

    簡介

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

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

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

    遺憾的是,要適當(dāng)?shù)乩斫忾]包就必須理解閉包背后運(yùn)行的機(jī)制,以及許多相關(guān)的技術(shù)細(xì)節(jié)。雖然本文的前半部分并沒有涉及 ECMA 262 規(guī)范指定的某些算法,但仍然有許多無法回避或簡化的內(nèi)容。對于個(gè)別熟悉對象屬性名解析的人來說,可以跳過相關(guān)的內(nèi)容,但是除非你對閉包也非常熟悉,否則最 好是不要跳過下面幾節(jié)。

    對象屬性名解析

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

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

    下面簡要介紹一下如何設(shè)置和讀取對象的屬性值,并最大程度地體現(xiàn)相應(yīng)的內(nèi)部細(xì)節(jié)。

    值的賦予

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

    var objectRef = new Object(); //創(chuàng)建一個(gè)普通的 javascript 對象。
    可以通過下面語句來創(chuàng)建名為 “testNumber” 的屬性:
    objectRef.testNumber = 5;

    /* - 或- */

    objectRef["testNumber"] = 5;
    在賦值之前,對象中沒有“testNumber” 屬性,但在賦值后,則創(chuàng)建一個(gè)屬性。之后的任何賦值語句都不需要再創(chuàng)建這個(gè)屬性,而只會重新設(shè)置它的值:
    objectRef.testNumber = 8;
    /* - 或- */
    objectRef["testNumber"] = 8;
    稍后我們會介紹,Javascript 對象都有原型(prototypes)屬性,而這些原型本身也是對象,因而也可以帶有命名的屬性。但是,原型對象命名屬性的作用并不體現(xiàn)在賦值階段。同 樣,在將值賦給其命名屬性時(shí),如果對象沒有該屬性則會創(chuàng)建該命名屬性,否則會重設(shè)該屬性的值。

    值的讀取

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

    /* 為命名屬性賦值。如果在賦值前對象沒有相應(yīng)的屬性,那么賦值后就會得到一個(gè):*/

    objectRef.testNumber = 8;



    /* 從屬性中讀取值 */



    var val = objectRef.testNumber;

    /* 現(xiàn)在, - val - 中保存著剛賦給對象命名屬性的值 8*/

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

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

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

    /* 創(chuàng)建 - MyObject1 - 類型對象的函數(shù)*/

    function MyObject1(formalParameter){

    /* 給創(chuàng)建的對象添加一個(gè)名為 - testNumber -

    的屬性并將傳遞給構(gòu)造函數(shù)的第一個(gè)參數(shù)指定為該屬性的值:*/


    this.testNumber = formalParameter;

    }



    /* 創(chuàng)建 - MyObject2 - 類型對象的函數(shù)*/

    function MyObject2(formalParameter){

    /* 給創(chuàng)建的對象添加一個(gè)名為 - testString -

    的屬性并將傳遞給構(gòu)造函數(shù)的第一個(gè)參數(shù)指定為該屬性的值:*/


    this.testString = formalParameter;

    }





    /* 接下來的操作用 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í)例,并將指向該對象的

    引用賦給變量 - objectRef - :*/




    var objectRef = new MyObject2( "String_Value" );

    被變量 objectRef 所引用的 MyObject2 的實(shí)例擁有一個(gè)原型鏈。該鏈中的第一個(gè)對象是在創(chuàng)建后被指定給 MyObject2 構(gòu)造函數(shù)的 prototype 屬性的 MyObject1 的一個(gè)實(shí)例。MyObject1 的實(shí)例也有一個(gè)原型,即與 Object.prototype 所引用的對象對應(yīng)的默認(rèn)的 Object 對象的原型。最后, Object.prototype 有一個(gè)值為 null 的原型,因此這條原型鏈到此結(jié)束。

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

    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)的命名屬性失敗后,解釋程序會繼續(xù)檢查其原型對象。而該實(shí)例的原型對象是 MyObject1 的實(shí)例,這個(gè)實(shí)例有一個(gè)名為“testNumber”的屬性并且值為 8,所以這個(gè)屬性訪問器最后會取得值 8。而且,雖然 MyObject1MyObject2 都沒有定義 toString 方法,但是當(dāng)屬性訪問器通過 objectRef 讀取 toString 屬性的值時(shí):

    var val = objectRef.toString;

    變量 val 也會被賦予一個(gè)函數(shù)的引用。這個(gè)函數(shù)就是在 Object.prototypetoString 屬性中所保存的函數(shù)。之所以會返回這個(gè)函數(shù),是因?yàn)榘l(fā)生了搜索objectRef 原型鏈的過程。當(dāng)在作為對象的 objectRef 中發(fā)現(xiàn)沒有“toString”屬性存在時(shí),會搜索其原型對象,而當(dāng)原型對象中不存在該屬性時(shí),則會繼續(xù)搜索原型的原型。而原型鏈中最終的原型是 Object.prototype,這個(gè)對象確實(shí)有一個(gè) toString 方法,因此該方法的引用被返回。

    最后:

    var val = objectRef.madeUpProperty;

    返回 undefined,因?yàn)樵谒阉髟玩湹倪^程中,直至 Object.prototype 的原型--null,都沒有找到任何對象有名為“madeUpPeoperty”的屬性,因此最終返回 undefined

    不論是在對象或?qū)ο蟮脑椭校x取命名屬性值的時(shí)候只返回首先找到的屬性值。而當(dāng)為對象的命名屬性賦值時(shí),如果對象自身不存在該屬性則創(chuàng)建相應(yīng)的屬性。

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

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

    標(biāo)識符解析、執(zhí)行環(huán)境和作用域鏈

    執(zhí)行環(huán)境

    執(zhí)行環(huán)境是 ECMAScript 規(guī)范(ECMA 262 第 3 版)用于定義 ECMAScript 實(shí)現(xiàn)必要行為的一個(gè)抽象的概念。對如何實(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))帶有屬性的對象--即使屬性不是公共屬性。

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

    當(dāng)調(diào)用一個(gè) JavaScript 函數(shù)時(shí),該函數(shù)就會進(jìn)入相應(yīng)的執(zhí)行環(huán)境。如果又調(diào)用了另外一個(gè)函數(shù)(或者遞歸地調(diào)用同一個(gè)函數(shù)),則又會創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并且在函數(shù)調(diào)用期間執(zhí)行過 程都處于該環(huán)境中。當(dāng)調(diào)用的函數(shù)返回后,執(zhí)行過程會返回原始執(zhí)行環(huán)境。因而,運(yùn)行中的 JavaScript 代碼就構(gòu)成了一個(gè)執(zhí)行環(huán)境棧。

    在創(chuàng)建執(zhí)行環(huán)境的過程中,會按照定義的先后順序完成一系列操作。首先,在一個(gè)函數(shù)的執(zhí)行環(huán)境中,會創(chuàng)建一個(gè)“活動”對象。活動對象是規(guī)范中規(guī)定的另 外一種機(jī)制。之所以稱之為對象,是因?yàn)樗鼡碛锌稍L問的命名屬性,但是它又不像正常對象那樣具有原型(至少沒有預(yù)定義的原型),而且不能通過 JavaScript 代碼直接引用活動對象。

    為函數(shù)調(diào)用創(chuàng)建執(zhí)行環(huán)境的下一步是創(chuàng)建一個(gè) arguments 對象,這是一個(gè)類似數(shù)組的對象,它以整數(shù)索引的數(shù)組成員一一對應(yīng)地保存著調(diào)用函數(shù)時(shí)所傳遞的參數(shù)。這個(gè)對象也有 lengthcallee 屬性(這兩個(gè)屬性與我們討論的內(nèi)容無關(guān),詳見規(guī)范)。然后,會為活動對象創(chuàng)建一個(gè)名為“arguments”的屬性,該屬性引用前面創(chuàng)建的 arguments 對象。

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

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

    根據(jù)聲明的局部變量創(chuàng)建的可變對象的屬性在變量實(shí)例化過程會被賦予 undefined 值。在執(zhí)行函數(shù)體內(nèi)的代碼、并計(jì)算相應(yīng)的賦值表達(dá)式之前不會對局部變量執(zhí)行真正的實(shí)例化。

    事實(shí)上,擁有 arguments 屬性的活動對象和擁有與函數(shù)局部變量對應(yīng)的命名屬性的可變對象是同一個(gè)對象。因此,可以將標(biāo)識符 arguments 作為函數(shù)的局部變量來看待。

    回到頂部

    最后,在this可以被使用之前,還必須先對其賦值。如果賦的值是一個(gè)對象的引用,則 this.m 訪問的便是該對象上的 m。如果(內(nèi)部)賦的值是 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. )

    創(chuàng)建全局執(zhí)行環(huán)境的過程會稍有不同,因?yàn)樗鼪]有參數(shù),所以不需要通過定義的活動對象來引用這些參數(shù)。但全局執(zhí)行環(huán)境也需要一個(gè)作用域,而它的作用域 鏈實(shí)際上只由一個(gè)對象--全局對象--組成。全局執(zhí)行環(huán)境也會有變量實(shí)例化的過程,它的內(nèi)部函數(shù)就是涉及大部分 JavaScript 代碼的、常規(guī)的頂級函數(shù)聲明。而且,在變量實(shí)例化過程中全局對象就是可變對象,這就是為什么全局性聲明 的函數(shù)是全局對象屬性的原因。全局性聲明的變量同樣如此。

    全局執(zhí)行環(huán)境也會使用 this 對象來引用全局對象。

    作用域鏈與 [[scope]]

    調(diào)用函數(shù)時(shí)創(chuàng)建的執(zhí)行環(huán)境會包含一個(gè)作用域鏈,這個(gè)作用域鏈?zhǔn)峭ㄟ^將該執(zhí)行環(huán)境的活動(可變)對象添加到保存于所調(diào)用函數(shù)對象的 [[scope]] 屬性中的作用域鏈前端而構(gòu)成的。所以,理解函數(shù)對象內(nèi)部的 [[scope]] 屬性的定義過程至關(guān)重要。

    在 ECMAScript 中,函數(shù)也是對象。函數(shù)對象在變量實(shí)例化過程中會根據(jù)函數(shù)聲明來創(chuàng)建,或者是在計(jì)算函數(shù)表達(dá)式或調(diào)用 Function 構(gòu)造函數(shù)時(shí)創(chuàng)建。

    通過調(diào)用 Function 構(gòu)造函數(shù)創(chuàng)建的函數(shù)對象,其內(nèi)部的 [[scope]] 屬性引用的作用域鏈中始終只包含全局對象。

    通過函數(shù)聲明或函數(shù)表達(dá)式創(chuàng)建的函數(shù)對象,其內(nèi)部的 [[scope]] 屬性引用的則是創(chuàng)建它們的執(zhí)行環(huán)境的作用域鏈。

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

    function exampleFunction(formalParameter){

    ... // 函數(shù)體內(nèi)的代碼

    }

    當(dāng)為創(chuàng)建全局執(zhí)行環(huán)境而進(jìn)行變量實(shí)例化時(shí),會根據(jù)上面的函數(shù)聲明創(chuàng)建相應(yīng)的函數(shù)對象。因?yàn)槿謭?zhí)行環(huán)境的作用域鏈中只包含全局對象,所以它就給自己創(chuàng)建的、并以名為“exampleFunction”的屬性引用的這個(gè)函數(shù)對象的內(nèi)部 [[scope]] 屬性,賦予了只包含全局對象的作用域鏈。

    當(dāng)在全局環(huán)境中計(jì)算函數(shù)表達(dá)式時(shí),也會發(fā)生類似的指定作用域鏈的過程:-

    var exampleFuncRef = function(){

    ... // 函數(shù)體代碼

    }

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

    內(nèi)部的函數(shù)聲明或表達(dá)式會導(dǎo)致在包含它們的外部函數(shù)的執(zhí)行環(huán)境中創(chuàng)建相應(yīng)的函數(shù)對象,因此這些函數(shù)對象的作用域鏈會稍微復(fù)雜一些。在下面的代碼中,先定義了一個(gè)帶有內(nèi)部函數(shù)聲明的外部函數(shù),然后調(diào)用外部函數(shù):

    function exampleOuterFunction(formalParameter){

    function exampleInnerFuncitonDec(){

    ... // 內(nèi)部函數(shù)體代碼

    }

    ... // 其余的外部函數(shù)體代碼

    }



    exampleOuterFunction( 5 );

    與外部函數(shù)聲明對應(yīng)的函數(shù)對象會在全局執(zhí)行環(huán)境的變量實(shí)例化過程中被創(chuàng)建。因此,外部函數(shù)對象的 [[scope]] 屬性中會包含一個(gè)只有全局對象的“單項(xiàng)目”作用域鏈。

    當(dāng)在全局執(zhí)行環(huán)境中調(diào)用 exampleOuterFunction 函數(shù)時(shí),會為該函數(shù)調(diào)用創(chuàng)建一個(gè)新的執(zhí)行環(huán)境和一個(gè)活動(可變)對象。這個(gè)新執(zhí)行環(huán)境的作用域就由新的活動對象后跟外部函數(shù)對象的 [[scope]] 屬性所引用的作用域鏈(只有全局對象)構(gòu)成。在新執(zhí)行環(huán)境的變量實(shí)例化過程中,會創(chuàng)建一個(gè)與內(nèi)部函數(shù)聲明對應(yīng)的函數(shù)對象,而同時(shí)會給這個(gè)函數(shù)對象的 [[scope]] 屬性指定創(chuàng)建該函數(shù)對象的執(zhí)行環(huán)境(即新執(zhí)行環(huán)境。譯者注)的作用域值--即一個(gè)包含活動對象后跟全局對象的作用域鏈。

    到目前為止,所有過程都是自動、或者由源代碼的結(jié)構(gòu)所控制的。但我們發(fā)現(xiàn),執(zhí)行環(huán)境的作用域鏈定義了執(zhí)行環(huán)境所創(chuàng)建的函數(shù)對象的 [[scope]] 屬性,而函數(shù)對象的 [[scope]] 屬性則定義了它的執(zhí)行環(huán)境的作用域(包括相應(yīng)的活動對象)。不過,ECMAScript 也提供了用于修改作用域鏈 with 語句。

    with 語句會計(jì)算一個(gè)表達(dá)式,如果該表達(dá)式是一個(gè)對象,那么就將這個(gè)對象添加到當(dāng)前執(zhí)行環(huán)境的作用域鏈中(在活動<可變>對象之前)。然后,執(zhí)行 with 語句(它自身也可能是一個(gè)語句塊)中的其他語句。之后,又恢復(fù)到調(diào)用它之前的執(zhí)行環(huán)境的作用域鏈中。

    with 語句不會影響在變量實(shí)例化過程中根據(jù)函數(shù)聲明創(chuàng)建函數(shù)對象。但是,可以在一個(gè) with 語句內(nèi)部對函數(shù)表達(dá)式求值:-

    /* 創(chuàng)建全局變量 - y - 它引用一個(gè)對象:- */

    var y = {x:5}; // 帶有一個(gè)屬性 - x - 的對象直接量

    function exampleFuncWith(){

    var z;

    /* 將全局對象 - y - 引用的對象添加到作用域鏈的前端:- */

    with(y){

    /* 對函數(shù)表達(dá)式求值,以創(chuàng)建函數(shù)對象并將該函數(shù)對象的引用指定給局部變量 - z - :- */

    z = function(){

    ... // 內(nèi)部函數(shù)表達(dá)式中的代碼;

    }

    }

    ...

    }



    /* 執(zhí)行 - exampleFuncWith - 函數(shù):- */

    exampleFuncWith();

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

    當(dāng)與 with 語句相關(guān)的語句塊執(zhí)行結(jié)束時(shí),執(zhí)行環(huán)境的作用域得以恢復(fù)(y 會被移除),但是已經(jīng)創(chuàng)建的函數(shù)對象(z。譯者注)的 [[scope]] 屬性所引用的作用域鏈中位于最前面的仍然是對象 y

    標(biāo)識符解析

    標(biāo)識符是沿作用域鏈逆向解析的。ECMA 262 將 this 歸類為關(guān)鍵字而不是標(biāo)識符,并非不合理。因?yàn)榻馕?this 值時(shí)始終要根據(jù)使用它的執(zhí)行環(huán)境來判斷,而與作用域鏈無關(guān)。

    標(biāo)識符解析從作用域鏈中的第一個(gè)對象開始。檢查該對象中是否包含與標(biāo)識符對應(yīng)的屬性名。因?yàn)樽饔糜蜴準(zhǔn)且粭l對象鏈,所以這個(gè)檢查過程也會包含相應(yīng)對象的原 型鏈(如果有)。如果沒有在作用域鏈的第一個(gè)對象中發(fā)現(xiàn)相應(yīng)的值,解析過程會繼續(xù)搜索下一個(gè)對象。這樣依次類推直至找到作用域鏈中包含以標(biāo)識符為屬性名的 對象為止,也有可能在作用域鏈的所有對象中都沒有發(fā)現(xiàn)該標(biāo)識符。

    當(dāng)基于對象使用屬性訪問器時(shí),也會發(fā)生與上面相同的標(biāo)識符解析過程。當(dāng)屬性訪問器中有相應(yīng)的屬性可以替換某個(gè)對象時(shí),這個(gè)屬性就成為表示該對象的標(biāo)識符,該對象在作用域鏈中的位置進(jìn)而被確定。全局對象始終都位于作用域鏈的尾端。

    因?yàn)榕c函數(shù)調(diào)用相關(guān)的執(zhí)行環(huán)境將會把活動(可變)對象添加到作用域鏈的前端,所以在函數(shù)體內(nèi)使用的標(biāo)識符會首先檢查自己是否與形式參數(shù)、內(nèi)部函數(shù)聲明的名稱或局部變量一致。這些都可以由活動(可變)對象的命名屬性來確定。

    閉包

    自動垃圾收集

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

    正常情況下,當(dāng)退出一個(gè)執(zhí)行環(huán)境時(shí)就會滿足類似的條件。此時(shí),作用域鏈結(jié)構(gòu)中的活動(可變)對象以及在該執(zhí)行環(huán)境中創(chuàng)建的任何對象--包括函數(shù)對象,都不再“可引用”,因此將成為垃圾收集的目標(biāo)。

    構(gòu)成閉包

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

    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ù)對象就不會被當(dāng)作垃圾收集,因?yàn)樵摵瘮?shù)對象被一個(gè)全局變量所引用,而且仍然是可以訪問的,甚至可以通過 globalVar(n) 來執(zhí)行。

    的確,情況比正常的時(shí)候要復(fù)雜一些。因?yàn)楝F(xiàn)在這個(gè)被變量 globalVar 引用的內(nèi)部函數(shù)對象的 [[scope]] 屬性所引用的作用域鏈中,包含著屬于創(chuàng)建該內(nèi)部函數(shù)對象的執(zhí)行環(huán)境的活動對象(和全局對象)。由于在執(zhí)行被 globalVar 引用的函數(shù)對象時(shí),每次都要把該函數(shù)對象的 [[scope]] 屬性所引用的整個(gè)作用域鏈添加到創(chuàng)建的(內(nèi)部函數(shù)的)執(zhí)行環(huán)境的作用域中(即此時(shí)的作用域中包括:內(nèi)部執(zhí)行環(huán)境的活動對象、外部執(zhí)行環(huán)境的活動對象、全局對象。譯者注), 所以這個(gè)(外部執(zhí)行環(huán)境的)活動對象不會被當(dāng)作垃圾收集。

    閉包因此而構(gòu)成。此時(shí),內(nèi)部函數(shù)對象擁有自由的變量,而位于該函數(shù)作用域鏈中的活動(可變)對象則成為與變量綁定的環(huán)境。

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

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

    如果再次調(diào)用 exampleClosureForm 函數(shù),如:-

    var secondGlobalVar = exampleClosureForm(12, 3);

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

    通過第二次執(zhí)行 exampleClosureForm 函數(shù),第二個(gè)、也是截然不同的閉包誕生了。

    通過執(zhí)行 exampleClosureForm 創(chuàng)建的兩個(gè)函數(shù)對象分別被指定給了全局變量 globalVarsecondGlobalVar,并返回了表達(dá)式 ((arg1 + arg2)/(innerArg + localVar))。該表達(dá)式對其中的四個(gè)標(biāo)識符應(yīng)用了不同的操作符。如何確定這些標(biāo)識符的值是體現(xiàn)閉包價(jià)值的關(guān)鍵所在。

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

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

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

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

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

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

    內(nèi)部函數(shù)自身也可能有內(nèi)部函數(shù)。在通過函數(shù)執(zhí)行返回內(nèi)部函數(shù)構(gòu)成閉包以后,相應(yīng)的閉包自身也可能會返回內(nèi)部函數(shù)從而構(gòu)成它們自己的閉包。每次作用域鏈嵌 套,都會增加由創(chuàng)建內(nèi)部函數(shù)對象的執(zhí)行環(huán)境引發(fā)的新活動對象。ECMAScript 規(guī)范要求作用域鏈?zhǔn)桥R時(shí)性的,但對作用域鏈的長度卻沒有加以限制。在具體實(shí)現(xiàn)中,可能會存在實(shí)際的限制,但還沒有發(fā)現(xiàn)有具體限制數(shù)量的報(bào)告。目前來看,嵌 套的內(nèi)部函數(shù)所擁有的潛能,仍然超出了使用它們的人的想像能力。

    通過閉包可以做什么?

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

    例 1:為函數(shù)引用設(shè)置延時(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í)行間隔。也就是說,當(dāng)在一段代碼中使用 setTimeout 時(shí),要將一個(gè)函數(shù)的引用作為它的第一個(gè)參數(shù),而將以毫秒表示的時(shí)間值作為第二個(gè)參數(shù)。但是,傳遞函數(shù)引用的同時(shí)無法為計(jì)劃執(zhí)行的函數(shù)提供參數(shù)。

    然而,可以在代碼中調(diào)用另外一個(gè)函數(shù),由它返回一個(gè)對內(nèi)部函數(shù)的引用,再把這個(gè)對內(nèi)部函數(shù)對象的引用傳遞給 setTimeout 函數(shù)。執(zhí)行這個(gè)內(nèi)部函數(shù)時(shí)要使用的參數(shù)在調(diào)用返回它的外部函數(shù)時(shí)傳遞。這樣,setTimeout 在執(zhí)行這個(gè)內(nèi)部函數(shù)時(shí),不用傳遞參數(shù),但該內(nèi)部函數(shù)仍然能夠訪問在調(diào)用返回它的外部函數(shù)時(shí)傳遞的參數(shù):

    function callLater(paramA, paramB, paramC){

    /* 返回一個(gè)由函數(shù)表達(dá)式創(chuàng)建的匿名內(nèi)部函數(shù)的引用:- */



    return (function(){

    /* 這個(gè)內(nèi)部函數(shù)將通過 - setTimeout - 執(zhí)行,

    而且當(dāng)它執(zhí)行時(shí)它會讀取并按照傳遞給

    外部函數(shù)的參數(shù)行事:

    */


    paramA[paramB] = paramC;

    });

    }



    ...



    /* 調(diào)用這個(gè)函數(shù)將返回一個(gè)在其執(zhí)行環(huán)境中創(chuàng)建的內(nèi)部函數(shù)對象的引用。

    傳遞的參數(shù)最終將作為外部函數(shù)的參數(shù)被內(nèi)部函數(shù)使用。

    返回的對內(nèi)部函數(shù)的引用被賦給一個(gè)全局變量:-

    */



    var functRef = callLater(elStyle, "display", "none");

    /* 調(diào)用 setTimeout 函數(shù),將賦給變量 - functRef -

    的內(nèi)部函數(shù)的引用作為傳遞的第一個(gè)參數(shù):- */



    hideMenu=setTimeout(functRef, 500);

    例 2: 通過對象實(shí)例方法關(guān)聯(lián)函數(shù)

    回到頂部

    許多時(shí)候我們需要將一個(gè)函數(shù)對象暫時(shí)掛到一個(gè)引用上留待后面執(zhí)行,因?yàn)椴坏鹊綀?zhí)行的時(shí)候是很難知道其具體參數(shù)的,而先前將它賦給那個(gè)引用的時(shí)候更是壓根不知道的。 (此段由 pangba 劉未鵬 翻譯)

    (luyy朋友的翻譯_2008-7-7更新)很多時(shí)候需要將一個(gè)函數(shù)引用進(jìn)行賦值,以便在將來某個(gè)時(shí)候執(zhí)行該函數(shù),在執(zhí)行這些函數(shù)時(shí)給函數(shù)提供參數(shù)將會是有用處的,但這些參數(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 對象來封裝與特定 DOM 元素的交互。這個(gè) JavaScript 對象具有 doOnClickdoMouseOverdoMouseOut 方法,并且當(dāng)用戶在該特定的 DOM 元素中觸發(fā)了相應(yīng)的事件時(shí)要執(zhí)行這些方法。不過,可能會創(chuàng)建與不同的 DOM 元素關(guān)聯(lián)的任意數(shù)量的 JavaScript 對象,而且每個(gè)對象實(shí)例并不知道實(shí)例化它們的代碼將會如何操縱它們(即注冊事件處理函數(shù)與定義相應(yīng)的事件處理函數(shù)分離。譯者注)。這些對象實(shí)例并不知道如 何在全局環(huán)境中引用它們自身,因?yàn)樗鼈儾恢缹付膫€(gè)全局變量(如果有)引用它們的實(shí)例。

    因而問題可以歸結(jié)為執(zhí)行一個(gè)與特定的 JavaScript 對象關(guān)聯(lián)的事件處理函數(shù),并且要知道調(diào)用該對象的哪個(gè)方法。

    下面這個(gè)例子使用了一個(gè)基于閉包構(gòu)建的一般化的函數(shù)(此句多謝未鵬指點(diǎn)),該函數(shù)會將對象實(shí)例與 DOM 元素事件關(guān)聯(lián)起來,安排執(zhí)行事件處理程序時(shí)調(diào)用對象實(shí)例的指定方法,給象的指定方法傳遞的參數(shù)是事件對象和與元素關(guān)聯(lián)的引用,該函數(shù)返回執(zhí)行相應(yīng)方法后的返回值。

    /* 一個(gè)關(guān)聯(lián)對象實(shí)例和事件處理器的函數(shù)。 

    它返回的內(nèi)部函數(shù)被用作事件處理器。對象實(shí)例以 - obj - 參數(shù)表示,

    而在該對象實(shí)例中調(diào)用的方法名則以 - methodName - (字符串)參數(shù)表示。

    */



    function associateObjWithEvent(obj, methodName){

    /* 下面這個(gè)返回的內(nèi)部函數(shù)將作為一個(gè) DOM 元素的事件處理器*/



    return (function(e){

    /* 在支持標(biāo)準(zhǔn) DOM 規(guī)范的瀏覽器中,事件對象會被解析為參數(shù) - e - ,

    若沒有正常解析,則使用 IE 的事件對象來規(guī)范化事件對象。

    */



    e = e||window.event;

    /* 事件處理器通過保存在字符串 - methodName - 中的方法名調(diào)用了對象

    - obj - 的一個(gè)方法。并傳遞已經(jīng)規(guī)范化的事件對象和觸發(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)的對象,

    DOM 元素的 ID 作為構(gòu)造函數(shù)的字符串參數(shù)。

    所創(chuàng)建的對象會在相應(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 - 值會在內(nèi)部通過類型轉(zhuǎn)換變?yōu)椴紶栔担员?- if - 語句加以判斷。

    因此,如果它引用一個(gè)對象結(jié)果將返回 true,如果是 null 則返回 false。

    下面的代碼塊只有當(dāng) - el - 變量返回一個(gè) DOM 元素時(shí)才會被執(zhí)行。

    */


    if(el){

    /* 為給元素的事件處理器指定一個(gè)函數(shù),該對象調(diào)用了

    - associateObjWithEvent - 函數(shù)。

    同時(shí)對象將自身(通過 - this - 關(guān)鍵字)作為調(diào)用方法的對象,

    并提供了調(diào)用的方法名稱。 - associateObjWithEvent - 函數(shù)會返回

    一個(gè)內(nèi)部函數(shù),該內(nèi)部函數(shù)被指定為 DOM 元素的事件處理器。

    在響應(yīng)事件時(shí),執(zhí)行這個(gè)內(nèi)部函數(shù)就會調(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í)例都會將自身與相應(yīng)的 DOM 元素關(guān)聯(lián)起來,而這些 DOM 元素不必知道其他代碼如何操縱它們(即當(dāng)觸發(fā)相應(yīng)事件時(shí),會執(zhí)行什么代碼。譯者注),也不必理會全局命名空間的影響以及與 DhtmlObject 的其他實(shí)例間存在沖突的危險(xiǎn)。

    例 3:包裝相關(guān)的功能

    閉包可以用于創(chuàng)建額外的作用域,通過該作用域可以將相關(guān)的和具有依賴性的代碼組織起來,以便將意外交互的風(fēng)險(xiǎn)降到最低。假設(shè)有一個(gè)用于構(gòu)建字符串的 函數(shù),為了避免重復(fù)性的連接操作(和創(chuàng)建眾多的中間字符串),我們的愿望是使用一個(gè)數(shù)組按順序來存儲字符串的各個(gè)部分,然后再使用 Array.prototype.join 方法(以空字符串作為其參數(shù))輸出結(jié)果。這個(gè)數(shù)組將作為輸出的緩沖器,但是將數(shù)組作為函數(shù)的局部變量又會導(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ù)的全局變量會使用這個(gè)緩沖 數(shù)組外,還會多出一個(gè)全局屬性引用數(shù)組自身。如此不僅使代碼變得不容易管理,而且,如果要在其他地方使用這個(gè)數(shù)組時(shí),開發(fā)者必須要再次定義函數(shù)和數(shù)組。這 樣一來,也使得代碼不容易與其他代碼整合,因?yàn)榇藭r(shí)不僅要保證所使用的函數(shù)名在全局命名空間中是唯一的,而且還要保證函數(shù)所依賴的數(shù)組在全局命名空間中也 必須是唯一的。

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

    其中的關(guān)鍵技巧在于通過執(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ù)提供。

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

    /* 聲明一個(gè)全局變量 - getImgInPositionedDivHtml - 

    并將一次調(diào)用一個(gè)外部函數(shù)表達(dá)式返回的內(nèi)部函數(shù)賦給它。



    這個(gè)內(nèi)部函數(shù)會返回一個(gè)用于表示絕對定位的 DIV 元素

    包圍著一個(gè) IMG 元素 的 HTML 字符串,這樣一來,

    所有可變的屬性值都由調(diào)用該函數(shù)時(shí)的參數(shù)提供:

    */


    var getImgInPositionedDivHtml = (function(){

    /* 外部函數(shù)表達(dá)式的局部變量 - buffAr - 保存著緩沖數(shù)組。

    這個(gè)數(shù)組只會被創(chuàng)建一次,生成的數(shù)組實(shí)例對內(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>'

    ];

    /* 返回作為對函數(shù)表達(dá)式求值后結(jié)果的內(nèi)部函數(shù)對象。

    這個(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;

    /* 返回通過使用空字符串(相當(dāng)于將數(shù)組元素連接起來)

    連接數(shù)組每個(gè)元素后形成的字符串:

    */


    return buffAr.join('');

    }); //:內(nèi)部函數(shù)表達(dá)式結(jié)束。

    })();

    /*^^- :單行外部函數(shù)表達(dá)式。*/

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

    其他例子

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

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

    意外的閉包

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

    正如下一節(jié)談到 IE 中內(nèi)存泄漏問題時(shí)所提及的,意外創(chuàng)建的閉包可能導(dǎo)致嚴(yán)重的負(fù)面效應(yīng),而且也會影響到代碼的性能。問題不在于閉包本身,如果能夠真正做到謹(jǐn)慎地使用它們,反而會有助于創(chuàng)建高效的代碼。換句話說,使用內(nèi)部函數(shù)會影響到效率。

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

    /* 定義一個(gè)全局變量,通過下面的函數(shù)將它的值

    作為查詢字符串的一部分添加到鏈接的 - href - 中:

    */


    var quantaty = 5;

    /* 當(dāng)給這個(gè)函數(shù)傳遞一個(gè)鏈接(作為函數(shù)中的參數(shù) - linkRef -)時(shí),

    會將一個(gè) onclick 事件處理器指定給該鏈接,該事件處理器

    將全局變量 - quantaty - 的值作為字符串添加到鏈接的 - href -

    屬性中,然后返回 true 使該鏈接在單擊后定位到由 - href -

    屬性包含的查詢字符串指定的資源:

    */


    function addGlobalQueryOnClick(linkRef){

    /* 如果可以將參數(shù) - linkRef - 通過類型轉(zhuǎn)換為 ture

    (說明它引用了一個(gè)對象):

    */


    if(linkRef){

    /* 對一個(gè)函數(shù)表達(dá)式求值,并將對該函數(shù)對象的引用

    指定給這個(gè)鏈接元素的 onclick 事件處理器:

    */


    linkRef.onclick = function(){

    /* 這個(gè)內(nèi)部函數(shù)表達(dá)式將查詢字符串

    添加到附加事件處理器的元素的 - href - 屬性中:

    */


    this.href += ('?quantaty='+escape(quantaty));

    return true;

    };

    }

    }

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

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

    /* 定義一個(gè)全局變量,通過下面的函數(shù)將它的值

    作為查詢字符串的一部分添加到鏈接的 - href - 中:

    */


    var quantaty = 5;



    /* 當(dāng)把一個(gè)鏈接(作為函數(shù)中的參數(shù) - linkRef -)傳遞給這個(gè)函數(shù)時(shí),

    會給這個(gè)鏈接添加一個(gè) onclick 事件處理器,該事件處理器會

    將全局變量 - quantaty - 的值作為查詢字符串的一部分添加到

    鏈接的 - href - 中,然后返回 true,以便單擊鏈接時(shí)定位到由

    作為 - href - 屬性值的查詢字符串所指定的資源:

    */


    function addGlobalQueryOnClick(linkRef){

    /* 如果 - linkRef - 參數(shù)能夠通過類型轉(zhuǎn)換為 true

    (說明它引用了一個(gè)對象):

    */


    if(linkRef){

    /* 將一個(gè)對全局函數(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ù)對象。

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

    function ExampleConst(param){

    /* 通過對函數(shù)表達(dá)式求值創(chuàng)建對象的方法,

    并將求值所得的函數(shù)對象的引用賦給要?jiǎng)?chuàng)建對象的屬性:

    */


    this.method1 = function(){

    ... // 方法體。

    };

    this.method2 = function(){

    ... // 方法體。

    };

    this.method3 = function(){

    ... // 方法體。

    };

    /* 把構(gòu)造函數(shù)的參數(shù)賦給對象的一個(gè)屬性:

    */


    this.publicProp = param;

    }

    每當(dāng)通過 new ExampleConst(n) 使用這個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè)對象時(shí),都會創(chuàng)建一組新的、作為對象方法的函數(shù)對象。因此,創(chuàng)建的對象實(shí)例越多,相應(yīng)的函數(shù)對象也就越多。

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

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

    function ExampleConst(param){

    /* 將構(gòu)造函數(shù)的參數(shù)賦給對象的一個(gè)屬性:

    */


    this.publicProp = param;

    }

    /* 通過對函數(shù)表達(dá)式求值,并將結(jié)果函數(shù)對象的引用

    指定給構(gòu)造函數(shù)原型的相應(yīng)屬性來創(chuàng)建對象的方法:

    */


    ExampleConst.prototype.method1 = function(){

    ... // 方法體。

    };

    ExampleConst.prototype.method2 = function(){

    ... // 方法體。

    };

    ExampleConst.prototype.method3 = function(){

    ... // 方法體。

    };



    Internet Explorer 的內(nèi)存泄漏問題

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

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

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

    多加注意可以避免形成循環(huán)引用,而在無法避免時(shí),也可以使用補(bǔ)償?shù)姆椒ǎ热缡褂?IE 的 onunload 事件來來清空(null)事件處理函數(shù)的引用。時(shí)刻意識到這個(gè)問題并理解閉包的工作機(jī)制是在 IE 中避免此類問題的關(guān)鍵。

    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 @ 2009-03-24 18:25 二胡 閱讀(111) | 評論 (0)編輯 收藏

    僅列出標(biāo)題
    共165頁: First 上一頁 84 85 86 87 88 89 90 91 92 下一頁 Last 
    主站蜘蛛池模板: 天天影视色香欲综合免费| 波多野结衣久久高清免费| 亚洲国产高清在线精品一区| 思思99re66在线精品免费观看| 好猛好深好爽好硬免费视频| 亚洲视频一区在线观看| 午夜一区二区免费视频| a级毛片无码免费真人| 永久在线观看免费视频| 久久亚洲AV成人无码国产最大| 亚洲国产精品久久久久| 免费在线观看黄网| 毛片免费观看网址| 毛片无码免费无码播放| 日韩毛片在线免费观看| 久久久久国产成人精品亚洲午夜 | 777爽死你无码免费看一二区| 国产一区二区三区亚洲综合| 亚洲精品午夜国产va久久| 亚洲AV无码乱码国产麻豆穿越| 免费大学生国产在线观看p| 免费成人黄色大片| 在线播放亚洲第一字幕| 亚洲第一页综合图片自拍| 国产桃色在线成免费视频| 免费毛片a线观看| 国产无限免费观看黄网站| 久爱免费观看在线网站| 美女被免费网站91色| 久久这里只精品国产免费10| 国产四虎免费精品视频| 91短视频免费在线观看| 成人免费毛片内射美女APP| 国产免费AV片无码永久免费 | 日日躁狠狠躁狠狠爱免费视频| 香蕉免费一级视频在线观看| 亚洲a一级免费视频| 久久精品免费视频观看| 美女被cao免费看在线看网站| 国产在线19禁免费观看国产| 日本免费一本天堂在线|