函數(shù)只定義一次,但可能被執(zhí)行或調(diào)用任意次蛆楞。JS函數(shù)是參數(shù)化的伟端,函數(shù)的定義會包括一個稱為形參的標(biāo)識符列表杯道,這些參數(shù)在函數(shù)體中像局部變量一樣工作。函數(shù)調(diào)用會為形參提供實參的值责蝠。函數(shù)使用它們實參的值來計算返回值党巾,成為該函數(shù)調(diào)用表達(dá)式的值。除了實參之外霜医,每次調(diào)用還會擁有另一個值齿拂,本次調(diào)用的上下文——this關(guān)鍵字的值。
如果函數(shù)掛載在一個對象上肴敛,作為對象的一個屬性署海,就稱它為對象的方法。當(dāng)通過這個對象來調(diào)用函數(shù)時医男,該對象就是此次調(diào)用的上下文砸狞,也就是該函數(shù)的this的值。用于初始化一個新創(chuàng)建的對象的函數(shù)稱為構(gòu)造函數(shù)镀梭。
在JS里刀森,函數(shù)即對象,JS可以把函數(shù)賦值給變量报账,或者作為參數(shù)傳遞給其他函數(shù)研底。因為函數(shù)是對象埠偿,所以可以給它們設(shè)置屬性,甚至調(diào)用它們的方法榜晦。
JS的函數(shù)可以嵌套在其他函數(shù)中定義冠蒋,這樣它們就可以訪問它們被定義時所處的作用域中的任何變量。這意味著JS函數(shù)構(gòu)成了一個閉包乾胶。
函數(shù)定義
函數(shù)使用function關(guān)鍵字來定義浊服,它可以用在函數(shù)定義表達(dá)式或函數(shù)聲明語句里。在這兩種形式中胚吁,函數(shù)定義都從function關(guān)鍵字開始,其后跟隨這些組成部分:
函數(shù)名稱標(biāo)識符愁憔。新定義的函數(shù)對象會賦值給這個變量腕扶。對函數(shù)定義表達(dá)式來說,這個名字是可選的:如果存在吨掌,該名字只存在于函數(shù)體中半抱,并指代該函數(shù)對象本身。
一對圓括號膜宋。其中包含0個或多個用逗號隔開的標(biāo)識符組成的列表窿侈。這些標(biāo)識符是函數(shù)的參數(shù)名稱,它們就像函數(shù)體中的局部變量一樣秋茫。
一對花括號史简。其中包含0或多條JS語句。這些語句構(gòu)成了函數(shù)體肛著,一旦調(diào)用函數(shù)就會執(zhí)行這些語句圆兵。
//輸出o的每個屬性的名稱和值,返回undefined
function printprops(o){
for(var p in o){
console.log(p+":"+o[p]+"\n")
}
}
//這個函數(shù)表達(dá)式定義了一個函數(shù)用來求傳入?yún)?shù)的平方
var square=function(x){return xx;}
//函數(shù)表達(dá)式可以包含名稱枢贿,這在遞歸時很有用
var f=function fact(x){
if(x<=1){return 1;}else{ return xfact(x-1);}
};
//函數(shù)表達(dá)式也可以作為參數(shù)傳給其它參數(shù)
data.sort(function(a,b){return a-b;});
//函數(shù)表達(dá)式有時定義后立即調(diào)用
var tansquared=(function(x){return x*x;}(10))
以表達(dá)式方式定義的函數(shù)殉农,函數(shù)的名稱是可選的。一條函數(shù)聲明語句實際上聲明了一個變量局荚,并把一個函數(shù)對象賦值給它超凳。定義函數(shù)表達(dá)式時并沒有聲明一個變量。如果一個函數(shù)定義表達(dá)式包含名稱耀态,函數(shù)的局部作用域?qū)粋€綁定到函數(shù)對象的名稱轮傍。實際上,函數(shù)的名稱將成為函數(shù)內(nèi)部的一個局部變量茫陆。
函數(shù)聲明語句被提前到外部腳本或外部函數(shù)作用域的頂部金麸,所以以這種方式聲明的函數(shù),可以被在它定義之前出現(xiàn)的代碼調(diào)用簿盅。以表達(dá)式方式定義的函數(shù)在定義之前無法調(diào)用挥下,因為變量的聲明雖然提前了揍魂,但給變量賦值是不會提前的。
return語句導(dǎo)致函數(shù)停止執(zhí)行棚瘟,并返回它的表達(dá)式的值給調(diào)用者现斋。如果return語句沒有一個與之相關(guān)的表達(dá)式,則它返回undefined值偎蘸。如果一個函數(shù)不包含return語句庄蹋,那它就只執(zhí)行函數(shù)體中的每條語句,并返回undefined值給調(diào)用者迷雪。
嵌套函數(shù)
函數(shù)可以嵌套在其他函數(shù)里限书。
function hypotenuse(a,b){
function square(x){return x*x;}
return Math.sqrt()(square(a)+square(b));
}
嵌套函數(shù)的變量作用域規(guī)則:它們可以訪問嵌套它們的函數(shù)的參數(shù)和變量。
函數(shù)聲明語句并非真正的語句章咧,ECMAScript規(guī)范只是允許它們作為頂級語句倦西。它們可以出現(xiàn)在全局代碼里,或者內(nèi)嵌在其他函數(shù)中赁严,但它們不能出現(xiàn)在循環(huán)語句扰柠、條件判斷語句、或try/catch/finally和with語句中疼约。函數(shù)定義表達(dá)式可以出現(xiàn)在JS代碼的任何地方卤档。
函數(shù)調(diào)用
構(gòu)成函數(shù)主體的JS代碼在定義之時并不會執(zhí)行,只有調(diào)用該函數(shù)時程剥,它們才會執(zhí)行劝枣。有4種方式來調(diào)用函數(shù):
作為函數(shù)
作為方法
作為構(gòu)造函數(shù)
通過它們的call()和apply()方法間接調(diào)用
函數(shù)調(diào)用
使用調(diào)用表達(dá)式可以進(jìn)行普通的函數(shù)調(diào)用也可進(jìn)行方法調(diào)用。一個調(diào)用表達(dá)式由多個函數(shù)表達(dá)式組成倡缠,每個函數(shù)表達(dá)式都是由一個函數(shù)對象和左圓括號哨免、參數(shù)列表和右圓括號組成,參數(shù)列表是由逗號分隔的0個或多個參數(shù)表達(dá)式組成昙沦。如果函數(shù)表達(dá)式是一個屬性訪問表達(dá)式琢唾,即該函數(shù)是一個對象的屬性或數(shù)組中的一個元素,那么它就是一個方法調(diào)用表達(dá)式盾饮。
printprops({x:1});
var total=distance(0,0,2,1)+distance(2,1,3,5);
var probability=factorial(5)/factorial(13);
在一次調(diào)用中采桃,每個參數(shù)表達(dá)式都會計算出一個值,計算的結(jié)果作為參數(shù)傳遞給另外一個函數(shù)丘损。這些值作為實參傳遞給聲明函數(shù)時定義的形參普办。在函數(shù)體中存在一個形參的引用,指向當(dāng)前傳入的實參列表徘钥,通過它可以獲得參數(shù)的值衔蹲。
對于普通的函數(shù)調(diào)用,函數(shù)的返回值成為調(diào)用表達(dá)式的值。如果該函數(shù)返回是因為解釋器到達(dá)結(jié)尾舆驶,返回值就是undefined橱健。如果函數(shù)返回是因為解釋器執(zhí)行到一條return語句,返回值就是return之后的表達(dá)式的值沙廉,如果return語句沒有值拘荡,則返回undefined。
以函數(shù)形式調(diào)用的函數(shù)通常不適用this關(guān)鍵字撬陵。不過this可以用來判斷當(dāng)前是否是嚴(yán)格模式珊皿。根據(jù)ES3和非嚴(yán)格的ES5對函數(shù)調(diào)用的規(guī)定,調(diào)用上下文(this的值)是全局對象巨税。在嚴(yán)格模式下調(diào)用上下文則是undefined蟋定。
//定義并調(diào)用一個函數(shù)來確定當(dāng)前腳本運行時是否為嚴(yán)格模式
var strict=(function(){return !this;}());
方法調(diào)用
一個方法就是保存在一個對象的屬性里的函數(shù)。如果有一個函數(shù)f和一個對象o草添,則給o定義一個名為m()的方法可用下面的方法:
o.m=f;
o.m(); //調(diào)用m()方法
對方法調(diào)用的參數(shù)和返回值的處理溢吻,和普通函數(shù)調(diào)用一致。方法調(diào)用和函數(shù)調(diào)用有一個重要區(qū)別果元,即調(diào)用上下文。屬性訪問表達(dá)式由兩部分組成:一個對象(o)和屬性名稱(m)犀盟。在像這樣的方法調(diào)用表達(dá)式里而晒,對象o成為調(diào)用上下文,函數(shù)體可以使用this引用該對象阅畴。
var calculator={
operand1:1,
operand2:1,
add:function(){
this.result=this.operand1+this.operand2;
}
};
calculator.add(); //calculator.result=>2
大多數(shù)方法調(diào)用使用點符號來訪問屬性倡怎,使用方括號也可以進(jìn)行屬性訪問操作。
o"m"; //o.m(x,y)的另一種寫法
a0; //假設(shè)a[0]是一個函數(shù)
方法調(diào)用可能包括更復(fù)雜的屬性訪問表達(dá)式:
customer.surname.toUpperCase(); //調(diào)用customer.surname的方法
f().m(); //在f()調(diào)用結(jié)束后繼續(xù)調(diào)用返回值中的方法m()
任何函數(shù)只要作為方法調(diào)用實際上都會傳入一個隱式的實參贱枣。這個實參是一個對象监署,方法調(diào)用的母體就是這個對象。通常來講纽哥,基于那個對象的方法可以執(zhí)行多種操作钠乏,方法調(diào)用的語法表明了函數(shù)將基于一個對象進(jìn)行操作。
rect.setSize(width,height);
setRectSize(width,height);
第一行的方法調(diào)用表明這個函數(shù)執(zhí)行的載體是rect對象春塌,函數(shù)中的所有操作都將基于這個對象晓避。
this是一個關(guān)鍵字,不是變量也不是屬性名只壳。JS的語法不允許給this賦值俏拱。
和變量不同,this沒有作用域的限制吼句,嵌套的函數(shù)不會從調(diào)用它的函數(shù)中繼承this锅必。如果嵌套函數(shù)作為方法調(diào)用,其this的值指向調(diào)用它的對象惕艳。如果嵌套函數(shù)作為函數(shù)調(diào)用搞隐,其this值不是全局對象(非嚴(yán)格模式下)就是undefined(嚴(yán)格模式下)驹愚。如果想訪問外部函數(shù)的this值,需要將this的值保存在一個變量里尔许,這個變量和內(nèi)部函數(shù)都同在一個作用域內(nèi)么鹤。
var o={
m:function(){
var self=this;
console.log(this===o); //true
f();
function f(){ //定義一個嵌套函數(shù)f()
console.log(this===o); //false,this的值是全局對象或undefined
console.log(self===o); //true
}
}
};
o.m();
構(gòu)造函數(shù)調(diào)用
如果函數(shù)或方法調(diào)用之前帶有關(guān)鍵字new,它就構(gòu)成構(gòu)造函數(shù)調(diào)用味廊。如果構(gòu)造函數(shù)調(diào)用在圓括號內(nèi)包含一組實參列表蒸甜,先計算這些實參表達(dá)式,然后傳入函數(shù)內(nèi)余佛,這和函數(shù)調(diào)用和方法調(diào)用是一致的柠新。但如果構(gòu)造函數(shù)沒有形參,構(gòu)造函數(shù)調(diào)用的語法允許省略實參列表和圓括號辉巡。凡是沒有形參的構(gòu)造函數(shù)調(diào)用都可以省略圓括號恨憎。
var o=new Object();
var o=new Object;
構(gòu)造函數(shù)調(diào)用創(chuàng)建一個新的空對象,這個對象繼承自構(gòu)造函數(shù)的prototype屬性郊楣。構(gòu)造函數(shù)試圖初始化這個新創(chuàng)建的對象憔恳,并將這個對象用做其調(diào)用上下文,因此構(gòu)造函數(shù)可以使用this關(guān)鍵字來引用這個新創(chuàng)建的對象净蚤。盡管構(gòu)造函數(shù)看起來像一個方法調(diào)用钥组,它依然會使用這個新對象作為調(diào)用上下文。也就是說今瀑,在表達(dá)式new o.m()中程梦,調(diào)用上下文并不是o。
構(gòu)造函數(shù)通常不使用return關(guān)鍵字橘荠,它們通常初始化新對象屿附,當(dāng)構(gòu)造函數(shù)的函數(shù)體執(zhí)行完畢時,它會顯式返回哥童。在這種情況下挺份,構(gòu)造函數(shù)調(diào)用表達(dá)式的計算結(jié)果就是這個新對象的值。如果構(gòu)造函數(shù)顯式地使用return語句返回一個對象贮懈,那么調(diào)用表達(dá)式的值就是這個對象压恒。如果構(gòu)造函數(shù)使用return語句但沒有指定返回值,或返回一個原始值错邦,那么這時將忽略返回值探赫,同時使用這個新對象作為調(diào)用結(jié)果。
間接調(diào)用
函數(shù)也是對象撬呢,函數(shù)對象也可以包含方法伦吠。其中的兩個方法call()和apply()可以間接地調(diào)用函數(shù)。兩個方法都允許顯式指定調(diào)用所需的this值,也就是說任何函數(shù)可以作為任何對象的方法來調(diào)用毛仪,哪怕這個函數(shù)不是那個對象的方法搁嗓。兩個方法都可以指定調(diào)用的實參。call()方法使用它自有的實參列表作為函數(shù)的實參箱靴。apply()方法則要求以數(shù)組的形式傳入?yún)?shù)腺逛。
函數(shù)的實參和形參
函數(shù)定義并未指定函數(shù)形參的類型,函數(shù)調(diào)用也未對傳入的實參值做任何類型的檢查衡怀。函數(shù)調(diào)用甚至不檢查傳入形參的個數(shù)棍矛。
可選形參
當(dāng)調(diào)用函數(shù)的時候傳入的實參比函數(shù)聲明時指定的形參個數(shù)要少,剩下的形參都將設(shè)置為undefined值抛杨。因此在調(diào)用函數(shù)時形參是否可選以及是否可以省略應(yīng)當(dāng)保持較好的適應(yīng)性够委。為做到這一點,應(yīng)當(dāng)給省略的參數(shù)賦一個合理的默認(rèn)值怖现。
//將對象o中可枚舉的屬性名追加至數(shù)組a中茁帽,并返回這個數(shù)組a
//如果省略a,則創(chuàng)建一個新數(shù)組并返回這個新數(shù)組
function getPropertyNames(o,/optional/a){
if(a===undefined){a=[];} //如果未定義則使用新數(shù)組或使用a=a||[];
for(var property in o){a.push(property);}
return a;
}
//這個函數(shù)調(diào)用可傳入1個或2個實參
var a=getPropertyNames(o); //將o的屬性存儲到一個新數(shù)組中
getPropertyNames(p,a); //將p的屬性追加至數(shù)組a中
當(dāng)用這種可選實參來實現(xiàn)函數(shù)時屈嗤,需要將可選實參放在實參列表的最后潘拨。調(diào)用函數(shù)是沒辦法省略第一個實參并傳入第二個實參的,它必須將undefined作為第一個實參顯式傳入饶号。在函數(shù)定義中使用/optional/來強(qiáng)調(diào)形參是可選的战秋。
可變長的實參列表:實參對象
當(dāng)調(diào)用函數(shù)的時候傳入的實參個數(shù)超過函數(shù)定義時的形參個數(shù)時,沒有辦法直接獲得未命名值的引用讨韭。參數(shù)對象解決了這個問題。在函數(shù)體中癣蟋,標(biāo)識符arguments是指向?qū)崊ο蟮囊猛赶酰瑢崊ο笫且粋€類數(shù)組對象,這樣可以通過數(shù)字下標(biāo)就能訪問傳入函數(shù)的實參值疯搅,而不用非要通過名字來得到實參濒生。
假設(shè)定義了函數(shù)f,它的實參只有一個x幔欧。如果調(diào)用這個函數(shù)時傳入兩個實參罪治,第一個實參可以通過參數(shù)名x來獲得,也可以通過arguments[0]來得到礁蔗。第二個實參只能通過arguments[1]來得到觉义。arguments也包含一個length屬性。浴井,用以標(biāo)識其所包含元素的個數(shù)晒骇。
function f(x,y,z){
//首先驗證傳入實參的個數(shù)是否正確
if(arguments.length!=3){
throw new Error("function f called with"+arguments.length+"arguments")
}
}
省略的實參都將是undefined,多出的參數(shù)會自動省略。
實參對象有一個重要的用處洪囤,就是讓函數(shù)可以操作任意數(shù)量的實參徒坡。
function max(/.../){
var max=Number.NEGATIVE_INFINITY;
//遍歷實參,查找并記住最大值
for(var i=0;i<arguments.length;i++){
if(arguments[i]>max) {max=arguments[i];}
return max; //返回最大值
}
}
var largest=max(1,10,100,2,3,10000); 10000
類似這種函數(shù)可以接收任意個數(shù)的參數(shù)瘤缩,這種函數(shù)也稱為不定實參函數(shù)喇完。不定實參函數(shù)的實參個數(shù)不能為0,arguments[]對象最適合在這樣一類函數(shù)中剥啤,這類函數(shù)包含固定個數(shù)的命名和必須參數(shù)锦溪,以及隨后個數(shù)不定的可選實參。
arguments并不是真正的數(shù)組铐殃,它是一個實參對象海洼。每個實參對象都包含以數(shù)字為索引的一組元素以及l(fā)ength屬性。
數(shù)組對象包含一個特性富腊。在非嚴(yán)格模式下坏逢,當(dāng)一個函數(shù)包含若干形參,實參對象的數(shù)組元素是函數(shù)形參所對應(yīng)實參的別名赘被,實參對象中以數(shù)字索引是整,并且形參名稱可以認(rèn)為是相同變量的不同命名。通過實參名字來修改實參值的話民假,通過arguments[]數(shù)組也可以獲取到更改后的值浮入。
function f(x){
console.log(x); //輸出實參的初始值
arguments[0]=null; //修改實參數(shù)組的元素同樣會修改x的值
console.log(x); //輸出null
}
在ES5中移除了實參對象的這個特性。在非嚴(yán)格模式中羊异,函數(shù)里的arguments僅僅是一個標(biāo)識符事秀,在嚴(yán)格模式中,它變成了一個保留字野舶。嚴(yán)格模式中的函數(shù)無法使用arguments作為形參名或局部變量名易迹,也不能給arguments賦值。
callee和caller屬性
除了數(shù)組元素平道,實參對象還定義了callee和caller屬性睹欲。在ES5嚴(yán)格模式中,對這兩個屬性的讀寫操作都會產(chǎn)生一個類型錯誤一屋。在非嚴(yán)格模式下callee屬性指代當(dāng)前正在執(zhí)行的函數(shù)窘疮。caller是非標(biāo)準(zhǔn)的,它指代調(diào)用當(dāng)前正在執(zhí)行的函數(shù)的函數(shù)冀墨。通過caller屬性可以訪問調(diào)用棧闸衫。callee屬性在某些時候非常有用,比如在匿名函數(shù)中通過callee來遞歸的調(diào)用自身诽嘉。
var factorial=function(x){
if(x<=1) {return 1;}
return x*arguments.callee(x-1);
};
將對象屬性用做實參
當(dāng)一個函數(shù)包含超過三個形參時楚堤,最好通過名/值對的形式來傳入?yún)?shù)疫蔓,這樣參數(shù)的順序就無關(guān)緊要了。為了實現(xiàn)這種調(diào)用身冬,定義函數(shù)的時候衅胀,傳入的實參都寫入一個對象中,在調(diào)用的時候傳入一個對象酥筝,對象中的名/值對是真正需要的實參數(shù)據(jù)滚躯。
//將原始數(shù)組的length元素復(fù)制至目標(biāo)數(shù)組
//開始復(fù)制原始數(shù)組的from_start元素
//并且將其復(fù)制至目標(biāo)數(shù)組的to_start中
function arraycopy(/array/from,/index/from_start,/array/to,/index/to_start,/integer/length){
//邏輯代碼
}
function easycopy(args){
arraycopy(args.from,args.from_start||0,args.to,args.to_start||0,args.length);
}
var a=[1,2,3,4],b=[];
easycopy({from:a,to:b,length:4});
實參類型
JS函數(shù)的形參并未聲明類型,在形參傳入函數(shù)體之前也未做任何類型檢查嘿歌。當(dāng)一個函數(shù)可以接收任意數(shù)量的實參時掸掏,可以使用省略號。
function max(/number.../){}
如果函數(shù)期望接收一個字符串實參宙帝,而調(diào)用函數(shù)時傳入其他類型值丧凤,所傳入的值會在函數(shù)體內(nèi)將其用做字符串的地方轉(zhuǎn)換為字符串類型。所有的原始類型都可以轉(zhuǎn)換為字符串步脓,所有的對象都包含toString()方法(盡管不一定有)愿待,所以不會報錯。
JS是一種靈活的弱類型語言靴患,有時適合編寫實參類型和實參個數(shù)不確定的函數(shù)仍侥。
//可以接收任意數(shù)量的實參,并可以遞歸地處理實參是數(shù)組的情況
//這個方法盡可能的在拋出異常之前將非數(shù)字轉(zhuǎn)換為數(shù)字
function flexisum(a){
var total=0;
for(var i=0;i<arguments.length;i++){
var element=arguments[i],n;
if(element==null){continue;} //忽略null和undefined實參
if(isArray(element)){n=flexisum.apply(this,element);} //如果是數(shù)組遞歸計算累加和
else if(typeof element=="function"){ //如果是函數(shù)
n=Number(element()); //調(diào)用它并做類型轉(zhuǎn)換
}
else {n=Number(element);} //否則直接做類型轉(zhuǎn)換
if(isNaN(n)){throw Error("flexisum():can't convert "+element+"to number")};
total+=n;
}
return total;
}
作為值的函數(shù)
可以將函數(shù)賦值給變量鸳君,存儲在對象的屬性或數(shù)組的元素中农渊,作為參數(shù)傳入另外一個函數(shù)。
fucntion square(x){return x*x;}
這個定義創(chuàng)建一個新的函數(shù)對象或颊,并將其賦值給變量square砸紊。函數(shù)的名字實際上是看不見的,它(square)僅僅是變量的名字囱挑,這個變量指代函數(shù)對象醉顽。函數(shù)還可以賦值給其他變量或賦值給對象的屬性。當(dāng)函數(shù)作為對象的屬性調(diào)用時看铆,函數(shù)就稱為方法。
fucntion square(x){return xx;}
var s=square; //square(4)=s(4)
var o={square:function(){return xx;}};
var y=o.square(16); // y=256
var a=[function(x){return x*x;},20];
a0; //400
自定義函數(shù)屬性
函數(shù)并不是原始值盛末,而是一種特殊的對象弹惦,也就是說,函數(shù)可以擁有屬性悄但。當(dāng)函數(shù)需要一個“靜態(tài)”變量來在調(diào)用時保持某個值不變棠隐,最方便的方法就是給函數(shù)定義屬性。
//由于函數(shù)聲明被提前了檐嚣,因此可以在函數(shù)聲明之前給它的成員賦值
uniqueInteger.counter=0;
//每次調(diào)用這個函數(shù)都會返回一個不同的整數(shù)
function uniqueInteger(){
return uniqueInteger.counter++; //先返回計數(shù)器的值助泽,然后計數(shù)器自增1
}
//第二個例子啰扛,計算階乘并將結(jié)果緩存至函數(shù)的屬性中
function factorial(n){
if(isFinite(n)&&n>0&&n==Math.round(n)){ //有限的正整數(shù)
if(!(n in factorial)){ //如果沒有緩存結(jié)果
factorial[n]=n*factorial(n-1); //計算結(jié)果并緩存
}
return factorial[n]; //返回緩存結(jié)果
}else{return NaN;} //如果輸入有誤
}
factorial[1]=1; //初始化緩存以保存這種基本情況
作為命名空間的函數(shù)
函數(shù)作用域的概念:在函數(shù)中聲明的變量在整個函數(shù)體內(nèi)都是可見的,在函數(shù)的外部是不可見的嗡贺。不在任何函數(shù)內(nèi)聲明的變量是全局變量隐解。在JS中是無法聲明只在一個代碼塊內(nèi)可見的變量的,基于這個原因诫睬,我們常常簡單的定義一個函數(shù)用做臨時的命名空間煞茫,在這個命名空間內(nèi)定義的變量不會污染全局環(huán)境。
閉包
函數(shù)的執(zhí)行依賴于變量作用域摄凡,這個作用域是在函數(shù)定義時決定的续徽,而不是函數(shù)調(diào)用時決定的。函數(shù)對象可以通過作用域鏈相互關(guān)聯(lián)起來亲澡,函數(shù)體內(nèi)的變量都可以保存在函數(shù)作用域內(nèi)钦扭,這種特性稱為閉包。
所有的函數(shù)都是閉包:它們都是對象床绪,它們都關(guān)聯(lián)到作用域鏈客情。定義大多數(shù)函數(shù)時的作用域鏈在調(diào)用函數(shù)時依然有效,但這并不影響閉包会涎。
var scope="global scope";
function checkscope(){
var scope="local scope";
function f(){return scope;}
return f();
}
checkscope(); //local scope
//改變一下代碼
function checkscope(){
var scope="local scope";
function f(){return scope;}
return f;
}
checkscope()(); //local scope
改動代碼后checkscope()現(xiàn)在僅僅返回函數(shù)內(nèi)嵌套的一個函數(shù)對象裹匙,而不是直接返回結(jié)果。函數(shù)的執(zhí)行用到了作用域鏈末秃,這個作用域鏈?zhǔn)呛瘮?shù)定義時創(chuàng)建的概页。嵌套的函數(shù)f()定義在這個作用域鏈里,其中的變量scope一定是局部變量练慕,不管在何時何地執(zhí)行f()惰匙,這種綁定在執(zhí)行f()時依然有效。因此返回local scope铃将。
var uniqueInteger=(function(){
var counter=0;
return function(){return counter++;}
}());
這段代碼定義了一個立即調(diào)用函數(shù)项鬼,因此是這個函數(shù)的返回值賦值給變量uniqueInteger。這個函數(shù)返回另外一個函數(shù)劲阎,這是一個嵌套的函數(shù)绘盟,我們將它賦值給變量uniqueInteger,嵌套的函數(shù)是可以訪問作用域內(nèi)的變量的悯仙,而且可以訪問外部函數(shù)中定義的counter變量龄毡。當(dāng)外部函數(shù)返回之后,其他任何代碼都無法訪問counter變量锡垄,只有內(nèi)部的函數(shù)可以訪問到它沦零。
像counter一樣的私有變量不是只能用在一個單獨的閉包內(nèi),在同一個外部函數(shù)內(nèi)定義的多個嵌套函數(shù)也可以訪問它货岭,這多個嵌套函數(shù)都共享一個作用域鏈路操。
function counter(){
var n=0;
return {
count:function(){return n++;}
reset:function(){n=0;}
};
}
var c=counter(),d=counter();
c.count(); //0
d.count(); //0
c.reset(); //reset()和count()方法共享狀態(tài)
c.count(); //0
d.count(); //1
reset()方法和count()方法都可以訪問私有變量n疾渴。每次調(diào)用counter()都會創(chuàng)建一個新的作用域鏈和一個新的私有變量。因此如果調(diào)用counter()兩次屯仗,則會得到兩個計數(shù)器對象搞坝,而且彼此包含不同的私有變量,調(diào)用其中一個計數(shù)器對象的count()或reset()不會影響到另外一個對象祭钉。
函數(shù)屬性瞄沙、方法和構(gòu)造函數(shù)
length屬性
在函數(shù)體里,arguments.length表示傳入函數(shù)的實參的個數(shù)慌核。函數(shù)的length屬性是只讀屬性距境,它代表函數(shù)實參的數(shù)量,這里的參數(shù)指的是“形參”而非“實參”垮卓,也就是在函數(shù)定義時給出的實參個數(shù)垫桂,通常也是在函數(shù)調(diào)用時期望傳入函數(shù)的實參個數(shù)。
//這個函數(shù)使用arguments.callee粟按,因此它不能在嚴(yán)格模式下工作
function check(args){
var actual=args.length; //實參的真實個數(shù)
var expected=args.callee.length; //期望的實參個數(shù)
if(actual!==expected){
throw Error("Expected"+expected+"args;got"+actual);
}
}
function f(x,y,z){
check(arguments);
return x+y+z;
}
prototype屬性
每一個函數(shù)都包含一個prototype屬性诬滩,這個屬性是指向一個對象的引用,這個對象稱做“原型對象”灭将。每一個函數(shù)都包含不同的原型對象疼鸟。當(dāng)將函數(shù)用做構(gòu)造函數(shù)的時候,新創(chuàng)建的對象會從原型對象上繼承屬性庙曙。
call()方法和apply()方法
我們可以將call()和apply()看做是某個對象的方法空镜,通過調(diào)用方法的形式來間接調(diào)用函數(shù)。call()和apply()的第一個實參是要調(diào)用函數(shù)的母對象捌朴,它是調(diào)用上下文吴攒,在函數(shù)體內(nèi)通過this來獲得對它的引用。要想以對象o的方法來調(diào)用函數(shù)f()砂蔽,可以像下面這樣:
f.call(o);
f.apply(o);
//每行代碼和下面的代碼功能類似
//假設(shè)對象o中預(yù)先不存在名為m的屬性
o.m=f; //將f存儲為o的臨時方法
o.m();
delete o.m;
在ES5的嚴(yán)格模式中洼怔,call()和apply()的第一個實參都會變?yōu)閠his的值,哪怕傳入的實參是原始值甚至是null或undefined左驾。在ES3和非嚴(yán)格模式中镣隶,傳入的null和undefined都會被全局對象替代,而其他原始值則會被相應(yīng)的包裝對象所替代诡右。
對于call()來說安岂,第一個調(diào)用上下文實參之后的所有實參就是要傳入待調(diào)用函數(shù)的值。
//以對象o
的方法的形式調(diào)用函數(shù)f
稻爬,并傳入兩個參數(shù)
f.call(o,1,2);
apply()方法傳入的實參都放入一個數(shù)組中嗜闻。
f.apply(o,[1,2])
如果一個函數(shù)的實參可以是任意數(shù)量蜕依,給apply()傳入的參數(shù)數(shù)組可以是任意長度的桅锄。
var biggest=Math.max.apply(Math,array_of_numbers);
傳入apply()的參數(shù)數(shù)組可以是類數(shù)組對象也可以是真實數(shù)組琉雳。可以將當(dāng)前函數(shù)的arguments數(shù)組直接傳入(另一個函數(shù)的)apply()來調(diào)用另一個函數(shù)友瘤。
//將對象o中名為m()的方法替換為另一個方法
//可以在調(diào)用原始的方法之前和之后記錄日志消息
function trace(o,m){
var original=o[m]; //在閉包中保存原始方法
o[m]=function(){
console.log(new Date(),"entering:",m); //輸出日志消息
var result=original.apply(this,arguments); //調(diào)用原始函數(shù)
console.log(new Date(),"exiting:",m);
return result;
}
}
bind()方法
bind()是ES5中新增的方法翠肘。這個方法主要作用是將函數(shù)綁定至某個對象。當(dāng)在函數(shù)f()上調(diào)用bind()方法并傳入一個對象o作為參數(shù)辫秧,這個方法將返回一個新的函數(shù)束倍。(以函數(shù)調(diào)用的方式)調(diào)用新的函數(shù)將會把原始的函數(shù)f()當(dāng)做o的方法來調(diào)用。傳入新函數(shù)的任何實參都將傳入原始函數(shù)盟戏。
function f(y){return this.x+y;} // 待綁定的函數(shù)
var o={x:1}; // 將要綁定的對象
var g=f.bind(o); // 通過調(diào)用g(x)來調(diào)用o.f(x)
//g(2) =>3
可以通過如下代碼實現(xiàn)這種綁定绪妹。
//返回一個函數(shù),通過調(diào)用它來調(diào)用o
中的方法f()
乡洼,傳遞它所有的實參
function bind(f,o){
if(f.bind) {return f.bind(o);} //如果bind()方法存在,使用bind()方法
else return function(){
return f.apply(o,arguments);
}
}
ES5中的bind()方法不僅僅是將函數(shù)綁定至一個對象津坑,它還附帶一些其它應(yīng)用:除了第一個實參之外累舷,傳入bind()的實參也會綁定至this,這個附帶的應(yīng)用是一種常見的函數(shù)式編程技術(shù)婶肩,有時也被稱為“柯里化”。
var sum=function(x,y){return x+y;}
//創(chuàng)建一個類似sum的新函數(shù)貌夕,但this的值綁定到null
//并且第一個參數(shù)綁定到1律歼,這個新的函數(shù)期望只傳入一個實參
var succ=sum.bind(null,1);
succ(2); //3,x綁定到1,并傳入2作為實參y
function f(y,z){return this.x+y+z;};
var g=f.bind({x:1,2}); //g(3)=6,this.x綁定到1,y綁定到2,z綁定到3
bind()方法返回一個函數(shù)對象啡专,這個函數(shù)對象的length屬性是綁定函數(shù)的形參個數(shù)減去綁定實參的個數(shù)险毁。bind()方法可以順帶用作構(gòu)造函數(shù),如果bind()返回的函數(shù)用作構(gòu)造函數(shù)植旧,將忽略傳入bind()的this辱揭,原始函數(shù)就會以構(gòu)造函數(shù)的形式調(diào)用,其實參也已經(jīng)綁定病附。由bind()方法所返回的函數(shù)并不包含prototype屬性问窃,并且將這些綁定的函數(shù)用作構(gòu)造函數(shù)時所創(chuàng)建的對象從原始的未綁定的構(gòu)造函數(shù)中繼承prototype。同樣完沪,在使用instanceof運算符時域庇,綁定構(gòu)造函數(shù)和未綁定構(gòu)造函數(shù)并無兩樣。
toString()方法
函數(shù)的toString()方法返回一個字符串覆积,這個字符串和函數(shù)聲明語句的語法有關(guān)听皿。實際上,大多數(shù)的toString()方法的實現(xiàn)都返回函數(shù)的完整源碼宽档。內(nèi)置函數(shù)往往返回一個類似[native code]的字符串作為函數(shù)體尉姨。
Function()構(gòu)造函數(shù)
除了函數(shù)聲明和函數(shù)表達(dá)式,函數(shù)還可以通過Function()構(gòu)造函數(shù)來定義吗冤。
var f=new Function("x","y","return x*y;");
Function()構(gòu)造函數(shù)可以傳入任意數(shù)量的字符串實參又厉,最后一個實參所表示的文本就是函數(shù)體九府,它可以包含任意的JS語句,每兩條語句之間用分號分隔覆致。傳入構(gòu)造函數(shù)的其他所有的實參字符串是指定函數(shù)的形參名字的字符串侄旬。如果定義的函數(shù)不包含任何參數(shù),只需給構(gòu)造函數(shù)傳入一個字符串——函數(shù)體即可煌妈。
Function()構(gòu)造函數(shù)并不需要通過傳入實參以指定函數(shù)名儡羔。就像函數(shù)字面量一樣,F(xiàn)unction()構(gòu)造函數(shù)創(chuàng)建一個匿名函數(shù)璧诵。
關(guān)于Function()構(gòu)造函數(shù)有幾點需要注意:
Function()構(gòu)造函數(shù)允許JS在運行時動態(tài)的創(chuàng)建并編譯函數(shù)汰蜘。
每次調(diào)用Function()構(gòu)造函數(shù)都會解析函數(shù)體,并創(chuàng)建新的函數(shù)對象之宿。如果是在一個循環(huán)或者多次調(diào)用的函數(shù)中執(zhí)行這個構(gòu)造函數(shù)鉴扫,執(zhí)行效率會受影響。相比之下澈缺,循環(huán)中的嵌套函數(shù)和函數(shù)定義表達(dá)式則不會每次執(zhí)行時都重新編譯坪创。
Function()構(gòu)造函數(shù)創(chuàng)建的函數(shù)并不是使用詞法作用域,相反姐赡,函數(shù)體代碼的編譯總是會在頂層函數(shù)執(zhí)行莱预。
var socpe="global";
function constructFunction(){
var scope="local";
return new Function("return scope"); //無法捕獲局部作用域
}
constructFunction()(); //global