JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記7 js函數(shù)(上)
變量類型
在說(shuō)函數(shù)之前,先來(lái)說(shuō)說(shuō)變量類型。
1、變量:變量在本質(zhì)上就是命名的內(nèi)存空間。
2、變量的數(shù)據(jù)類型:就是指變量可以存儲(chǔ)的值的數(shù)據(jù)類型,比如Number類型、Boolean類型、Object類型等,在ECMAScript中,變量的數(shù)據(jù)類型是動(dòng)態(tài)的,可以在運(yùn)行時(shí)改變變量的數(shù)據(jù)類型。
3、變量類型:是指變量本身的類型,在ECMAScript中,變量類型就只有兩種:值類型和引用類型。當(dāng)變量的數(shù)據(jù)類型是簡(jiǎn)單數(shù)據(jù)類型時(shí),變量類型就是值類型,當(dāng)變量的數(shù)據(jù)類型是對(duì)象類型時(shí),變量類型就是引用類型。在不引起歧義的情況下,也可以稱變量的數(shù)據(jù)類型為變量類型。
那么,值類型和引用類型有什么區(qū)別呢?最主要的一個(gè),就是當(dāng)變量類型為值類型時(shí),變量存儲(chǔ)的就是變量值本身,而當(dāng)變量類型為引用類型時(shí),變量存儲(chǔ)的并不是變量值,而只是一個(gè)指向變量值的指針,訪問(wèn)引用類型的變量值時(shí),首先是取到這個(gè)指針,然后是根據(jù)這個(gè)指針去獲取變量值。如果將一個(gè)引用類型的變量值賦給另一個(gè)變量,最終結(jié)果是這兩個(gè)變量同時(shí)指向了一個(gè)變量值,修改其中一個(gè)會(huì)同時(shí)修改到另一個(gè):
var a = {
name:'linjisong',
age:29
};
var b = a;//將引用類型的變量a賦給變量b,a、b同時(shí)指向了a開始指向的那個(gè)對(duì)象
b.name = 'oulinhai';//修改b指向的對(duì)象,也就是修改了a指向的對(duì)象
console.info(a.name);//oulinhai
b = {//將變量重新賦值,但是b原來(lái)指向的對(duì)象沒(méi)有變更,也就是a指向的對(duì)象沒(méi)有變化
name:'hujinxing',
age:23
};
console.info(a.name);//oulinhai
好了,關(guān)于變量類型先說(shuō)到這,如果再繼續(xù)到內(nèi)存存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)的話,就怕沉得下去浮不上來(lái)。
函數(shù)
如果說(shuō)對(duì)象是房間,那么函數(shù)就是有魔幻效應(yīng)的房間了。函數(shù)首先是對(duì)象,然后這個(gè)函數(shù)對(duì)象還具有很多魔幻功能……
1、函數(shù)
(1)函數(shù)是對(duì)象
函數(shù)也是一種對(duì)象,而用于創(chuàng)建函數(shù)對(duì)象實(shí)例的函數(shù)就是內(nèi)置的Function()函數(shù)(創(chuàng)建對(duì)象實(shí)例需要函數(shù),而函數(shù)又是一種對(duì)象實(shí)例,是不是讓你有了先有雞還是先有蛋的困惑?別鉆牛角尖了,只要雞能生蛋,蛋能孵雞就行了,誰(shuí)先誰(shuí)后還是留給哲學(xué)家吧),但是函數(shù)這種對(duì)象,又和一般的對(duì)象有著極大的不同,以至于對(duì)函數(shù)對(duì)象實(shí)例使用typeof時(shí)返回的不是object而是function了。
(2)函數(shù)名是指向函數(shù)對(duì)象的引用類型變量
function fn(p){
console.info(p);
}
console.info(fn);//fn(p),可以將fn作為一般變量來(lái)訪問(wèn)
var b = fn;
b('function');//function,可以對(duì)b使用函數(shù)調(diào)用,說(shuō)明b指向的對(duì)象(也就是原來(lái)fn指向的對(duì)象)是一個(gè)函數(shù)
注:關(guān)于函數(shù)名,在ES5的嚴(yán)格模式下,已經(jīng)不允許使用eval和arguments了,當(dāng)然,參數(shù)名也不能用這兩個(gè)了(我想除非你是專業(yè)黑客,否則也不會(huì)使用這些作為標(biāo)識(shí)符來(lái)使用吧)。
2、函數(shù)創(chuàng)建
(1)作為一種對(duì)象,函數(shù)也有和普通對(duì)象類似的創(chuàng)建方式,使用new調(diào)用構(gòu)造函數(shù)Function(),它可以接受任意數(shù)量的參數(shù),最后一個(gè)參數(shù)作為函數(shù)體,而前面的所有參數(shù)都作為函數(shù)的形式參數(shù),前面的形式參數(shù)還可以使用逗號(hào)隔開作為一個(gè)參數(shù)傳入,一般形式為:
var fn = new Function(p1, p2, ..., pn, body);
//或者
var fn = Function(p1, p2, ..., pn, body);
//或者
var fn = new Function("p1, p2, ..., pn", q1, q2, ..., qn, body);
//或者
var fn = Function("p1, p2, ..., pn", q1, q2, ..., qn, body);
例如:
var add = new Function('a','b','return a + b;');
console.info(add(2,1));//3
var subtract = Function('a','b','return a - b;');
console.info(subtract(2,1));//1
var sum = new Function('a,b','c','return a + b + c;');
console.info(sum(1,2,3));//6
這種方式創(chuàng)建函數(shù),會(huì)解析兩次代碼,一次正常解析,一次解析函數(shù)體,效率會(huì)影響,但是比較適合函數(shù)體需要?jiǎng)討B(tài)編譯的情況。
(2)由于函數(shù)對(duì)象本身的特殊性,我們還可以使用關(guān)鍵字function來(lái)創(chuàng)建函數(shù):
function add(a, b){
return a + b;
}
console.info(add(2,1));//3
var subtract = function(a, b){
return a - b;
};
console.info(subtract(2,1));//1
從上可以看到,使用function關(guān)鍵字創(chuàng)建函數(shù)也有兩種方式:函數(shù)聲明和函數(shù)表達(dá)式。這兩種方式都能實(shí)現(xiàn)我們想要的效果,那他們之間有什么區(qū)別呢?這就是我們下面要講的。
3、函數(shù)聲明和函數(shù)表達(dá)式
(1)從形式上區(qū)分,在ECMA-262的規(guī)范中,可以看到:
函數(shù)聲明: function Identifier (參數(shù)列表(可選)){函數(shù)體}
函數(shù)表達(dá)式:function Identifier(可選)(參數(shù)列表(可選)){函數(shù)體}
除了函數(shù)表達(dá)式的標(biāo)識(shí)符(函數(shù)名)是可選的之外沒(méi)有任何區(qū)別,但我們也可以從中得知:沒(méi)有函數(shù)名的一定是函數(shù)表達(dá)式。當(dāng)然,有函數(shù)名的,我們就只能從上下文來(lái)判斷了。
(2)從上下文區(qū)分,這個(gè)說(shuō)起來(lái)簡(jiǎn)單,就是:只允許表達(dá)式出現(xiàn)的上下文中的一定是函數(shù)表達(dá)式,只允許聲明出現(xiàn)的上下文的一定是函數(shù)聲明。舉一些例子:
function fn(){};//函數(shù)聲明
//function fn(){}(); // 異常,函數(shù)聲明不能直接調(diào)用
var fn = function fn(){};//函數(shù)表達(dá)式
(function fn(){});//函數(shù)表達(dá)式,在分組操作符內(nèi)
+function fn(){console.info(1);}();//1,函數(shù)表達(dá)式,出現(xiàn)在操作符+之后,因此可以直接調(diào)用,這里,也可以使用其它的操作符,比如new
new function fn(){console.info(2);}();//2,函數(shù)表達(dá)式,new操作符之后
(function(){
function fn(){};//函數(shù)聲明
});
(3)區(qū)別:我們?yōu)槭裁匆ㄟ@么大力氣來(lái)區(qū)分函數(shù)聲明和函數(shù)表達(dá)式呢?自然就是因?yàn)樗鼈兊牟煌c(diǎn)了,他們之間最大的不同,就是聲明會(huì)提升,關(guān)于聲明提升,在前面基礎(chǔ)語(yǔ)法的那一篇文章中,曾經(jīng)對(duì)全局作用域中的聲明提升做過(guò)討論,我們把那里的結(jié)論復(fù)習(xí)一下:
A、引擎在解析時(shí),首先會(huì)解析函數(shù)聲明,然后解析變量聲明(解析時(shí)不會(huì)覆蓋類型),最后再執(zhí)行代碼;
B、解析函數(shù)聲明時(shí),會(huì)同時(shí)解析類型(函數(shù)),但不會(huì)執(zhí)行,解析變量聲明時(shí),只解析變量,不會(huì)初始化。
在那里也舉了一些例子來(lái)演示(回憶一下),不過(guò)沒(méi)有同名稱的聲明例子,這里補(bǔ)充一下:
console.info(typeof fn);//function,聲明提升,以函數(shù)為準(zhǔn)
var fn = '';
function fn(){
}
console.info(typeof fn);//string,由于已經(jīng)執(zhí)行了代碼,這里fn的類型變?yōu)閟tring
try{
fn();//已經(jīng)是string類型,不能調(diào)用了,拋出類型異常
}catch(e){
console.info(e);//TypeError
}
fn = function(){console.info('fn');};//如果想調(diào)用fn,只能再使用函數(shù)表達(dá)式賦值給fn
fn();//fn,可以調(diào)用
console.info(typeof gn);//function
function gn(){
}
var gn = '';
console.info(typeof gn);//string
可以看出:不管變量聲明是在前還是在后,在聲明提升時(shí)都是以函數(shù)聲明優(yōu)先,但是在聲明提升之后,由于要執(zhí)行變量初始化,而函數(shù)聲明不再有初始化(函數(shù)類型在提升時(shí)已經(jīng)解析),因此后面輸出時(shí)就成為String類型了。
上面第3行定義了一個(gè)函數(shù),然后第7行馬上調(diào)用,結(jié)果竟然不行!你該明白保持全局命名空間清潔的重要性了吧,要不然,你可能會(huì)遇到“我在代碼中明明定義了一個(gè)函數(shù)卻不能調(diào)用”這種鬼事情,反過(guò)來(lái),如果你想確保你定義的函數(shù)可用,最好就是使用函數(shù)表達(dá)式來(lái)定義。
還有一個(gè)問(wèn)題,這里我們?cè)趺创_定變量類型是在初始化時(shí)候而不是在變量聲明提升時(shí)候改變的呢?看下面的代碼:
console.info(typeof fn);//function
function fn(){
}
var fn;
console.info(typeof fn);//function
可以看到,聲明提升后類型為function,并且由于沒(méi)有初始化代碼,最后的類型沒(méi)有改變。
關(guān)于函數(shù)聲明和函數(shù)表達(dá)式,還有一點(diǎn)需要注意的,看下面的代碼:
if(true){
function fn(){
return 1;
}
}else{
function fn(){
return 2;
}
}
console.info(fn());// 在Firefox輸出1,在Opera輸出2,在Opera中聲明提升,后面的聲明會(huì)覆蓋前面的同級(jí)別聲明
if(true){
gn = function(){
return 1;
};
}else{
gn = function(){
return 2;
};
}
console.info(gn());// 1,所有瀏覽器輸出都是1
在ECMAScript規(guī)范中,命名函數(shù)表達(dá)式的標(biāo)識(shí)符屬于內(nèi)部作用域,而函數(shù)聲明的標(biāo)識(shí)符屬于定義作用域。
var sum = function fn(){
var total = 0,
l = arguments.length;
for(; l; l--)
{
total += arguments[l-1];
}
console.info(typeof fn);
return total;
}
console.info(sum(1,2,3,4));//function,10
console.info(fn(1,2,3,4));//ReferenceError
上面是一個(gè)命名函數(shù)表達(dá)式在FireFox中的運(yùn)行結(jié)果,在函數(shù)作用域內(nèi)可以訪問(wèn)這個(gè)名稱,但是在全局作用域中訪問(wèn)出現(xiàn)引用異常。不過(guò)命名函數(shù)表達(dá)式在IE9之前的IE瀏覽器中會(huì)被同時(shí)作為函數(shù)聲明和函數(shù)表達(dá)式來(lái)解析,并且會(huì)創(chuàng)建兩個(gè)對(duì)象,好在IE9已經(jīng)修正。
除了全局作用域,還有一種函數(shù)作用域,在函數(shù)作用域中,參與到聲明提升競(jìng)爭(zhēng)的還有函數(shù)的參數(shù)。首先要明確的是,函數(shù)作用域在函數(shù)定義時(shí)不存在的,只有在函數(shù)實(shí)際調(diào)用才有函數(shù)作用域。
// 參數(shù)與內(nèi)部變量,參數(shù)優(yōu)先
function fn(inner){
console.info(inner);// param
console.info(other);// undefined
var inner = 'inner';
var other = 'other';
console.info(inner);// inner
console.info(other);// other
}
fn('param');
// 參數(shù)與內(nèi)部函數(shù),內(nèi)部函數(shù)優(yōu)先
function gn(inner){
console.info(inner);// inner()函數(shù)
console.info(inner());// undefined
function inner(){
return other;
}
var other = 'other';
console.info(inner);// inner()函數(shù)
console.info(inner());// other
}
gn('param');
通過(guò)上面的輸出結(jié)果,我們得出優(yōu)先級(jí):內(nèi)部函數(shù)聲明 > 函數(shù)參數(shù) > 內(nèi)部變量聲明。
這里面的一個(gè)過(guò)程是:首先內(nèi)部函數(shù)聲明提升,并將函數(shù)名的類型設(shè)置為函數(shù)類型,然后解析函數(shù)參數(shù),將傳入的實(shí)際參數(shù)值賦給形式參數(shù),最后再內(nèi)部變量聲明提升,只提升聲明,不初始化,如果有重名,同優(yōu)先級(jí)的后面覆蓋前面的,不同優(yōu)先級(jí)的不覆蓋(已經(jīng)解析了優(yōu)先級(jí)高的,就不再解析優(yōu)先級(jí)低的)。
說(shuō)明一下,這只是我根據(jù)輸出結(jié)果的推斷,至于后臺(tái)實(shí)現(xiàn),也有可能步驟完全相反,并且每一步都覆蓋前一步的結(jié)果,甚至是從中間開始,然后做一個(gè)優(yōu)先級(jí)標(biāo)志確定是否需要覆蓋,當(dāng)然,從效率上來(lái)看,應(yīng)該是我推斷的過(guò)程會(huì)更好。另外,全局作用域其實(shí)就是函數(shù)作用域的一個(gè)簡(jiǎn)化版,沒(méi)有函數(shù)參數(shù)。
這里就不再舉綜合的例子了,建議將這篇文章和前面的基礎(chǔ)語(yǔ)法那一篇一起閱讀,可能效果會(huì)更好。關(guān)于優(yōu)先級(jí)與覆蓋,也引出下面要說(shuō)的一個(gè)問(wèn)題。
4、函數(shù)重載
函數(shù)是對(duì)象,函數(shù)名是指向函數(shù)對(duì)象的引用類型變量,這使得我們不可能像一般面向?qū)ο笳Z(yǔ)言中那樣實(shí)現(xiàn)重載:
function fn(a){
return a;
}
function fn(a,b){
return a + b;
}
console.info(fn(1)); // NaN
console.info(fn(1,2));// 3
不要奇怪第8行為什么輸出NaN,因?yàn)楹瘮?shù)名只是一個(gè)變量而已,兩次函數(shù)聲明會(huì)依次解析,這個(gè)變量最終指向的函數(shù)就是第二個(gè)函數(shù),而第8行只傳入1個(gè)參數(shù),在函數(shù)內(nèi)部b就自動(dòng)賦值為undefined,然后與1相加,結(jié)果就是NaN。換成函數(shù)表達(dá)式,也許就好理解多了,只是賦值了兩次而已,自然后面的賦值會(huì)覆蓋前面的:
var fn = function (a){ return a; }
fn = function (a,b){ return a + b;}
那么,在ECMAScript中,怎么實(shí)現(xiàn)重載呢?回想一下簡(jiǎn)單數(shù)據(jù)類型包裝對(duì)象(Boolean、Number、String),既可以作為構(gòu)造函數(shù)創(chuàng)建對(duì)象,也可以作為轉(zhuǎn)換函數(shù)轉(zhuǎn)換數(shù)據(jù)類型,這是一個(gè)典型的重載。這個(gè)重載其實(shí)在前一篇文章中我們?cè)?jīng)討論過(guò):
(1)根據(jù)函數(shù)的作用來(lái)重載,這種方式的一般格式為:
function fn(){
if(this instanceof fn)
{
// 功能1
}else
{
// 功能2
}
}
這種方式雖然可行,但是很明顯作用也是有限的,比如就只能重載兩次,并且只能重載包含構(gòu)造函數(shù)的這種情形。當(dāng)然,你可以結(jié)合apply()或者call()甚至ES5中新增的bind()來(lái)動(dòng)態(tài)綁定函數(shù)內(nèi)部的this值來(lái)擴(kuò)展重載,但這已經(jīng)有了根據(jù)函數(shù)內(nèi)部屬性重載的意思了。
(2)根據(jù)函數(shù)內(nèi)部屬性來(lái)重載
function fn(){
var length = arguments.length;
if(0 == length)//將字面量放到左邊是從Java中帶過(guò)來(lái)的習(xí)慣,因?yàn)槿绻麑⒈容^操作符寫成了賦值操作符(0=length)的話,編譯器會(huì)提示我錯(cuò)誤。如果你不習(xí)慣這種方式,請(qǐng)?jiān)徫?br /> {
return 0;
}else if(1 == length)
{
return +arguments[0];
}else{
return (+arguments[0])+(+arguments[1]);
}
}
console.info(fn());//0
console.info(fn(1));//1
console.info(fn(true));//1
console.info(fn(1,2));//3
console.info(fn('1','2'));//3
這里就是利用函數(shù)內(nèi)部屬性arguments來(lái)實(shí)現(xiàn)重載的。當(dāng)然,在內(nèi)部重載的方式可以多種多樣,你還可以結(jié)合typeof、instanceof等操作符來(lái)實(shí)現(xiàn)你想要的功能。至于內(nèi)部屬性arguments具體是什么?這就是下面要講的。
5、函數(shù)內(nèi)部屬性arguments
簡(jiǎn)單一點(diǎn)說(shuō),函數(shù)內(nèi)部屬性,就是只能在函數(shù)體內(nèi)訪問(wèn)的屬性,由于函數(shù)體只有在函數(shù)被調(diào)用的時(shí)候才會(huì)去執(zhí)行,因此函數(shù)內(nèi)部屬性也只有在函數(shù)調(diào)用時(shí)才會(huì)去解析,每次調(diào)用都會(huì)有相應(yīng)的解析,因此具有動(dòng)態(tài)特性。這種屬性有:this和arguments,這里先看arguments,在下一篇文章中再說(shuō)this。
(1)在函數(shù)定義中的參數(shù)列表稱為形式參數(shù),而在函數(shù)調(diào)用時(shí)候?qū)嶋H傳入的參數(shù)稱為實(shí)際參數(shù)。一般的類C語(yǔ)言,要求在函數(shù)調(diào)用時(shí)實(shí)際參數(shù)要和形式參數(shù)一致,但是在ECMAScript中,這兩者之間沒(méi)有任何限制,你可以在定義的時(shí)候有2個(gè)形式參數(shù),在調(diào)用的時(shí)候傳入2個(gè)實(shí)際參數(shù),但你也可以傳入3個(gè)實(shí)際參數(shù),還可以只傳入1個(gè)實(shí)際參數(shù),甚至你什么參數(shù)都不傳也可以。這種特性,正是利用函數(shù)內(nèi)部屬性來(lái)實(shí)現(xiàn)重載的基礎(chǔ)。
(2)形式參數(shù)甚至可以取相同的名稱,只是在實(shí)際傳入時(shí)會(huì)取后面的值作為形式參數(shù)的值(這種情況下可以使用arguments來(lái)訪問(wèn)前面的實(shí)際參數(shù)):
function gn(a,a){
console.info(a);
console.info(arguments[0]);
console.info(arguments[1]);
}
gn(1,2);//2,1,2
gn(1);//undefined,1,undefined
這其實(shí)也可以用本文前面關(guān)于聲明提升的結(jié)論來(lái)解釋:同優(yōu)先級(jí)的后面的覆蓋前面的,并且函數(shù)參數(shù)解析時(shí)同時(shí)解析值。當(dāng)然,這樣一來(lái),安全性就很成問(wèn)題了,因此在ES5的嚴(yán)格模式下,重名的形式參數(shù)被禁止了。
(3)實(shí)際參數(shù)的值由形式參數(shù)來(lái)接受,但如果實(shí)際參數(shù)和形式參數(shù)不一致怎么辦呢?答案就是使用arguments來(lái)存儲(chǔ),事實(shí)上,即便實(shí)際參數(shù)和形式參數(shù)一致,也存在arguments對(duì)象,并且保持著和已經(jīng)接受了實(shí)際參數(shù)的形式參數(shù)之間的同步。將這句話細(xì)化一下來(lái)理解:
•arguments是一個(gè)類數(shù)組對(duì)象,可以像訪問(wèn)數(shù)組元素那樣通過(guò)方括號(hào)和索引來(lái)訪問(wèn)arguments元素,如arguments[0]、arugments[1]。
•arguments是一個(gè)類數(shù)組對(duì)象,除了繼承自O(shè)bject的屬性和方法(有些方法被重寫了)外,還有自己本身的一些屬性,如length、callee、caller,這里length表示實(shí)際參數(shù)的個(gè)數(shù)(形式參數(shù)的個(gè)數(shù)?那就是函數(shù)屬性length了),callee表示當(dāng)前函數(shù)對(duì)象,而caller只是為了和函數(shù)屬性caller區(qū)分而定義的,其值為undefined。
•arguments是一個(gè)類數(shù)組對(duì)象,但并不是真正的數(shù)組對(duì)象,不能直接對(duì)arguments調(diào)用數(shù)組對(duì)象的方法,如果要調(diào)用,可以先使用Array.prototype.slice.call(arguments)先轉(zhuǎn)換為數(shù)組對(duì)象。
•arguments保存著函數(shù)被調(diào)用時(shí)傳入的實(shí)際參數(shù),第0個(gè)元素保存第一個(gè)實(shí)際參數(shù),第1個(gè)元素保存第二個(gè)實(shí)際參數(shù),依次類推。
•arguments保存實(shí)際參數(shù)值,而形式參數(shù)也保存實(shí)際參數(shù)值,這兩者之間有一個(gè)同步關(guān)系,修改一個(gè),另一個(gè)也會(huì)隨之修改。
•arguments和形式參數(shù)之間的同步,只有當(dāng)形式參數(shù)實(shí)際接收了實(shí)際參數(shù)時(shí)才存在,對(duì)于沒(méi)有接收實(shí)際參數(shù)的形式參數(shù),不存在這種同步關(guān)系。
•arguments對(duì)象雖然很強(qiáng)大,但是從性能上來(lái)說(shuō)也存有一定的損耗,所以如果不是必要,就不要使用,建議還是優(yōu)先使用形式參數(shù)。
fn(0,-1);
function fn(para1,para2,para3,para4){
console.info(fn.length);//4,形式參數(shù)個(gè)數(shù)
console.info(arguments.length);//2,實(shí)際參數(shù)個(gè)數(shù)
console.info(arguments.callee === fn);//true,callee對(duì)象指向fn本身
console.info(arguments.caller);//undefined
console.info(arguments.constructor);//Object(),而不是Array()
try{
arguments.sort();//類數(shù)組畢竟不是數(shù)組,不能直接調(diào)用數(shù)組方法,拋出異常
}catch(e){
console.info(e);//TypeError
}
var arr = Array.prototype.slice.call(arguments);//先轉(zhuǎn)換為數(shù)組
console.info(arr.sort());//[-1,0],已經(jīng)排好序了
console.info(para1);//0
arguments[0] = 1;
console.info(para1);//1,修改arguments[0],會(huì)同步修改形式參數(shù)para1
console.info(arguments[1]);//-1
para2 = 2;
console.info(arguments[1]);//2,修改形式參數(shù)para2,會(huì)同步修改arguments[1]
console.info(para3);//undefined,未傳入實(shí)際參數(shù)的形式參數(shù)為undefined
arguments[2] = 3;
console.info(arguments[2]);//3
console.info(para3);//undefined,未接受實(shí)際參數(shù)的形式參數(shù)沒(méi)有同步關(guān)系
console.info(arguments[3]);//undefined,未傳入實(shí)際參數(shù),值為undefined
para4 = 4;
console.info(para4);//4
console.info(arguments[3]);//undefined,為傳入實(shí)際參數(shù),不會(huì)同步
}
經(jīng)過(guò)測(cè)試,arguments和形式參數(shù)之間的同步是雙向的,但是《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》中第66頁(yè)說(shuō)是單向的:修改形式參數(shù)不會(huì)改變arguments。這可能是原書另一個(gè)Bug,也可能是FireFox對(duì)規(guī)范做了擴(kuò)展。不過(guò),這也讓我們知道,即便經(jīng)典如此,也還是存有Bug的可能,一切當(dāng)以實(shí)際運(yùn)行為準(zhǔn)。
•結(jié)合arguments及其屬性callee,可以實(shí)現(xiàn)在函數(shù)內(nèi)部調(diào)用自身時(shí)與函數(shù)名解耦,這樣即便函數(shù)賦給了另一個(gè)變量,而函數(shù)名(別忘了,也是一個(gè)變量)另外被賦值,也能夠保證運(yùn)行正確。典型的例子有求階乘函數(shù)、斐波那契數(shù)列等。
//求階乘
function factorial(num){
if(num <= 1)
{
return 1;
}else{
return num * factorial(num - 1);
}
}
var fn = factorial;
factorial = null;
try{
fn(2);//由于函數(shù)內(nèi)部遞歸調(diào)用了factorial,而factorial已經(jīng)賦值為null了,所以拋出異常
}catch(e){
console.info(e);//TypeError
}
//斐波那契數(shù)列
function fibonacci(num){
if(1 == num || 2 == num){
return 1;
}else{
return arguments.callee(num - 1) + arguments.callee(num - 2);
}
}
var gn = fibonacci;
fibonacci = null;
console.info(gn(9));//34,使用arguments.callee,實(shí)現(xiàn)了函數(shù)對(duì)象和函數(shù)名的解耦,可以正常執(zhí)行
遞歸的算法非常簡(jiǎn)潔,但因?yàn)橐S護(hù)運(yùn)行棧,效率不是很好。關(guān)于遞歸的優(yōu)化,也有很多非常酣暢漓淋的算法,這里就不深入了。
需要注意的是,arguments.callee在ES5的嚴(yán)格模式下已經(jīng)被禁止使用了,這時(shí)候可以使用命名的函數(shù)表達(dá)式來(lái)實(shí)現(xiàn)同樣的效果:
//斐波那契數(shù)列
var fibonacci = (function f(num){
return num <= 2 ? 1 : (f(num - 1) + f(num - 2));
});
var gn = fibonacci;
fibonacci = null;
console.info(gn(9));//34,使用命名函數(shù)表達(dá)式實(shí)現(xiàn)了函數(shù)對(duì)象和函數(shù)名的解耦,可以正常執(zhí)行
相關(guān)文章
javascript設(shè)計(jì)模式之鴨子類型和多態(tài)
這篇文章主要為大家介紹了javascript鴨子類型和多態(tài),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助<BR>2022-01-01JavaScript DOM學(xué)習(xí)第八章 表單錯(cuò)誤提示
這一章詳細(xì)介紹的表單錯(cuò)誤提示的方法比那種大多數(shù)使用警告框的方法要好的多。2010-02-02JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記12 js正則表達(dá)式
前面在分析PhoneGap源碼的時(shí)候,曾經(jīng)總結(jié)過(guò)一次正則表達(dá)式的用法,為了不同系列文章的完整性,這里將那里的總結(jié)遷移過(guò)來(lái)2012-10-10JavaScript SetInterval與setTimeout使用方法詳解
本文講解了JavaScript SetInterval與setTimeout的區(qū)別,并用代碼示例演示了使用方法2013-11-11Javascript入門學(xué)習(xí)資料收集整理篇
為大家更好的接觸和學(xué)習(xí)js資料,所以我轉(zhuǎn)了這篇文章,我大約的看了下,文章寫的非常不錯(cuò),希望大家不要急,慢慢看,第一次看不懂不要緊,多練習(xí)就可以了2008-07-07Javascript標(biāo)準(zhǔn)DOM Range操作全集
Javascript標(biāo)準(zhǔn)DOM Range操作全集...2007-01-01微信小程序自定義數(shù)據(jù)實(shí)現(xiàn)級(jí)聯(lián)省市區(qū)組件功能
這篇文章主要介紹了微信小程序自定義數(shù)據(jù)實(shí)現(xiàn)級(jí)聯(lián)省市區(qū)組件功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03