摘要:定義函數(shù)表達(dá)式的方式有兩種函數(shù)聲明。不過(guò),這并不是匿名函數(shù)唯一的用途??梢允褂妹瘮?shù)表達(dá)式來(lái)達(dá)成相同的結(jié)果閉包匿名函數(shù)和閉包是兩個(gè)概念,容易混淆。匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其對(duì)象通常指向通過(guò)改變函數(shù)的執(zhí)行環(huán)境的情況除外。
定義函數(shù)表達(dá)式的方式有兩種:
函數(shù)聲明。它的重要特征就是 函數(shù)聲明提升(function declaration hoisting) 即在執(zhí)行代碼之前會(huì)先讀取函數(shù)聲明。這就意味著可以把函數(shù)聲明放在調(diào)用它的語(yǔ)句后面。
函數(shù)表達(dá)式。
// 函數(shù)聲明 function functionName(params) { ... }
// 函數(shù)表達(dá)式有幾種不同的方式,下面是最常見(jiàn)的一種 var functionName = function(params) { ... }
上面這種形式看起來(lái)好像是常規(guī)的變量賦值語(yǔ)句。而右邊這種形式創(chuàng)建的函數(shù)叫做 匿名函數(shù) (anonymous function)(有時(shí)候也叫 拉姆達(dá)函數(shù) lambda),因?yàn)閒unction關(guān)鍵字后面沒(méi)有標(biāo)識(shí)符。
函數(shù)表達(dá)式與其他表達(dá)式一樣,在使用前必須先賦值,否則會(huì)導(dǎo)致出錯(cuò)。
sayHi(); // 錯(cuò)誤,函數(shù)還不存在 var sayHi = function () { console.log("Hi!"); };
表面上看,下面的代碼沒(méi)有問(wèn)題,condition為true時(shí),使用一個(gè)定義,否則使用另一個(gè)定義。實(shí)際上,在ECMAScript中屬于無(wú)效語(yǔ)法,JavaScript引擎會(huì)嘗試修正錯(cuò)誤,將其轉(zhuǎn)換為合理狀態(tài)。但問(wèn)題是瀏覽器嘗試修正錯(cuò)誤的做法并不一致。大多數(shù)瀏覽器會(huì)返回第二個(gè)聲明,忽略condition的值;Firefox會(huì)在condition為true的時(shí)候返回第一個(gè)聲明。因此這種做法很危險(xiǎn),不應(yīng)該出現(xiàn)在你的代碼中。
// 不要這樣做! if (condition) { function sayHi() { console.log("Hi!"); } } else { function sayHi() { console.log("Yo!"); } }
上述代碼改為函數(shù)表達(dá)式就沒(méi)有問(wèn)題
// 可以這樣做 var sayHi; if (condition) { sayHi = function() { console.log("Hi!"); } } else { sayHi = function() { console.log("Yo!"); } }
能夠創(chuàng)建函數(shù)再賦值給變量,也就能把函數(shù)作為其他函數(shù)的返回值。createComparisonFunction() 就返回了一個(gè)匿名函數(shù)。
function createComparisonFunction (propertyName) { return function (object1, object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } }
在把函數(shù)當(dāng)成值來(lái)使用的情況下,都可以使用匿名函數(shù)。不過(guò),這并不是匿名函數(shù)唯一的用途。
遞歸遞歸函數(shù)是在一個(gè)函數(shù)通過(guò)名字調(diào)用自身的情況下構(gòu)成的
// 經(jīng)典的遞歸階乘函數(shù) function factorial (num) { if (num <= -1) { return 1; } else { return num * factorial(num - 1); } }
雖然遞歸階乘函數(shù)表面看沒(méi)有什么問(wèn)題,但下面的代碼卻可能導(dǎo)致它出錯(cuò)
// 把factorial() 函數(shù)保存在變量anotherFactorial中 var anotherFactorial = factorial; // 將factorial設(shè)置為null // 現(xiàn)在指向原始函數(shù)的引用只剩下anotherFactorial factorial = null; // 原始函數(shù)必須執(zhí)行factorial() // 但factorial不再是函數(shù),所以導(dǎo)致出錯(cuò) anotherFactorial(4); // throw error!
可以使用 arguments.callee (指向正在執(zhí)行函數(shù)的指針)實(shí)現(xiàn)函數(shù)的遞歸調(diào)用
// 非嚴(yán)格模式 function factorial (num) { if (num <= -1) { return 1; } else { return num * arguments.callee(num - 1); } }
但在嚴(yán)格模式下不能通過(guò)腳本訪問(wèn) arguments.callee,會(huì)導(dǎo)致出錯(cuò)??梢允褂妹瘮?shù)表達(dá)式來(lái)達(dá)成相同的結(jié)果
var factorial = (function f(num) { if (num <= -1) { return 1; } else { return num * f(num - 1); } })閉包
匿名函數(shù) 和 閉包 是兩個(gè)概念,容易混淆。 閉包 是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。
創(chuàng)建閉包的常見(jiàn)方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),仍以前面的 createComparisonFunction() 函數(shù)為例
function createComparisonFunction (propertyName) { return function (object1, object2) { // 下面兩行代碼訪問(wèn)了外部函數(shù)中的變量propertyName // 即使這個(gè)內(nèi)部函數(shù)被返回了,而且是在其他地方被調(diào)用了 // 它仍然可以訪問(wèn)變量 propertyName var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } }
上述例子,即使內(nèi)部函數(shù)被返回了,在其他地方調(diào)用,它仍然可以訪問(wèn)propertName。因?yàn)檫@個(gè)內(nèi)部函數(shù)的作用域鏈中包含 createComparisonFunction() 的作用域。要搞清楚其中細(xì)節(jié),必須從理解函數(shù)被調(diào)用的時(shí)候都會(huì)發(fā)生什么入手。
第4章介紹過(guò) 作用域鏈。當(dāng)某個(gè)函數(shù)被 調(diào)用 時(shí)會(huì)發(fā)生下列事情:
創(chuàng)建一個(gè) 執(zhí)行環(huán)境(execution context) 及相應(yīng)的 作用域鏈
使用 arguments 和其他命名參數(shù)的值來(lái)初始化函數(shù)的 活動(dòng)對(duì)象(activation object)
形成作用域鏈。外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位...直至作為作用域終點(diǎn)的全局執(zhí)行環(huán)境
函數(shù)執(zhí)行過(guò)程中,為讀寫(xiě)變量的值,就需要在作用域鏈中查找變量。
function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var result = compare(5, 10);
后臺(tái)的每個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象——變量對(duì)象。全局環(huán)境的變量對(duì)象始終存在,而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象,則只在函數(shù)執(zhí)行的過(guò)程中存在。
在創(chuàng)建 compare() 函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈,這個(gè)作用域鏈被保存在內(nèi)部的[[scope]]屬性中。
當(dāng)調(diào)用 compare() 函數(shù)時(shí),會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過(guò)復(fù)制函數(shù)的[[scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。
此后,又有一個(gè)活動(dòng)對(duì)象(在此作為變量對(duì)象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端。
對(duì)于本例, compare() 函數(shù)的執(zhí)行環(huán)境而言,其作用域鏈中包含兩個(gè)變量對(duì)象:
本地活動(dòng)對(duì)象
全局活動(dòng)對(duì)象
作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表,它只引用但不實(shí)際包含變量對(duì)象。
無(wú)論什么時(shí)候在函數(shù)中訪問(wèn)一個(gè)變量時(shí),就會(huì)從作用域鏈中搜索具有相應(yīng)名字的變量。一般而言,當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷(xiāo)毀,內(nèi)存中近保存全局作用域(全局執(zhí)行環(huán)境的變量對(duì)象)。
但是閉包的情況有所不同
function createComparisonFunction (propertyName) { return function (object1, object2) { // 下面兩行代碼訪問(wèn)了外部函數(shù)中的變量propertyName // 即使這個(gè)內(nèi)部函數(shù)被返回了,而且是在其他地方被調(diào)用了 // 它仍然可以訪問(wèn)變量 propertyName // 即為 createComparisonFunction 的活動(dòng)對(duì)象 var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } } // 創(chuàng)建比較函數(shù) // 調(diào)用了 createComparisonFunction() 方法 // 創(chuàng)建了 createComparisonFunction 的活動(dòng)對(duì)象 // 返回內(nèi)部的匿名函數(shù) 保存在 compareNames // createComparisonFunction 執(zhí)行完畢 // 但它的活動(dòng)對(duì)象仍被 內(nèi)部匿名函數(shù)引用,所以活動(dòng)對(duì)象仍然存在,不會(huì)銷(xiāo)毀 var compareNames = createComparisonFunction("name"); // 此時(shí)result調(diào)用了 保存在 compareNames 的匿名函數(shù) // 該匿名函數(shù)保持了對(duì) createComparisonFunction 活動(dòng)對(duì)象的引用 var result = compareNames({ name: "Nicholas" }, { name: "Greg" }); // 即使 compareNames 執(zhí)行完畢,createComparisonFunction 活動(dòng)對(duì)象依然存在 // 需要手動(dòng)解除對(duì)匿名函數(shù)的引用(以便釋放內(nèi)存) compareNames = null;
首先,創(chuàng)建的比較函數(shù)被保存在變量compareNames中,而通過(guò)將其設(shè)置為null解除引用,就等于通知垃圾回收例程將其消除。隨著匿名函數(shù)的作用域鏈被銷(xiāo)毀,其他作用域鏈(除了全局作用域)也都可以安全地的銷(xiāo)毀了。圖7-2展示了調(diào)用compareNames()的過(guò)程中產(chǎn)生的作用域鏈之間的關(guān)系
由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存。過(guò)度使用閉包可能導(dǎo)致內(nèi)存占用過(guò)多,我們建議讀者只在絕對(duì)必要時(shí)再考慮使用閉包。雖然像V8等優(yōu)化后的JavaScript引擎會(huì)嘗試回收被閉包占用的內(nèi)存,但請(qǐng)還是要謹(jǐn)慎使用。
閉包與變量作用域鏈的這種配置機(jī)制,引出了一個(gè)值得注意的副作用,即閉包只能取得包含函數(shù)中任何變量的最后一值。別忘了閉包所保存的是整個(gè)變量對(duì)象,而不是某個(gè)特殊的變量。
function createFunctions() { var result = new Array(); for (var i=0; i < 10; i++) { // 賦值給數(shù)組元素的是匿名函數(shù)本身,不是具體的值 // 所以在 createFunctions() 執(zhí)行完畢后,調(diào)用數(shù)組內(nèi)的函數(shù),返回的是變量i的值 // 而變量i在執(zhí)行完畢后,等于 10 result[i] = function() { // 返回指向變量 i 的指針 return i; }; } return result; }
這個(gè)函數(shù)會(huì)返回一個(gè) 函數(shù)數(shù)組。表面上看result里的每一項(xiàng)函數(shù)都應(yīng)該返回自己的索引值。但實(shí)際上每一個(gè)函數(shù)返回的都是10。
因?yàn)槊總€(gè)函數(shù)的作用域中都保存著createFunctions()函數(shù)的活動(dòng)對(duì)象,所以它們引用的都是同一個(gè)變量i。當(dāng)createFunctions()函數(shù)返回后,變量i的值是10,此時(shí)每個(gè)函數(shù)都引用著保存變量i的同一個(gè)變量對(duì)象,所以在每個(gè)函數(shù)內(nèi)部i的值都是10.
可以通過(guò)創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期
function createFunctions() { var result = new Array(); for (var i=0; i < 10; i++) { // 此時(shí)返回的里層匿名函數(shù)調(diào)用了外層匿名函數(shù)的 num // 里層匿名函數(shù)創(chuàng)建并返回了一個(gè)訪問(wèn) num 的閉包 // 如此一來(lái) result 數(shù)組中的每個(gè)函數(shù)都有自己的num變量副本 result[i] = function(num) { // 返回創(chuàng)建的另一個(gè)匿名函數(shù) return function() { return num; }; }(i); } return result; }關(guān)于this對(duì)象
在閉包中使用this對(duì)象也可能會(huì)導(dǎo)致一些問(wèn)題。this對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的:
在全局函數(shù)中,this等于window
當(dāng)函數(shù)被作為某個(gè)對(duì)象的方法調(diào)用時(shí),this等于那個(gè)對(duì)象。
匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其this對(duì)象通常指向window(通過(guò)call() apply()改變函數(shù)的執(zhí)行環(huán)境的情況除外)。但有時(shí)候由于變成寫(xiě)閉包的方式不同,這一點(diǎn)可能不會(huì)那么明顯
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; // 在非嚴(yán)格模式下 object.getNameFunc()(); // "The Window"
每個(gè)函數(shù)在被調(diào)用時(shí)都會(huì)自動(dòng)取得兩個(gè)特殊變量: this和arguments。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會(huì)搜索到其活動(dòng)對(duì)象為止,因此永遠(yuǎn)不可能直接訪問(wèn)外部函數(shù)中的這兩個(gè)變量(這一點(diǎn)通過(guò)圖7-2可以看的清楚)。
不過(guò),把外部作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)該對(duì)象了。
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { var that = this; return function() { return that.name; }; } }; object.getNameFunc()(); // "My Object"
在幾種特殊情況下,this的值可能會(huì)意外的改變。
var name = "The Window"; var object = { name: "My Object", getName: function() { return this.name; } }; // this 指向 object object.getName(); // "My Object" // 加上了括號(hào),看似在引用一個(gè)函數(shù) // 但 (object.getName) 和 object.getName 的定義是相同的 // 所以這行代碼與上面的代碼無(wú)異 (object.getName)(); // "My Object" // 非嚴(yán)格模式 // 賦值語(yǔ)句會(huì)返回 object.getName 的匿名函數(shù) // 相當(dāng)于將匿名函數(shù)在全局環(huán)境下運(yùn)行 (object.getName = object.getName)(); // "The Window"
第三行代碼先執(zhí)行了一條賦值語(yǔ)句,然后再調(diào)用賦值后的結(jié)果。因?yàn)檫@個(gè)賦值表達(dá)式的值是函數(shù)本身,所以this的值不能得到維持,結(jié)果就返回了 "The Window" 。
內(nèi)存泄漏由于IE9之前的版本對(duì)JScript對(duì)象和COM對(duì)象使用不同的垃圾收集例程(第4章介紹過(guò)),因此閉包在IE的這些版本中會(huì)導(dǎo)致一些特殊的問(wèn)題。具體來(lái)說(shuō),如果閉包的作用域鏈中保存著一個(gè)HTML元素,那么就意味著該元素將無(wú)法被銷(xiāo)毀
function assignHandler() { var element = document.getElementById("someElement"); element.onclick = function() { console.log(element.id); }; }
以上代碼創(chuàng)建了一個(gè)作為element元素處理程序的閉包,而這個(gè)閉包則又創(chuàng)建了一個(gè)循環(huán)引用(事件將在第13章討論)。由于匿名函數(shù)保存了一個(gè)對(duì)assignHandler()的活動(dòng)對(duì)象的引用,因此就會(huì)導(dǎo)致無(wú)法減少element的引用數(shù)。只要匿名函數(shù)存在,element的引用數(shù)至少是1。
// 以下修改可以避免這個(gè)問(wèn)題 function assignHandler() { var element = document.getElementById("someElement"); var id = element.id; element.onclick = function() { console.log(id); }; element = null; }
閉包中引用包含函數(shù)的整個(gè)活動(dòng)對(duì)象,而其中包含著element。即使閉包不直接引用element,包含函數(shù)的活動(dòng)對(duì)象也仍然會(huì)保存一個(gè)引用。因此有必要把element變量設(shè)置為null
模仿塊級(jí)作用域JavaScript沒(méi)有塊級(jí)作用域。這意味著在塊語(yǔ)句中定義的變量,實(shí)際上是在包含函數(shù)中而非語(yǔ)句中創(chuàng)建的。
function outputNumerbs(cout) { for (var i=0; i < cout; i++) { console.log(i); } console.log(i); // 計(jì)數(shù) }
在Java, C++等語(yǔ)言中,變量i只會(huì)在for循環(huán)的語(yǔ)句塊中有定義,循環(huán)一旦結(jié)束,變量i就會(huì)被銷(xiāo)毀??墒窃贘avaScript中,變量i是定義在outputNumbers()的活動(dòng)對(duì)象中的,因此從它有定義開(kāi)始,就可以在函數(shù)內(nèi)部隨處訪問(wèn)它。即使像下面這樣錯(cuò)誤的重新聲明變量也不會(huì)改變值。
function outputNumerbs(cout) { for (var i=0; i < cout; i++) { console.log(i); } var i; // 重新聲明變量 console.log(i); // 計(jì)數(shù) }
JavaScript從來(lái)不會(huì)告訴你是否多次聲明了同一個(gè)變量,遇到這種情況,它只會(huì)對(duì)后續(xù)的聲明視而不見(jiàn)(不過(guò),它會(huì)執(zhí)行后續(xù)聲明中的變量初始化)。
匿名函數(shù)可以用來(lái)模仿塊級(jí)作用域并避免這個(gè)問(wèn)題。用作塊級(jí)作用域(通常稱為 私有作用域 )的匿名函數(shù)的語(yǔ)法如下:
(function() { // 這里是塊級(jí)作用域 ... })()
// 常見(jiàn)的代碼片段 // 定義了一個(gè)函數(shù),然后立即調(diào)用它 var someFunction = function() { // 這里是塊級(jí)作用域 ... }; someFunction();
那這里如果將函數(shù)名也去掉呢?答案是不行,會(huì)導(dǎo)致出錯(cuò)。因?yàn)?b>JavaScript將function關(guān)鍵字當(dāng)做一個(gè)函數(shù)聲明的開(kāi)始,而函數(shù)聲明后面不能跟圓括號(hào)。(函數(shù)表達(dá)式可以)
function() { // 這里是塊級(jí)作用域 ... }() // 出錯(cuò)!
要將函數(shù)聲明轉(zhuǎn)換成函數(shù)表達(dá)式,只要外面包裹圓括號(hào)即可
(function() { // 這里是塊級(jí)作用域 ... }())
無(wú)論在什么地方,只要臨時(shí)需要一些變量,就可以使用私有作用域
function outputNumbers(cout) { // 這里是一個(gè)閉包,匿名函數(shù)可以訪問(wèn) cout (function () { for (var i=0; i < cout; i++) { console.log(i); } })(); // 在這里調(diào)用變量 i 會(huì)報(bào)錯(cuò) console.log(i); // throw error }
這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過(guò)多的變量和函數(shù)。
(function() { var now = new Date(); if (now.getMonth() == 0 && now.gettDate() == 1) { console.log("Haapy new year!"); } })();
這種做法可以減少閉包占用的內(nèi)存,因?yàn)闆](méi)有指向匿名函數(shù)的引用。只要函數(shù)執(zhí)行完畢,就可以立即銷(xiāo)毀其作用域鏈了。
私有變量嚴(yán)格來(lái)講,JavaScript中沒(méi)有私有成員的概念;所有對(duì)象屬性都是共有的。不過(guò)倒是有一個(gè)私有變量的概念。
任何在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因?yàn)椴荒茉诤瘮?shù)的外部訪問(wèn)這些變量。
如果在函數(shù)內(nèi)部創(chuàng)建一個(gè)閉包,那么閉包通過(guò)自己的作用域鏈可以訪問(wèn)這些私有變量。利用這一點(diǎn),我們就可以創(chuàng)建用于訪問(wèn)私有變量的公有方法。
有權(quán)訪問(wèn)私有變量和私有函數(shù)的公有方法稱為 特權(quán)方法(privileged method) 。有兩種創(chuàng)建特權(quán)方法的方式:
在構(gòu)造函數(shù)中定義特權(quán)方法(靜態(tài)私有變量)
模塊模式
構(gòu)造函數(shù)中定義,基本的模式如下
// 構(gòu)造函數(shù)Person // 入?yún)?name 是它的私有變量 function Person(name) { this.getName = function() { return name; }; this.setName = function(value) { name = value; }; } var person = new Person("Nicholas"); console.log(person.getName()); // "Nicholas" person.setName("Greg"); console.log(person.getName()); // "Greg"
這種模式有一個(gè)缺點(diǎn),那就是必須使用構(gòu)造函數(shù)來(lái)達(dá)到目的,而第6章討論過(guò),構(gòu)造函數(shù)模式的確定是針對(duì)每一個(gè)實(shí)例都會(huì)創(chuàng)建出同樣的一組新方法
而使用靜態(tài)私有變量來(lái)實(shí)現(xiàn)特權(quán)方法就可以避免這個(gè)問(wèn)題。
靜態(tài)私有變量(function() { // 私有變量 var privateVariable = 10; // 私有函數(shù) function privateFunction() { return false; } // 構(gòu)造函數(shù) // 這里沒(méi)有使用var操作符,自動(dòng)創(chuàng)建全局變量 // 嚴(yán)格模式下不能使用 MyObject = function() {}; // 公有/特權(quán)方法 MyObject.prototype.publicMethod = function() { privateVariable++; return privateFunction(); }; })();
公有方法是在原型上定義的,避免了重復(fù)創(chuàng)建方法的情況。
需要注意的是,這個(gè)模式在定義構(gòu)造函數(shù)時(shí)沒(méi)有使用函數(shù)聲明,而是使用了函數(shù)表達(dá)式。函數(shù)聲明只能創(chuàng)建局部函數(shù),但那并不是我們想要的。
這個(gè)特權(quán)方法,作為一個(gè)閉包,總是保存著對(duì)包含作用域的引用。
(function() { var name = ""; Person = function(value) { name = value; }; Person.prototype.getName = function() { return name; }; Person.prototype.setName = function (value) { name = value; }; })(); var person1 = new Person("Nicholas"); console.log(person1.getName()); // "Nicholas" person1.setName("Greg"); console.log(person1.getName()); // "Greg" var person2 = new Person("Michael"); console.log(person1.getName()); // "Michael" console.log(person2.getName()); // "Michael"
這個(gè)例子中的Person構(gòu)造函數(shù)與getName() setName() 方法一樣,都有權(quán)訪問(wèn)私有變量name。
name變成了一個(gè)靜態(tài)的、由所有實(shí)例共享的屬性。
以這種方式創(chuàng)建靜態(tài)私有變量會(huì)因?yàn)槭褂迷投鲞M(jìn)代碼復(fù)用,但每個(gè)實(shí)例都沒(méi)有自己的私有變量。
多查找作用域鏈中的一個(gè)層次,就會(huì)在一定程度上影響查找速度。而這正是使用閉包和私有變量的一個(gè)明顯的不足之處。
模塊模式模塊模式通過(guò)為單例添加私有變量和特權(quán)方法使其得到增強(qiáng)
這種模式在需要對(duì)單例進(jìn)行某些初始化,同時(shí)又需要維護(hù)其私有變量時(shí)非常有用
var application = function() { // 私有變量和函數(shù) var components = new Array(); // 初始化 components.push(new BaseComponent()); // 公共 return { getComponentCuont: function() { return components.length; }, registerComponent: function(component) { if (typeof component == "object") { components.push(component) } } }; }();
在WEB應(yīng)用程序中,經(jīng)常需要使用一個(gè)單例來(lái)管理應(yīng)用程序級(jí)的信息。這個(gè)簡(jiǎn)單的例子創(chuàng)建了一個(gè)用于管理組件的application對(duì)象。
簡(jiǎn)而言之,如果必須創(chuàng)建一個(gè)對(duì)象并以某些數(shù)據(jù)對(duì)其進(jìn)行初始化,同時(shí)還要公開(kāi)一些能夠訪問(wèn)這些私有數(shù)據(jù)的方法,那么就可以使用模塊模式。
以模塊模式創(chuàng)建的每個(gè)單例都是Object的實(shí)例,因?yàn)樽罱K要通過(guò)一個(gè)對(duì)象字面量來(lái)表示他。
增強(qiáng)的模塊模式增強(qiáng)的模塊模式適合那些單例必須是某種類(lèi)型的實(shí)例,同時(shí)還必須添加某些屬性和(或)方法對(duì)其加以增強(qiáng)的情況。
如果前述例子中的application對(duì)象必須是BaseComponent的實(shí)例,可以如下代碼
var application = function() { // 私有變量和函數(shù) var components = new Array(); // 初始化 components.push(new BaseComponent()); // 創(chuàng)建 application 的一個(gè)局部副本 var app = new BaseComponent(); // 公共接口 app.getComponentCuont = function() { return components.length; }; app.registerComponent = function(component) { if (typeof component == "object") { components.push(component); } }; // 返回這個(gè)副本 return app; }();
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109501.html
摘要:具體說(shuō)就是執(zhí)行流進(jìn)入下列任何一個(gè)語(yǔ)句時(shí),作用域鏈就會(huì)得到加長(zhǎng)語(yǔ)句的塊。如果局部環(huán)境中存在著同名的標(biāo)識(shí)符,就不會(huì)使用位于父環(huán)境中的標(biāo)識(shí)符訪問(wèn)局部變量要比訪問(wèn)全局變量更快,因?yàn)椴挥孟蛏纤阉髯饔糜蜴湣? 基本類(lèi)型和引用類(lèi)型的值 ECMAscript變量包含 基本類(lèi)型值和引用類(lèi)型值 基本類(lèi)型值值的是基本數(shù)據(jù)類(lèi)型:Undefined, Null, Boolean, Number, String ...
摘要:表示應(yīng)該立即下載腳本,但不應(yīng)妨礙頁(yè)面中的其他操作可選。表示通過(guò)屬性指定的代碼的字符集。表示腳本可以延遲到文檔完全被解析和顯示之后再執(zhí)行。實(shí)際上,服務(wù)器在傳送文件時(shí)使用的類(lèi)型通常是,但在中設(shè)置這個(gè)值卻可能導(dǎo)致腳本被忽略。 第1章 JavaScript 簡(jiǎn)介 雖然JavaScript和ECMAScript通常被人們用來(lái)表達(dá)相同的含義,但JavaScript的含義比ECMA-262要多得多...
摘要:用戶代理檢測(cè)用戶代理檢測(cè)是爭(zhēng)議最大的客戶端檢測(cè)技術(shù)。第二個(gè)要檢測(cè)是。由于實(shí)際的版本號(hào)可能會(huì)包含數(shù)字小數(shù)點(diǎn)和字母,所以捕獲組中使用了表示非空格的特殊字符。版本號(hào)不在后面,而是在后面。除了知道設(shè)備,最好還能知道的版本號(hào)。 檢測(cè)Web客戶端的手段很多,各有利弊,但不到萬(wàn)不得已就不要使用客戶端檢測(cè)。只要能找到更通用的方法,就應(yīng)該優(yōu)先采用更通用的方法。一言蔽之,先設(shè)計(jì)最通用的方案,然后再使用特定...
摘要:本質(zhì)上是由一組無(wú)序名值對(duì)組成的。浮點(diǎn)數(shù)值的最高精度是位小數(shù),但在進(jìn)行計(jì)算時(shí)其精度遠(yuǎn)遠(yuǎn)不如證書(shū)。例如這是使用基于數(shù)值的浮點(diǎn)計(jì)算的通病,并非獨(dú)此一家數(shù)值范圍。 函數(shù)名不能使用關(guān)鍵字(typeof不行但typeOf可以,區(qū)分大小寫(xiě)) 標(biāo)識(shí)符就是指變量、函數(shù)、屬性的名字,或者函數(shù)的參數(shù)。 第一個(gè)字符必須是一個(gè)字母、下劃線(_)或者一個(gè)美元符號(hào)($) 其他字符可以是字母、下劃線、美元符號(hào)或...
摘要:引用類(lèi)型的值對(duì)象是引用類(lèi)型的一個(gè)實(shí)例。引用類(lèi)型是一種數(shù)據(jù)結(jié)構(gòu),用于將數(shù)據(jù)和功能組織在一起。對(duì)數(shù)組中的每一項(xiàng)運(yùn)行給定函數(shù),如果該函數(shù)對(duì)任一項(xiàng)返回,則返回。組零始終代表整個(gè)表達(dá)式。所以,使用非捕獲組較使用捕獲組更節(jié)省內(nèi)存。 引用類(lèi)型的值(對(duì)象)是引用類(lèi)型的一個(gè)實(shí)例。 引用類(lèi)型是一種數(shù)據(jù)結(jié)構(gòu),用于將數(shù)據(jù)和功能組織在一起。它同行被稱為類(lèi),但這種稱呼并不妥當(dāng),盡管ECMAScript從技術(shù)上講...
閱讀 3217·2021-11-08 13:18
閱讀 1368·2021-10-09 09:57
閱讀 1198·2021-09-22 15:33
閱讀 4007·2021-08-17 10:12
閱讀 5086·2021-08-16 11:02
閱讀 2697·2019-08-30 10:56
閱讀 981·2019-08-29 18:31
閱讀 3267·2019-08-29 16:30