最近學(xué)這塊知識(shí)學(xué)得有些吃力。還有很多遺漏的地方,只能以后多看些書(shū)來(lái)彌補(bǔ)了。
<h1 id="7">第7章 函數(shù)表達(dá)式</h1>
函數(shù)定義的兩種方式:函數(shù)聲明,函數(shù)表達(dá)式。這個(gè)在第5章函數(shù)聲明與函數(shù)表達(dá)式也有提到的
函數(shù)聲明:
- 語(yǔ)法:
function functionName(){}
- 一些瀏覽器給函數(shù)定義了一個(gè)非標(biāo)準(zhǔn)的name屬性,這個(gè)屬性值=functionName
- 函數(shù)聲明提升:執(zhí)行代碼之前會(huì)先讀取函數(shù)函數(shù)聲明。就是說(shuō)可以把函數(shù)聲明放在調(diào)用它的語(yǔ)句之后
函數(shù)表達(dá)式:
- 函數(shù)表達(dá)式有幾種不同的語(yǔ)法形式
- 這個(gè)比較常見(jiàn):
var functionName = function(){};
- 用以上創(chuàng)建的函數(shù)叫匿名函數(shù)(name屬性是空字符串)。
- 既然是表達(dá)式,在使用前就要先賦值
<h2 id="7.1">7.1 遞歸</h2>
arguments.callee這是個(gè)很有用的屬性,指向正在執(zhí)行的函數(shù)的指針??梢杂盟鼘?shí)現(xiàn)對(duì)函數(shù)的遞歸調(diào)用,第5章函數(shù)內(nèi)部屬性也講到過(guò)。貼一段經(jīng)典代碼!
function factorial(num){
if (num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
用arguments.callee
比用函數(shù)名更保險(xiǎn)。不過(guò)嚴(yán)格模式下會(huì)有錯(cuò)誤。
var factorial = (function f(num){
if (num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
}
});
以上代碼就是把函數(shù)f賦值給factorial了
<h2 id="7.2">7.2 閉包</h2>
這里剛開(kāi)始看的時(shí)候有許多概念不懂,導(dǎo)致前后不能連貫,對(duì)作用域鏈一直存在疑問(wèn)。所以我首先列舉一下一些重要的概念。之前在第4章提到過(guò)的執(zhí)行環(huán)境。我看了當(dāng)時(shí)做的筆記,記的不是很完整。 所以在這里也補(bǔ)充一下
執(zhí)行環(huán)境:
定義了變量或函數(shù)有權(quán)訪(fǎng)問(wèn)的其他數(shù)據(jù)
每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象。這個(gè)變量對(duì)象是用來(lái)保存環(huán)境中定義的所有變量和函數(shù)的
-
執(zhí)行環(huán)境可以分為全局執(zhí)行環(huán)境和函數(shù)執(zhí)行環(huán)境
- 全局執(zhí)行環(huán)境直到應(yīng)用程序退出時(shí)才會(huì)銷(xiāo)毀
- 每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中。執(zhí)行完畢后棧將其環(huán)境彈出
當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈
作用域鏈:
- 作用域鏈的前端始終都是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對(duì)象
- 作用域鏈的下一個(gè)變量對(duì)象來(lái)租包含(外部)環(huán)境
- 如果當(dāng)前執(zhí)行代碼所在環(huán)境是函數(shù),則將其活動(dòng)對(duì)象作為變量對(duì)象
- 活動(dòng)對(duì)象最開(kāi)始只包含一個(gè)變量,即arguments對(duì)象。
變量對(duì)象:
- 保存了環(huán)境中定義的所有變量和函數(shù)
- 每個(gè)執(zhí)行環(huán)境都有一個(gè)變量對(duì)象
- 局部環(huán)境的變量對(duì)象只在函數(shù)執(zhí)行的過(guò)程中存在
- 全局環(huán)境變量對(duì)象始終存在(這兩點(diǎn)和執(zhí)行環(huán)境相通)
- 變量對(duì)象存儲(chǔ)著環(huán)境中的以下內(nèi)容
- 函數(shù)的形參
- var聲明的變量
- 函數(shù)聲明(但不包含函數(shù)表達(dá)式)
活動(dòng)對(duì)象:
- 活動(dòng)對(duì)象就是作用域鏈上正在被執(zhí)行和引用的變量對(duì)象
當(dāng)創(chuàng)建一個(gè)函數(shù)時(shí):
- 創(chuàng)建預(yù)先包含全局變量對(duì)象的作用域鏈
- 這個(gè)作用域鏈保存在函數(shù)內(nèi)部的[[Scope]]屬性中
當(dāng)?shù)谝淮握{(diào)用函數(shù)時(shí):
- 創(chuàng)建一個(gè)執(zhí)行環(huán)境(注意要調(diào)用的時(shí)候才會(huì)有執(zhí)行環(huán)境)
- 復(fù)制函數(shù)的[[Scope]]屬性中的對(duì)象,構(gòu)建這個(gè)執(zhí)行環(huán)境的作用域鏈
- 創(chuàng)建一個(gè)活動(dòng)對(duì)象,使用this、arguments和其他命名參數(shù)的值來(lái)初始化函數(shù)的活動(dòng)對(duì)象,把這個(gè)活動(dòng)對(duì)象推入執(zhí)行環(huán)境作用域鏈的前端
- 外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象在第三位,以此類(lèi)推,直到作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境
下面結(jié)合書(shū)上的例子來(lái)看一下上述的兩個(gè)過(guò)程
function compare(value1, value2){
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
}
var result = compare(5, 10);
-
創(chuàng)建這個(gè)函數(shù)的時(shí)候:
創(chuàng)建.png - 第一次調(diào)用
-
復(fù)制作用域鏈到執(zhí)行環(huán)境中
這個(gè)時(shí)候還只有全局變量對(duì)象的作用域鏈.png -
把活動(dòng)對(duì)象推入執(zhí)行環(huán)境作用域鏈的前端
加入了活動(dòng)對(duì)象的作用域鏈.png
一般來(lái)說(shuō),當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷(xiāo)毀,內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對(duì)象)。但是閉包的情況要特殊一點(diǎn)。
在另一個(gè)函數(shù)內(nèi)部定義的函數(shù)會(huì)將包含函數(shù)(外部函數(shù))的活動(dòng)對(duì)象添加到它的作用域鏈中。下面看另一個(gè)例子
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;
}
}
}
//創(chuàng)建函數(shù)
var compareNames = createComparisonFunction("name");
//調(diào)用函數(shù)
var result = compareNames({name:"xjh"},{name:"xkld"});
//解除對(duì)匿名函數(shù)的引用(以便釋放內(nèi)存)
compareNames = null;
也就是說(shuō)這個(gè)時(shí)候里面的匿名函數(shù)是有權(quán)訪(fǎng)問(wèn)propertyName的
<h3 id="7.2.1">7.2.1 閉包與變量</h3>
閉包保存的是整個(gè)變量對(duì)象,不是某個(gè)特殊的值
<h3 id="7.2.2">7.2.2 關(guān)于this對(duì)象</h3>
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。
<h3 id="7.2.3">7.2.3 內(nèi)存泄漏</h3>
如果閉包的作用域鏈中保存著一個(gè)HTML元素,那么就意味著該元素?zé)o法被銷(xiāo)毀
<h2 id="7.3">7.3 模仿塊級(jí)作用域</h2>
JS中沒(méi)有塊級(jí)作用域的概念,任何變量都是在函數(shù)中創(chuàng)建的
當(dāng)重復(fù)聲明一個(gè)變量時(shí),只會(huì)對(duì)后續(xù)的聲明視為不見(jiàn)(不過(guò)可以執(zhí)行后續(xù)聲明的變量初始化)
模擬塊級(jí)作用域(私有作用域):
(function(){ //在外面加()的目的是把函數(shù)聲明轉(zhuǎn)換成函數(shù)表達(dá)式。JS中函數(shù)聲明后面不能加圓括號(hào)
//塊級(jí)作用域
})();
這段代碼定義并調(diào)用了一個(gè)匿名函數(shù),將函數(shù)聲明包含在()中,最后的()會(huì)立即調(diào)用這個(gè)函數(shù)
定義匿名函數(shù)可以減少閉包占用的內(nèi)存問(wèn)題,因?yàn)闆](méi)有指向匿名函數(shù)的引用,只要函數(shù)執(zhí)行完畢,就可以立即銷(xiāo)毀其作用域鏈
<h2 id="7.4">7.4 私有變量</h2>
JS沒(méi)有私有成員的概念:所有對(duì)象屬性都是共有的,不過(guò)有一個(gè)私有變量的概念:不能在函數(shù)的外部訪(fǎng)問(wèn)這些變量
私有變量包括函數(shù)的參數(shù)、局部變量、在函數(shù)內(nèi)部定義的其他函數(shù)
可以利用閉包創(chuàng)建用于訪(fǎng)問(wèn)私有變量的公有方法
特權(quán)方法:有權(quán)訪(fǎng)問(wèn)私有變量和私有函數(shù)的公有方法
創(chuàng)建特權(quán)方法的兩種方式:
- 在構(gòu)造函數(shù)中定義特權(quán)方法
function MyObject(){
var privateVar = 10;
function privateFunction(){
return false;
}
this.publicMethod = function(){
privateVar++;
return privateFunction();
};
}
這個(gè)privateVar和privateFunction可以看成是私有的,因?yàn)樵趧e的地方無(wú)法訪(fǎng)問(wèn)到這些變量(可以參考私有作用域for循環(huán)里面的i)
publicMethod因?yàn)槭菍?duì)象的屬性(前面有this的),這樣的話(huà)用構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例的時(shí)候就有了這個(gè)公有的方法。而這個(gè)publicMethod的作用域鏈包含著MyObject的作用域鏈,就可以訪(fǎng)問(wèn)到對(duì)應(yīng)的私有變量了
看如下代碼:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function(value){
name = value
};
}
getName()和setName()作為閉包能通過(guò)作用域鏈訪(fǎng)問(wèn)name。私有變量name在Person的每一個(gè)實(shí)例中都不同,每次調(diào)用構(gòu)造函數(shù)都會(huì)重新創(chuàng)建這兩個(gè)方法。這個(gè)方法和通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象一樣有個(gè)缺點(diǎn),就是每次都要重新創(chuàng)建get和set這兩個(gè)方法
- 使用靜態(tài)私有變量
<h3 id="7.4.1">7.4.1 靜態(tài)私有變量</h3>
(function(){
var privateVar = 10;
function privateFunction(){
return false;
}
MyObject = function(){//定義了一個(gè)全局變量MyObject指向這個(gè)匿名構(gòu)造函數(shù)
};
MyObject.prototype.publicMethod = function(){
...
};
})();
以上代碼主要是在私有作用域里面定義了一個(gè)構(gòu)造函數(shù)。利用這個(gè)構(gòu)造函數(shù)的原型訪(fǎng)問(wèn)私有變量
4.2中提到過(guò):使用var聲明的變量會(huì)自動(dòng)被添加到最接近的環(huán)境中。在函數(shù)內(nèi)部就是局部環(huán)境,with語(yǔ)句中就是函數(shù)環(huán)境。不使用var聲明,變量自動(dòng)添加到全局環(huán)境。這里定義構(gòu)造函數(shù)時(shí)不用函數(shù)聲明(function MyObject()
)的主要原因是函數(shù)聲明只能創(chuàng)建局部函數(shù)。而用函數(shù)表達(dá)式,變量前不加var就可以定義一個(gè)全局變量,能夠在私有作用域外被訪(fǎng)問(wèn)到(嚴(yán)格模式下會(huì)報(bào)錯(cuò))
但是這個(gè)方法每個(gè)實(shí)例都可以對(duì)name進(jìn)行修改,name就成為一個(gè)靜態(tài)私有變量
<h3 id="7.4.2">7.4.2 模塊模式</h3>
前面兩種模式主要用于為自定義類(lèi)型創(chuàng)建私有變量和特權(quán)方法。模塊模式是為單例創(chuàng)建私有變量和方法
JS中以對(duì)象字面量方式創(chuàng)建單例對(duì)象
var singleton = {
name:value;
method:function(){
}
};
模塊模式通過(guò)為單例添加私有變量和特權(quán)方法能夠使其得到增強(qiáng)
var singleton = function(){
var privateVar = 10;
function privateFunction(){
return false;
}
return {
publicProperty:true,
publicMethod:function(){
...
}
}
};
以上代碼返回一個(gè)對(duì)象字面量,對(duì)象字面量里的函數(shù)有權(quán)訪(fǎng)問(wèn)私有變量和函數(shù),在外部可以通過(guò)singleton.publicMethod()這種形式訪(fǎng)問(wèn)
從本質(zhì)上講,這個(gè)對(duì)象字面量定義的是單例的公共接口。這種模式在需要對(duì)單例進(jìn)行某些初始化,同時(shí)又需要維護(hù)其私有變量時(shí)是非常有用的(類(lèi)比java中的單例模式)
var application = function(){
var components = new Array();
//初始化
components.push(new BaseComponent());//這兩個(gè)語(yǔ)句在第一次執(zhí)行后就不再執(zhí)行,因?yàn)橥饷嬷粫?huì)使用return的兩個(gè)方法訪(fǎng)問(wèn)該私有變量
return {
getComponentCount:function(){
return components.length;
},
registerComponent:function(component){
if(typeof component == "Object"){
components.push(component);
}
}
};
}();
<h3 id="7.4.3">7.4.3 增強(qiáng)的模塊模式</h3>
適合一些單例必須是某種類(lèi)型的實(shí)例,同時(shí)必須添加某些屬性或方法對(duì)其加以增強(qiáng)的情況
var singleton = function(){
var privateVar = 10;
function privateFunction(){
return false;
}
var object = new CustomType();
object.publicProperty = true;
object.publicMethod = function(){
...
}
return object;
}();
以上代碼要求singleton對(duì)象必須是CustomType的實(shí)例(區(qū)別就是把公共屬性和方法定義到CustomType實(shí)例中了)
<h2 id="7.5">7.5 小結(jié)</h2>
-
函數(shù)表達(dá)式的特點(diǎn):
- 函數(shù)聲明要有名字,但函數(shù)表達(dá)式不需要。即函數(shù)表達(dá)式可以是匿名函數(shù)
- 在無(wú)法確定如何引用函數(shù)的情況下,遞歸函數(shù)會(huì)變得比較復(fù)雜
- 遞歸函數(shù)應(yīng)該始終使用arguments.callee來(lái)遞歸地調(diào)用自身,不要使用函數(shù)名——函數(shù)名可能會(huì)發(fā)生變化
-
閉包:在函數(shù)內(nèi)部定義其他函數(shù)時(shí),就創(chuàng)建了閉包,閉包有權(quán)訪(fǎng)問(wèn)包含函數(shù)內(nèi)部的所有變量:
- 閉包的作用域鏈包含著自己的作用域、包含函數(shù)的作用域和全局函數(shù)的作用域
- 通常函數(shù)的作用域及其所有變量都會(huì)在函數(shù)執(zhí)行結(jié)束后被銷(xiāo)毀
- 但是當(dāng)函數(shù)返回了一個(gè)閉包時(shí),這個(gè)函數(shù)的作用域會(huì)一直在內(nèi)存中保存到閉包不存在為止
-
使用閉包模仿塊級(jí)作用域
- 思路就是創(chuàng)建并立即調(diào)用一個(gè)函數(shù),這樣會(huì)立即執(zhí)行里面的代碼,又不會(huì)在內(nèi)存中留下對(duì)該函數(shù)的引用
- 結(jié)果就是函數(shù)內(nèi)部的所有變量都會(huì)被立即銷(xiāo)毀——除非將某些變量賦值給了包含作用域中的變量
-
使用閉包創(chuàng)建私有變量:
- JS中沒(méi)有正式的私有對(duì)象的概念。這里的私有對(duì)象是指在外部訪(fǎng)問(wèn)不到的變量。可以用閉包來(lái)實(shí)現(xiàn)公有方法,從而訪(fǎng)問(wèn)到在包含作用域中定義的變量
- 有權(quán)訪(fǎng)問(wèn)私有變量的公有方法叫特權(quán)方法
- 自定義類(lèi)型的特權(quán)方法有:構(gòu)造函數(shù)模式、原型模式。單例的特權(quán)方法有:模塊模式、增強(qiáng)的模塊模式