函數(shù)

函數(shù)只定義一次,但可能被執(zhí)行或調(diào)用任意次。JS函數(shù)是參數(shù)化的,函數(shù)的定義會包括一個稱為形參的標(biāo)識符列表,這些參數(shù)在函數(shù)體中像局部變量一樣工作。函數(shù)調(diào)用會為形參提供實(shí)參的值。函數(shù)使用它們實(shí)參的值來計(jì)算返回值,成為該函數(shù)調(diào)用表達(dá)式的值。除了實(shí)參之外,每次調(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ù)。因?yàn)楹瘮?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 x
fact(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ù)對象賦值給它。定義函數(shù)表達(dá)式時并沒有聲明一個變量。如果一個函數(shù)定義表達(dá)式包含名稱,函數(shù)的局部作用域?qū)粋€綁定到函數(shù)對象的名稱。實(shí)際上,函數(shù)的名稱將成為函數(shù)內(nèi)部的一個局部變量。
函數(shù)聲明語句被提前到外部腳本或外部函數(shù)作用域的頂部,所以以這種方式聲明的函數(shù),可以被在它定義之前出現(xiàn)的代碼調(diào)用。以表達(dá)式方式定義的函數(shù)在定義之前無法調(diào)用,因?yàn)樽兞康穆暶麟m然提前了,但給變量賦值是不會提前的。
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á)式都會計(jì)算出一個值,計(jì)算的結(jié)果作為參數(shù)傳遞給另外一個函數(shù)。這些值作為實(shí)參傳遞給聲明函數(shù)時定義的形參。在函數(shù)體中存在一個形參的引用,指向當(dāng)前傳入的實(shí)參列表,通過它可以獲得參數(shù)的值。
對于普通的函數(shù)調(diào)用,函數(shù)的返回值成為調(diào)用表達(dá)式的值。如果該函數(shù)返回是因?yàn)榻忉屍鞯竭_(dá)結(jié)尾,返回值就是undefined。如果函數(shù)返回是因?yàn)榻忉屍鲌?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)行時是否為嚴(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)用使用點(diǎn)符號來訪問屬性,使用方括號也可以進(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)用實(shí)際上都會傳入一個隱式的實(shí)參。這個實(shí)參是一個對象,方法調(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)包含一組實(shí)參列表,先計(jì)算這些實(shí)參表達(dá)式,然后傳入函數(shù)內(nèi),這和函數(shù)調(diào)用和方法調(diào)用是一致的。但如果構(gòu)造函數(shù)沒有形參,構(gòu)造函數(shù)調(diào)用的語法允許省略實(shí)參列表和圓括號。凡是沒有形參的構(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á)式的計(jì)算結(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)用的實(shí)參。call()方法使用它自有的實(shí)參列表作為函數(shù)的實(shí)參。apply()方法則要求以數(shù)組的形式傳入?yún)?shù)。

函數(shù)的實(shí)參和形參

函數(shù)定義并未指定函數(shù)形參的類型,函數(shù)調(diào)用也未對傳入的實(shí)參值做任何類型的檢查。函數(shù)調(diào)用甚至不檢查傳入形參的個數(shù)。

可選形參

當(dāng)調(diào)用函數(shù)的時候傳入的實(shí)參比函數(shù)聲明時指定的形參個數(shù)要少,剩下的形參都將設(shè)置為undefined值。因此在調(diào)用函數(shù)時形參是否可選以及是否可以省略應(yīng)當(dāng)保持較好的適應(yīng)性。為做到這一點(diǎn),應(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個實(shí)參
var a=getPropertyNames(o); //將o的屬性存儲到一個新數(shù)組中
getPropertyNames(p,a); //將p的屬性追加至數(shù)組a中
當(dāng)用這種可選實(shí)參來實(shí)現(xiàn)函數(shù)時,需要將可選實(shí)參放在實(shí)參列表的最后。調(diào)用函數(shù)是沒辦法省略第一個實(shí)參并傳入第二個實(shí)參的,它必須將undefined作為第一個實(shí)參顯式傳入。在函數(shù)定義中使用/optional/來強(qiáng)調(diào)形參是可選的。

可變長的實(shí)參列表:實(shí)參對象

當(dāng)調(diào)用函數(shù)的時候傳入的實(shí)參個數(shù)超過函數(shù)定義時的形參個數(shù)時,沒有辦法直接獲得未命名值的引用。參數(shù)對象解決了這個問題。在函數(shù)體中,標(biāo)識符arguments是指向?qū)崊ο蟮囊茫瑢?shí)參對象是一個類數(shù)組對象,這樣可以通過數(shù)字下標(biāo)就能訪問傳入函數(shù)的實(shí)參值,而不用非要通過名字來得到實(shí)參。
假設(shè)定義了函數(shù)f,它的實(shí)參只有一個x。如果調(diào)用這個函數(shù)時傳入兩個實(shí)參,第一個實(shí)參可以通過參數(shù)名x來獲得,也可以通過arguments[0]來得到。第二個實(shí)參只能通過arguments[1]來得到。arguments也包含一個length屬性。,用以標(biāo)識其所包含元素的個數(shù)。

function f(x,y,z){
//首先驗(yàn)證傳入實(shí)參的個數(shù)是否正確
if(arguments.length!=3){
throw new Error("function f called with"+arguments.length+"arguments")
}
}
省略的實(shí)參都將是undefined,多出的參數(shù)會自動省略。
實(shí)參對象有一個重要的用處,就是讓函數(shù)可以操作任意數(shù)量的實(shí)參。

function max(/.../){
var max=Number.NEGATIVE_INFINITY;
//遍歷實(shí)參,查找并記住最大值
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í)參函數(shù)的實(shí)參個數(shù)不能為0,arguments[]對象最適合在這樣一類函數(shù)中,這類函數(shù)包含固定個數(shù)的命名和必須參數(shù),以及隨后個數(shù)不定的可選實(shí)參。
arguments并不是真正的數(shù)組,它是一個實(shí)參對象。每個實(shí)參對象都包含以數(shù)字為索引的一組元素以及l(fā)ength屬性。
數(shù)組對象包含一個特性。在非嚴(yán)格模式下,當(dāng)一個函數(shù)包含若干形參,實(shí)參對象的數(shù)組元素是函數(shù)形參所對應(yīng)實(shí)參的別名,實(shí)參對象中以數(shù)字索引,并且形參名稱可以認(rèn)為是相同變量的不同命名。通過實(shí)參名字來修改實(shí)參值的話,通過arguments[]數(shù)組也可以獲取到更改后的值。

function f(x){
console.log(x); //輸出實(shí)參的初始值
arguments[0]=null; //修改實(shí)參數(shù)組的元素同樣會修改x的值
console.log(x); //輸出null
}
在ES5中移除了實(shí)參對象的這個特性。在非嚴(yán)格模式中,函數(shù)里的arguments僅僅是一個標(biāo)識符,在嚴(yán)格模式中,它變成了一個保留字。嚴(yán)格模式中的函數(shù)無法使用arguments作為形參名或局部變量名,也不能給arguments賦值。

callee和caller屬性

除了數(shù)組元素,實(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);
};
將對象屬性用做實(shí)參

當(dāng)一個函數(shù)包含超過三個形參時,最好通過名/值對的形式來傳入?yún)?shù),這樣參數(shù)的順序就無關(guān)緊要了。為了實(shí)現(xiàn)這種調(diào)用,定義函數(shù)的時候,傳入的實(shí)參都寫入一個對象中,在調(diào)用的時候傳入一個對象,對象中的名/值對是真正需要的實(shí)參數(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});
實(shí)參類型

JS函數(shù)的形參并未聲明類型,在形參傳入函數(shù)體之前也未做任何類型檢查。當(dāng)一個函數(shù)可以接收任意數(shù)量的實(shí)參時,可以使用省略號。

function max(/number.../){}
如果函數(shù)期望接收一個字符串實(shí)參,而調(diào)用函數(shù)時傳入其他類型值,所傳入的值會在函數(shù)體內(nèi)將其用做字符串的地方轉(zhuǎn)換為字符串類型。所有的原始類型都可以轉(zhuǎn)換為字符串,所有的對象都包含toString()方法(盡管不一定有),所以不會報錯。
JS是一種靈活的弱類型語言,有時適合編寫實(shí)參類型和實(shí)參個數(shù)不確定的函數(shù)。

//可以接收任意數(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實(shí)參
if(isArray(element)){n=flexisum.apply(this,element);} //如果是數(shù)組遞歸計(jì)算累加和
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ù)的名字實(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++; //先返回計(jì)數(shù)器的值,然后計(jì)數(shù)器自增1
}
//第二個例子,計(jì)算階乘并將結(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); //計(jì)算結(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一樣的私有變量不是只能用在一個單獨(dú)的閉包內(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()兩次,則會得到兩個計(jì)數(shù)器對象,而且彼此包含不同的私有變量,調(diào)用其中一個計(jì)數(shù)器對象的count()或reset()不會影響到另外一個對象。

函數(shù)屬性、方法和構(gòu)造函數(shù)

length屬性

在函數(shù)體里,arguments.length表示傳入函數(shù)的實(shí)參的個數(shù)。函數(shù)的length屬性是只讀屬性,它代表函數(shù)實(shí)參的數(shù)量,這里的參數(shù)指的是“形參”而非“實(shí)參”,也就是在函數(shù)定義時給出的實(shí)參個數(shù),通常也是在函數(shù)調(diào)用時期望傳入函數(shù)的實(shí)參個數(shù)。

//這個函數(shù)使用arguments.callee,因此它不能在嚴(yán)格模式下工作
function check(args){
var actual=args.length; //實(shí)參的真實(shí)個數(shù)
var expected=args.callee.length; //期望的實(shí)參個數(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()的第一個實(shí)參是要調(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()的第一個實(shí)參都會變?yōu)閠his的值,哪怕傳入的實(shí)參是原始值甚至是null或undefined。在ES3和非嚴(yán)格模式中,傳入的null和undefined都會被全局對象替代,而其他原始值則會被相應(yīng)的包裝對象所替代。
對于call()來說,第一個調(diào)用上下文實(shí)參之后的所有實(shí)參就是要傳入待調(diào)用函數(shù)的值。

//以對象o的方法的形式調(diào)用函數(shù)f,并傳入兩個參數(shù)
f.call(o,1,2);
apply()方法傳入的實(shí)參都放入一個數(shù)組中。

f.apply(o,[1,2])
如果一個函數(shù)的實(shí)參可以是任意數(shù)量,給apply()傳入的參數(shù)數(shù)組可以是任意長度的。

var biggest=Math.max.apply(Math,array_of_numbers);
傳入apply()的參數(shù)數(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í)參都將傳入原始函數(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
可以通過如下代碼實(shí)現(xiàn)這種綁定。

//返回一個函數(shù),通過調(diào)用它來調(diào)用o中的方法f(),傳遞它所有的實(shí)參
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)用:除了第一個實(shí)參之外,傳入bind()的實(shí)參也會綁定至this,這個附帶的應(yīng)用是一種常見的函數(shù)式編程技術(shù),有時也被稱為“柯里化”。

var sum=function(x,y){return x+y;}
//創(chuàng)建一個類似sum的新函數(shù),但this的值綁定到null
//并且第一個參數(shù)綁定到1,這個新的函數(shù)期望只傳入一個實(shí)參
var succ=sum.bind(null,1);
succ(2); //3,x綁定到1,并傳入2作為實(shí)參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í)參的個數(shù)。bind()方法可以順帶用作構(gòu)造函數(shù),如果bind()返回的函數(shù)用作構(gòu)造函數(shù),將忽略傳入bind()的this,原始函數(shù)就會以構(gòu)造函數(shù)的形式調(diào)用,其實(shí)參也已經(jīng)綁定。由bind()方法所返回的函數(shù)并不包含prototype屬性,并且將這些綁定的函數(shù)用作構(gòu)造函數(shù)時所創(chuàng)建的對象從原始的未綁定的構(gòu)造函數(shù)中繼承prototype。同樣,在使用instanceof運(yùn)算符時,綁定構(gòu)造函數(shù)和未綁定構(gòu)造函數(shù)并無兩樣。

toString()方法

函數(shù)的toString()方法返回一個字符串,這個字符串和函數(shù)聲明語句的語法有關(guān)。實(shí)際上,大多數(shù)的toString()方法的實(shí)現(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í)參,最后一個實(shí)參所表示的文本就是函數(shù)體,它可以包含任意的JS語句,每兩條語句之間用分號分隔。傳入構(gòu)造函數(shù)的其他所有的實(shí)參字符串是指定函數(shù)的形參名字的字符串。如果定義的函數(shù)不包含任何參數(shù),只需給構(gòu)造函數(shù)傳入一個字符串——函數(shù)體即可。
Function()構(gòu)造函數(shù)并不需要通過傳入實(shí)參以指定函數(shù)名。就像函數(shù)字面量一樣,F(xiàn)unction()構(gòu)造函數(shù)創(chuàng)建一個匿名函數(shù)。
關(guān)于Function()構(gòu)造函數(shù)有幾點(diǎn)需要注意:

Function()構(gòu)造函數(shù)允許JS在運(yùn)行時動態(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容