讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議60:比較函數調用模式 >

建議60:比較函數調用模式

在JavaScript中,函數就是對象,對象是「名/值」對的集合,並擁有一個到原型對象的隱藏連接。對像字面量產生的對象連接到object.prototype,函數對像連接到Function.prototype,該原型對像本身連接到object.prototype。每個函數在創建時都有兩個附加的隱藏屬性:函數的上下文和實現函數行為的代碼。

每個函數對像在創建時也隨之帶一個prototype屬性,它的值是一個擁有constructor屬性且constructor屬性值為該函數的對象。這和隱藏連接到Function.prototype完全不同。這個令人費解的構造過程的意義將會在後面的章節中揭示。

作為對象,函數與其他值一樣可以在任何變量的位置使用。函數可以作為數組元素,可以作為函數的返回值,可以作為對象的成員值,也可以作為表達式參與運算,同時函數也可以擁有自己的方法。

調用一個函數將暫停當前函數的執行,傳遞控制權和參數給新函數。除了聲明時定義的形式參數,每個函數接收兩個附加的參數:this和arguments。參數this在面向對像編程中非常重要,它的值取決於調用的模式。在JavaScript中一共有4種調用模式:方法調用模式、函數調用模式、構造器調用模式和apply調用模式。這些模式在如何初始化關鍵參數this上存在差異。

調用運算符是跟在任何產生一個函數值的表達式之後的一對圓括號,圓括號內可以包含零個或多個用逗號隔開的表達式。每個表達式產生一個參數值。每個參數值被賦予函數聲明時定義的形式參數名。當實際參數(arguments)的個數與形式參數(parameters)的個數不匹配時不會導致運行時錯誤。如果實際參數值過多,那麼超出的參數值將被忽略。如果實際參數值過少,那麼缺失的值將會被替換為undefined。不會對參數值進行類型檢查,任何類型的值都可以傳遞給參數。

(1)方法調用模式

當一個函數被保存為對象的一個屬性時,將稱為一個方法。當一個方法被調用時,this被綁定到該對象。如果一個調用表達式包含一個屬性存取表達式(即一個點表達式或[subscript]下標表達式),那麼它被當做一個方法來調用。


var obj={

value:0,

increment:function(inc){

this.value+=typeof inc===\'number\'?inc:1;

}

}

obj.increment;

document.writeln(obj.value);//1

obj.increment(2);

document.writeln(obj.value);//3


在上面代碼中創建了obj對象,它有一個value屬性和一個increment方法。increment方法接受一個可選的參數,如果該參數不是數字,那麼默認使用數字1。

由於increment方法可以使用this去訪問對象,所以它能從對像中取值或修改該對象。this到對象的綁定發生在調用的時候。這個延遲綁定使函數可以對this高度復用。通過this可取得increment方法所屬對象的上下文的方法稱為公共方法。

(2)函數調用模式

當一個函數並非一個對象的屬性時,它將被當做一個函數來調用:


var sum=add(3,4);//7


當函數以此模式調用時,this被綁定到全局對象。這是語言設計上的一個錯誤。倘若語言設計正確,當內部函數被調用時,this應該仍綁定到外部函數的this變量。這個設計錯誤的後果是方法不能利用內部函數來幫助它工作,因為內部函數的this被綁定了錯誤的值,所以不能共享該方法對對象的訪問權。幸運的是,有一個很容易的解決方案:如果該方法定義一個變量並將它賦值為this,那麼內部函數就可以通過這個變量訪問this。按照約定,將這個變量命名為that。


var obj={

value:1,

doub:function{

var that=this;

var helper=function{

that.value=that.value*2;

};

helper;

}

}

obj.doub;

document.writeln(obj.value);//2


(3)構造器調用模式

JavaScript是一門基於原型繼承的語言,該語言是無類別的,對象可以直接從其他對像繼承屬性。當今大多數語言都是基於類的語言,雖然原型繼承有著強大的表現力,但它偏離了主流用法,並不被廣泛理解。JavaScript為了能夠兼容基於類語言的編寫風格,提供了一套基於類似類語言的對象構建語法。

如果在一個函數前面加上new運算符來進行調用,那麼將創建一個隱藏鏈接到該函數的prototype原型對象的新實例對象,同時this將會被綁定到這個新實例對像上。注意,new前綴也會改變return語句的行為。


var F=function(string){

this.status=string;

};

F.prototype.get=function{

return this.status;

};

var f=new F(\"new object\");

document.writeln(f.get);//\"new object\"


上面代碼創建一個名為F的構速器函數,此函數構建了一個帶有status屬性的對象。然後,為F所有實例提供一個名為get的公共方法。最後,創建一個實例對象,並調用get方法,以讀取status屬性的值。

結合new前綴調用的函數稱為構造器函數。按照約定,構造器函數應該保存在以大寫字母命名的變量中。如果調用構造器函數時沒有在前面加上new,可能會發生非常糟糕的事情,既沒有編譯時警告,也沒有運行時警告,所以大寫約定非常重要。

(4)apply調用模式

JavaScript是函數式的面向對像編程語言,函數可以擁有方法。apply就是函數的一個基本方法,使用這個方法可以調用函數,並修改函數體內的this值。apply方法包括兩個參數:第一個參數設置綁定給this的值;第二個參數是包含函數參數的數組。例如:


var array=[5,4];

var add=function{

var i,sum=0;

for(i=0;i<arguments.length;i+=1){

sum+=arguments[i];

}

return sum;

};

var sum=add.apply({},array);//9


上面代碼構建一個包含兩個數字的數組,然後使用apply方法調用add函數,將數組array中的元素值相加。


var F=function(string){

this.status=string;

};

F.prototype.get=function{

return this.status;

};

var obj={

status:\'obj\'

};

var status=F.prototype.get.apply(obj);//\'\'obj\'\'


上面代碼構建了一個構造函數F,為該函數定義了一個原型方法get,該方法能夠讀取當前對象的status屬性的值。然後定義一個obj對象,該對像包含一個status屬性,使用apply方法在obj對像上調用構造函數F的get方法,返回obj對象的status屬性值。