欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
理解 JavaScript 閉包

目錄

簡(jiǎn)介

返回目錄

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

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

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

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

對象屬性名解析

返回目錄

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

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

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

值的賦予

返回目錄

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

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

可以通過(guò)下面語(yǔ)句來(lái)創(chuàng )建名為 “testNumber” 的屬性:

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

在賦值之前,對象中沒(méi)有“testNumber” 屬性,但在賦值后,則創(chuàng )建一個(gè)屬性。之后的任何賦值語(yǔ)句都不需要再創(chuàng )建這個(gè)屬性,而只會(huì )重新設置它的值:

objectRef.testNumber = 8;
/* - or:- */
objectRef[”testNumber”] = 8;

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

值的讀取

返回目錄

當讀取對象的屬性值時(shí),原型對象的作用便體現出來(lái)。如果對象的原型中包含屬性訪(fǎng)問(wèn)器(property accessor)所使用的屬性名,那么該屬性的值就會(huì )返回:

/* 為命名屬性賦值。如果在賦值前對象沒(méi)有相應的屬性,那么賦值后就會(huì )得到一個(gè):*/
objectRef.testNumber = 8;

/* 從屬性中讀取值 */
var val = objectRef.testNumber;

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

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

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

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

/* 創(chuàng )建 - MyObject1 - 類(lèi)型對象的函數*/
function MyObject1(formalParameter){
/* 給創(chuàng )建的對象添加一個(gè)名為 - testNumber - 的屬性
并將傳遞給構造函數的第一個(gè)參數指定為該屬性的值:*/
this.testNumber = formalParameter;
}
/* 創(chuàng )建 - MyObject2 - 類(lèi)型對象的函數*/
function MyObject2(formalParameter){
/* 給創(chuàng )建的對象添加一個(gè)名為 - testString - 的屬性
并將傳遞給構造函數的第一個(gè)參數指定為該屬性的值:*/
this.testString = formalParameter;
}

/* 接下來(lái)的操作用 MyObject1 類(lèi)的實(shí)例替換了所有與 MyObject2 類(lèi)的實(shí)例相關(guān)聯(lián)的原型。而且,為 MyObject1 構造函數傳遞了參數 - 8 - ,因而其 - testNumber - 屬性被賦予該值:*/
MyObject2.prototype = new MyObject1( 8 );

/* 最后,將一個(gè)字符串作為構造函數的第一個(gè)參數,創(chuàng )建一個(gè) - MyObject2 - 的實(shí)例,并將指向該對象的引用賦給變量 - objectRef - :*/
var objectRef = new MyObject2( “String_Value” );

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

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

var val = objectRef.testString;

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

var val = objectRef.testNumber;

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

var val = objectRef.toString;

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

最后:

var val = objectRef.madeUpProperty;

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

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

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

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

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

執行環(huán)境

返回目錄

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

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

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

在創(chuàng )建執行環(huán)境的過(guò)程中,會(huì )按照定義的先后順序完成一系列操作。首先,在一個(gè)函數的執行環(huán)境中,會(huì )創(chuàng )建一個(gè)“活動(dòng)”對象?;顒?dòng)對象是規范中規定的另外一種機制。之所以稱(chēng)之為對象,是因為它擁有可訪(fǎng)問(wèn)的命名屬性,但是它又不像正常對象那樣具有原型(至少沒(méi)有預定義的原型),而且不能通過(guò) JavaScript 代碼直接引用活動(dòng)對象。

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

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

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

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

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

最后,要為使用 this 關(guān)鍵字而賦值。如果所賦的值引用一個(gè)對象,那么前綴以 this 關(guān)鍵字的屬性訪(fǎng)問(wèn)器就是引用該對象的屬性。如果所賦(內部)值是 null,那么 this 關(guān)鍵字則引用全局對象。

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

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

作用域鏈與 [[scope]]

返回目錄

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

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

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

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

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

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

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

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

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

在這種情況下,不同的是在全局執行環(huán)境的變量實(shí)例化過(guò)程中,會(huì )先為全局對象創(chuàng )建一個(gè)命名屬性。而在計算賦值語(yǔ)句之前,暫時(shí)不會(huì )創(chuàng )建函數對象,也不會(huì )將該函數對象的引用指定給全局對象的命名屬性。但是,最終還是會(huì )在全局執行環(huán)境中創(chuàng )建這個(gè)函數對象(當計算函數表達式時(shí)。譯者注),而為這個(gè)創(chuàng )建的函數對象的 [[scope]] 屬性指定的作用域鏈中仍然只包含全局對象。內部的函數聲明或表達式會(huì )導致在包含它們的外部函數的執行環(huán)境中創(chuàng )建相應的函數對象,因此這些函數對象的作用域鏈會(huì )稍微復雜一些。在下面的代碼中,先定義了一個(gè)帶有內部函數聲明的外部函數,然后調用外部函數:

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

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

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

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

返回目錄

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

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

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

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

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

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

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

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

其他例子

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

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

意外的閉包

返回目錄

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

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

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

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

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

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

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

在上面例子的第一個(gè)版本中,內部函數并沒(méi)有作為閉包發(fā)揮應有的作用。在那種情況下,反而是不使用閉包更有效率,因為不用重復創(chuàng )建許多本質(zhì)上相同的函數對象。

類(lèi)似地考量同樣適用于對象的構造函數。與下面代碼中的構造函數框架類(lèi)似的代碼并不罕見(jiàn):

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

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

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

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

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

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

返回目錄

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

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

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

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

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
JavaScript秘密花園
JS 語(yǔ)言核心(JavaScript權威指南第六版)(閱讀筆記)
函數(Functions)
高性能Javascript:高效的數據訪(fǎng)問(wèn)
前端常見(jiàn)20道高頻面試題深入解析
JavaScript執行環(huán)境+變量對象+作用域鏈+閉包
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久