函數(shù)

函數(shù)只定義一次谷扣,但可能被執(zhí)行或調(diào)用任意次。JS函數(shù)是參數(shù)化的捎琐,函數(shù)的定義會包括一個稱為形參的標(biāo)識符列表会涎,這些參數(shù)在函數(shù)體中像局部變量一樣工作。函數(shù)調(diào)用會為形參提供實參的值瑞凑。函數(shù)使用它們實參的值來計算返回值末秃,成為該函數(shù)調(diào)用表達式的值。除了實參之外拨黔,每次調(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ù)定義表達式或函數(shù)聲明語句里。在這兩種形式中丈牢,函數(shù)定義都從function關(guān)鍵字開始祭钉,其后跟隨這些組成部分:

函數(shù)名稱標(biāo)識符。新定義的函數(shù)對象會賦值給這個變量己沛。對函數(shù)定義表達式來說慌核,這個名字是可選的:如果存在,該名字只存在于函數(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ù)表達式定義了一個函數(shù)用來求傳入?yún)?shù)的平方
var square=function(x){return xx;}
//函數(shù)表達式可以包含名稱,這在遞歸時很有用
var f=function fact(x){
if(x<=1){return 1;}else{ return x
fact(x-1);}
};
//函數(shù)表達式也可以作為參數(shù)傳給其它參數(shù)
data.sort(function(a,b){return a-b;});
//函數(shù)表達式有時定義后立即調(diào)用
var tansquared=(function(x){return x*x;}(10))
以表達式方式定義的函數(shù)忆蚀,函數(shù)的名稱是可選的矾利。一條函數(shù)聲明語句實際上聲明了一個變量,并把一個函數(shù)對象賦值給它馋袜。定義函數(shù)表達式時并沒有聲明一個變量男旗。如果一個函數(shù)定義表達式包含名稱,函數(shù)的局部作用域?qū)粋€綁定到函數(shù)對象的名稱欣鳖。實際上察皇,函數(shù)的名稱將成為函數(shù)內(nèi)部的一個局部變量。
函數(shù)聲明語句被提前到外部腳本或外部函數(shù)作用域的頂部泽台,所以以這種方式聲明的函數(shù)什荣,可以被在它定義之前出現(xiàn)的代碼調(diào)用矾缓。以表達式方式定義的函數(shù)在定義之前無法調(diào)用,因為變量的聲明雖然提前了稻爬,但給變量賦值是不會提前的嗜闻。
return語句導(dǎo)致函數(shù)停止執(zhí)行,并返回它的表達式的值給調(diào)用者因篇。如果return語句沒有一個與之相關(guān)的表達式泞辐,則它返回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ù)定義表達式可以出現(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)用表達式可以進行普通的函數(shù)調(diào)用也可進行方法調(diào)用。一個調(diào)用表達式由多個函數(shù)表達式組成懂诗,每個函數(shù)表達式都是由一個函數(shù)對象和左圓括號蜂嗽、參數(shù)列表和右圓括號組成,參數(shù)列表是由逗號分隔的0個或多個參數(shù)表達式組成殃恒。如果函數(shù)表達式是一個屬性訪問表達式植旧,即該函數(shù)是一個對象的屬性或數(shù)組中的一個元素,那么它就是一個方法調(diào)用表達式离唐。

printprops({x:1});
var total=distance(0,0,2,1)+distance(2,1,3,5);
var probability=factorial(5)/factorial(13);
在一次調(diào)用中病附,每個參數(shù)表達式都會計算出一個值,計算的結(jié)果作為參數(shù)傳遞給另外一個函數(shù)。這些值作為實參傳遞給聲明函數(shù)時定義的形參食寡。在函數(shù)體中存在一個形參的引用凸舵,指向當(dāng)前傳入的實參列表崎逃,通過它可以獲得參數(shù)的值丽焊。
對于普通的函數(shù)調(diào)用较剃,函數(shù)的返回值成為調(diào)用表達式的值。如果該函數(shù)返回是因為解釋器到達結(jié)尾技健,返回值就是undefined写穴。如果函數(shù)返回是因為解釋器執(zhí)行到一條return語句,返回值就是return之后的表達式的值雌贱,如果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)用上下文腮猖。屬性訪問表達式由兩部分組成:一個對象(o)和屬性名稱(m)。在像這樣的方法調(diào)用表達式里赞枕,對象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)用使用點符號來訪問屬性鹦赎,使用方括號也可以進行屬性訪問操作谍椅。

o"m"; //o.m(x,y)的另一種寫法
a0; //假設(shè)a[0]是一個函數(shù)
方法調(diào)用可能包括更復(fù)雜的屬性訪問表達式:

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ù)將基于一個對象進行操作杖们。

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)包含一組實參列表泉懦,先計算這些實參表達式稿黍,然后傳入函數(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)用上下文。也就是說缨睡,在表達式new o.m()中鸟悴,調(diào)用上下文并不是o。
構(gòu)造函數(shù)通常不使用return關(guān)鍵字奖年,它們通常初始化新對象细诸,當(dāng)構(gòu)造函數(shù)的函數(shù)體執(zhí)行完畢時,它會顯式返回陋守。在這種情況下震贵,構(gòu)造函數(shù)調(diào)用表達式的計算結(jié)果就是這個新對象的值。如果構(gòu)造函數(shù)顯式地使用return語句返回一個對象水评,那么調(diào)用表達式的值就是這個對象猩系。如果構(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/來強調(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 x
x;}};
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ù)表達式蚕脏,函數(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ù)定義表達式則不會每次執(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奥额,隨后出現(xiàn)的幾起案子苫幢,更是在濱河造成了極大的恐慌,老刑警劉巖垫挨,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韩肝,死亡現(xiàn)場離奇詭異,居然都是意外死亡九榔,警方通過查閱死者的電腦和手機哀峻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哲泊,“玉大人剩蟀,你說我怎么就攤上這事∏型” “怎么了育特?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我缰冤,道長犬缨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任棉浸,我火速辦了婚禮怀薛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迷郑。我一直安慰自己枝恋,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布嗡害。 她就那樣靜靜地躺著焚碌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪就漾。 梳的紋絲不亂的頭發(fā)上呐能,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音抑堡,去河邊找鬼摆出。 笑死,一個胖子當(dāng)著我的面吹牛首妖,可吹牛的內(nèi)容都是我干的偎漫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼有缆,長吁一口氣:“原來是場噩夢啊……” “哼象踊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起棚壁,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤杯矩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袖外,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體史隆,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年曼验,在試婚紗的時候發(fā)現(xiàn)自己被綠了泌射。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鬓照,死狀恐怖熔酷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豺裆,我是刑警寧澤拒秘,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響翼抠,放射性物質(zhì)發(fā)生泄漏咙轩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一阴颖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丐膝,春花似錦量愧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至浑此,卻和暖如春累颂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凛俱。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工紊馏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒲犬。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓朱监,卻偏偏與公主長得像,于是被迫代替她去往敵國和親原叮。 傳聞我的和親對象是個殘疾皇子赫编,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念奋隶。通過函數(shù)可以封裝任意多條語句擂送,而且...
    道無虛閱讀 4,573評論 0 5
  • 函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)還能接受輸入的參數(shù)唯欣,不同的參數(shù)會返回不同的值嘹吨。 概述 函數(shù)的聲明 JavaSc...
    oWSQo閱讀 1,265評論 0 4
  • 在JavaScript中,函數(shù)即對象黍聂,程序可以隨意操控它們躺苦。比如,JavaScript可以把函數(shù)賦值給變量产还,或者作...
    kissLife閱讀 920評論 0 0
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,831評論 0 38
  • 早上好 以前看到別人那么優(yōu)秀那么棒匹厘,特別羨慕,有時候自己也想去努力去追做到那樣脐区,卻發(fā)現(xiàn)努力不是那么容易愈诚,堅持就更...
    佛系少女03閱讀 353評論 2 1