定義函數(shù)有兩種方式:函數(shù)聲明和函數(shù)表達(dá)式咒吐。
Firefox但荤、Chrome早芭、Safari彼城、Opera都給函數(shù)定義了一個(gè)非標(biāo)準(zhǔn)的name
屬性,通過這個(gè)屬性可以訪問到函數(shù)名退个。
函數(shù)聲明最重要的特征就是函數(shù)聲明提前(function declaration hoisting)募壕。在執(zhí)行代碼之前會(huì)先讀取函數(shù)聲明,意味著可以把函數(shù)聲明放在調(diào)用語句的后面语盈。
var functionName = function (arg0, arg1, arg2){
//函數(shù)體
};
創(chuàng)建一個(gè)函數(shù)并將它賦值給變量functionName
司抱。這種情況下創(chuàng)建的函數(shù)叫匿名函數(shù)(anonymous function),有時(shí)候也叫Lambda函數(shù)。匿名函數(shù)的name
屬性是空字符串黎烈。
函數(shù)表達(dá)式與其他表達(dá)式一樣习柠,必須先賦值后使用。
遞歸
function factorial(num) {
if(num<=){
return 1;
} else {
return num*factorial(num-1);
}
}
下面的代碼可能出錯(cuò):
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出錯(cuò)
可以使用arguments.callee
來解決這個(gè)問題照棋。arguments.callee
是一個(gè)指向正在執(zhí)行的函數(shù)的指針资溃。
function factorial(num) {
if(num<=){
return 1;
} else {
return num*arguments.callee(num-1);
}
}
但在嚴(yán)格模式下,不能通過腳本訪問arguments.callee
烈炭,訪問這個(gè)屬性會(huì)導(dǎo)致錯(cuò)誤溶锭。不過可以使用命名函數(shù)表達(dá)式來達(dá)成相同的結(jié)果。
var factorial = (function f (num) {
if (num <= 1) {
return 1;
} else {
return num * f(num-1);
}
});
閉包
匿名函數(shù)和閉包經(jīng)撤叮混淆趴捅。閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。
創(chuàng)建閉包的常見方式霹疫,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(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;
}
};
}
閉包與變量
作用域鏈的這種配置機(jī)制引出了一個(gè)值得注意的副作用,即閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值丽蝎。別忘了閉包所保存的是整個(gè)變量對(duì)象猎拨,而不是某個(gè)特殊的變量。
function createFunction () {
var result = new Array();
for (var i= 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
事實(shí)上result
中的每個(gè)函數(shù)都返回10。但是红省,我們可以通過創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期额各。
function createFunction () {
var result = new Array();
for (var i= 0; i < 10; i++) {
result[i] = function (num) {
return function() {
return num;
};
}(i);
}
return result;
}
關(guān)于this對(duì)象
在閉包中使用this對(duì)象也可能會(huì)導(dǎo)致一些問題。我們知道吧恃,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。但有時(shí)候由于編寫閉包的方式不同丁眼,這一點(diǎn)可能不會(huì)那么明顯筷凤。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window" (在非嚴(yán)格模式下)
每個(gè)函數(shù)在被調(diào)用時(shí),其活動(dòng)對(duì)象都會(huì)自動(dòng)取得兩個(gè)特殊變量:this和arguments苞七。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí)藐守,只會(huì)搜索到其活動(dòng)對(duì)象為止,因此永遠(yuǎn)不可能直接訪問外部函數(shù)中的這兩個(gè)變量蹂风。不過卢厂,把外部作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問到的變量里,就可以讓閉包訪問對(duì)象了惠啄。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function () {
var that = this;
return function () {
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
this和arguments也存在同樣的問題慎恒。如果想訪問作用域中的arguments對(duì)象,必須將對(duì)象的引用保存到另一個(gè)閉包能夠訪問的變量中撵渡。
在幾種特殊情況下融柬,this的值可能會(huì)意外地改變。
var name = "The Window";
var object = {
name : "My Object",
getName : function () {
return this.name;
}
};
object.getName(); //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window",在非嚴(yán)格模式下
內(nèi)存泄露
由于IE9之前的版本對(duì)JScript對(duì)象和COM對(duì)象使用不同的垃圾收集例程趋距,因此閉包在IE的這些版本中會(huì)導(dǎo)致一些特殊的問題:如果閉包的作用域鏈中保存著一個(gè)HTML元素粒氧,那么就意味著該元素將無法被銷毀。
function assignHandler () {
var element = document.getElementById("someElement");
element.onclick = function () {
alert(element.id);
};
}
這個(gè)問題可以通過修改下代碼來解決节腐。
function assignHandler () {
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function () {
alert(id);
};
element = null;
}
模仿塊級(jí)作用域
私有變量
嚴(yán)格來講外盯,JavaScript中沒有私有成員的概念;所有對(duì)象屬性都是公有的翼雀。不過饱苟,到是有一個(gè)私有變量的概念。任何函數(shù)中定義的變量狼渊,都可以認(rèn)為是私有變量掷空。私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)部定義的其他函數(shù)。
閉包可以通過自己的作用域鏈也可以訪問局部變量坦弟,利用這一點(diǎn)护锤,就可以創(chuàng)建用于訪問私有變量的公有方法。
我們把有權(quán)訪問私有變量和私有函數(shù)的公有方法稱為特權(quán)方法(privileged method)酿傍。有兩種在對(duì)象上創(chuàng)建特權(quán)方法的方式:
第一種是在構(gòu)造函數(shù)中定義特權(quán)方式烙懦。
function MyObject () {
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction () {
return false;
}
//特權(quán)方法
this.publicMethod = function () {
privateVariable++;
return privateFunction();
};
}
不過,在構(gòu)造函數(shù)中定義特權(quán)方法也有一個(gè)缺點(diǎn)赤炒,那就是你必須使用構(gòu)造函數(shù)模式來達(dá)到這個(gè)目的氯析。構(gòu)造函數(shù)模式的缺點(diǎn)是針對(duì)每個(gè)實(shí)例都會(huì)創(chuàng)建同樣一組新方法,而使用靜態(tài)私有變量來實(shí)現(xiàn)特權(quán)方法就可以避免這個(gè)問題莺褒。
靜態(tài)私有變量
通過在私有作用域中定義私有變量和函數(shù)掩缓,同樣也可以創(chuàng)建特權(quán)方法。
(function () {
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction () {
return false;
}
//構(gòu)造函數(shù)
MyObject = function () {};
//公有/特權(quán)方法
MyObject.prototype.publicMethod = function () {
privateVariable++;
return privateFunction();
};
})();
模塊模式
前面的模式是用于自定義類型創(chuàng)建私有變量和特權(quán)方法的遵岩。而道格拉斯所說的模塊模式(module patten)則是為單例創(chuàng)建私有變量和方法你辣。所謂單例(singleton),指的就是一個(gè)實(shí)例的對(duì)象尘执。
var signleton = function () {
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction () {
return false;
}
//公有/特權(quán)方法
return {
publicProperty : true;
publicMethod : function() {
privateVariable++;
return privateFunction();
}
};
}();
增強(qiáng)的模塊模式
這種增強(qiáng)的模塊模式適合那些單例必須是某種類型的實(shí)例舍哄,同時(shí)還必須添加某些屬性和(或)方法對(duì)其加以增強(qiáng)的情況。