函數(shù)
函數(shù)的一些特征
- 函數(shù)形參類似函數(shù)內(nèi)的局部變量
- 函數(shù)沒有return語句就返回undefined
- 除了函數(shù)實(shí)參之外,每次調(diào)用還會(huì)有另一個(gè)值——本次調(diào)用的上下文——就是this關(guān)鍵字的值
如果函數(shù)掛載在一個(gè)對(duì)象上,作為對(duì)象的一個(gè)屬性,就稱它為對(duì)象的方法。當(dāng)通過這個(gè)對(duì)象來調(diào)用函數(shù)時(shí),該對(duì)象就是此次調(diào)用的上下文(context),也就是該函數(shù)的this的值。用于初始化一個(gè)新創(chuàng)建的對(duì)象的函數(shù)稱為構(gòu)造函數(shù)(constructor)。
在javascript里,==函數(shù)即對(duì)象==,程序可以隨意操控它們。比如,javascript可以把函數(shù)賦值給變量,或者作為參數(shù)傳遞給其他函數(shù)。因?yàn)楹瘮?shù)就是對(duì)象,所以可以給他們?cè)O(shè)置屬性,甚至調(diào)用他們的方法。
javascript的函數(shù)可以嵌套在其他函數(shù)中定義,這樣他們就可以訪問它們被定義時(shí)所處的作用域中的任何變量。這意味著javascript函數(shù)構(gòu)成了一個(gè)==閉包(closure)==,它給javascript帶來了非常強(qiáng)勁的編程能力。
展示一個(gè)小例子,閉包demo:
var Counter=(function(){
//賦初值
var count=0;
//外部調(diào)用時(shí)形成閉包
function f(){
return ++count;
}
return f;
})();
Counter();//1
Counter();//2
Counter();//3
函數(shù)定義
定義函數(shù)的兩種方式:==函數(shù)聲明==和==函數(shù)表達(dá)式==,下面是一些函數(shù)使用的常用場景:
//輸出o的每個(gè)屬性的名稱和值,返回undefined
function printprops(o){
for(var p in o)
console.log(p + ": " + o[p] + "\n");
}
//計(jì)算兩個(gè)笛卡爾坐標(biāo)(x1,y1)和(x2,y2)之間的距離
function distance(x1,y1, x2,y2){
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
//計(jì)算階乘的遞歸函數(shù)(調(diào)用自身的函數(shù))
//x的值是從x到x遞減的值的累乘
function factorial(x){
if(x <= 1) return 1;
return x * factorial(x - 1);
}
//這個(gè)函數(shù)表達(dá)式定義了一個(gè)函數(shù)用來求傳入?yún)?shù)的平方
//注意我們把它賦值給一個(gè)變量
var square = function(x){ return x * x; }
//函數(shù)表達(dá)式可以包含名稱,這在遞歸時(shí)很有用
var f = function fact(x){
if(x <= 1) return 1;
return x * fact(x - 1);
}
//函數(shù)表達(dá)式也可以作為參數(shù)傳給其他函數(shù)
data.sort(function(a,b){return a - b;});
//函數(shù)表達(dá)式有時(shí)定義后立即調(diào)用
var tensquared = (function(x){ return x * x; }(10));
注:
- 前三個(gè)function是函數(shù)聲明方式,后四個(gè)是函數(shù)表達(dá)式
- 以表達(dá)式方式定義函數(shù),函數(shù)的名稱是可選的。
- 函數(shù)聲明語句實(shí)際上聲明了一個(gè)變量,并把一個(gè)函數(shù)對(duì)象賦值給它。定義函數(shù)表達(dá)式時(shí)并沒有聲明一個(gè)變量。
- 函數(shù)聲明定義函數(shù)會(huì)提前到代碼頂部,就是說在函數(shù)定義之前的代碼可以調(diào)用該函數(shù)。而函數(shù)定義表達(dá)式卻沒有“提前”
- 如果一個(gè)函數(shù)定義表達(dá)式包含名稱,函數(shù)的局部作用域?qū)?huì)包含一個(gè)綁定到函數(shù)對(duì)象的名稱。實(shí)際上,函數(shù)的名稱將成為函數(shù)內(nèi)部的一個(gè)局部變量。 (其實(shí)就是說,如果一個(gè)函數(shù)定義表達(dá)式如果包含名稱,那么函數(shù)的內(nèi)部可以通過這個(gè)名稱來訪問這個(gè)函數(shù),類似上面的階乘函數(shù))
函數(shù)命名
- 函數(shù)名稱一般由字母、數(shù)字、美元符號(hào)和下劃線組成
- 函數(shù)名稱通常是動(dòng)詞或動(dòng)詞為前綴的詞組。
- 當(dāng)函數(shù)名稱包含多個(gè)單詞時(shí),一般有兩種命名方式 :liek_this(),likeThis()——camel式(小駝峰式)
在一些編程風(fēng)格中,或者編程框架里,通常==為那些經(jīng)常調(diào)用的函數(shù)指定短暫的名稱==,比如jQuery就將最常用的方法重命名為$()。
嵌套函數(shù)
p180
函數(shù)調(diào)用
構(gòu)成函數(shù)主體的javascript代碼在定義之時(shí)并不會(huì)執(zhí)行,只有在調(diào)用該函數(shù)時(shí),他們才會(huì)執(zhí)行。有4種方式來調(diào)用javascript函數(shù):
- 作為函數(shù)(定義一個(gè)function funname(),然后執(zhí)行funname()調(diào)用該函數(shù))
- 作為方法(對(duì)象o.funname())
- 作為構(gòu)造函數(shù)(new funname())
- 通過他們的call()和apply()方法簡潔調(diào)用
作為函數(shù)調(diào)用
函數(shù)如果有return語句,那么執(zhí)行到return語句返回,沒有return語句,則返回undefined。
根據(jù)ECMAScript 3和非嚴(yán)格的ECMAScript 5對(duì)函數(shù)調(diào)用規(guī)定,調(diào)用上下文(this的值)是全局對(duì)象。然而,在嚴(yán)格模式下,調(diào)用上下文則是undefined。
//'use strict'
var strict = (function(){
return this;
}());
console.log(strict);//window
//如果取消'use strict`的注視,那么返回undefined
或者:
//'use strict'//非嚴(yán)格模式
function per(){
var self = this;//Window(非嚴(yán)格模式)
}
per('');
//去掉第一行注視(嚴(yán)格模式下),pre里面的self變量等于undefined了。
其實(shí)很好理解,因?yàn)檎{(diào)用(上面提到的作為函數(shù)調(diào)用)pre函數(shù)的是window對(duì)象,所以this是window(嚴(yán)格模式等于undefined是規(guī)定的,死記就好)。
其實(shí)這些是有章可循的,即看這個(gè)函數(shù)被當(dāng)作什么來調(diào)用,從而可以確定它(函數(shù)內(nèi)部的this對(duì)象)的所有者,this就指代這個(gè)所有者。函數(shù)里面的this對(duì)象指代規(guī)則如下:
- 作為函數(shù)調(diào)用,(嚴(yán)格模式下this是undefined,非嚴(yán)格模式下是window)
- 作為方法調(diào)用,this是調(diào)用當(dāng)前方法的對(duì)象
- 作為構(gòu)造函數(shù)調(diào)用,this是當(dāng)前new出來的對(duì)象
- 通過函數(shù)的call()或apply()調(diào)用,this就是傳遞進(jìn)去的對(duì)象(這兩個(gè)方法主要用來改變函數(shù)內(nèi)部的thi對(duì)象)
以函數(shù)形式調(diào)用的函數(shù)通常不使用this關(guān)鍵字(有時(shí)候也會(huì)這么做,當(dāng)你需要寫一個(gè)js的基類,然后很多子類都會(huì)繼承該類,并可以在子類里面覆蓋基類時(shí),通過這種方法可以減少很多重復(fù)性的代碼)。不過,“this”可以用來判斷當(dāng)前是否是嚴(yán)格模式。
//定義并調(diào)用一個(gè)函數(shù)來確定當(dāng)前腳本運(yùn)行時(shí)是否為嚴(yán)格模式
var strict = (function(){
return !this;
}());
注:函數(shù)調(diào)用這里的函數(shù)指不依賴或不掛載在其他對(duì)象下面的方法,就是一個(gè)簡簡單單的 function定義的表達(dá)式。
作為方法調(diào)用
一個(gè)方法無非是個(gè)保存在一個(gè)對(duì)象的屬性里面的javascript函數(shù)。如果有一個(gè)函數(shù)f和一個(gè)對(duì)象o,則可以用下面的代碼給o定義一個(gè)名為m()的方法。
o.m = f;
給對(duì)象o定義了方法m(),調(diào)用它時(shí)就像這樣:
o.m();
上面代碼意味著該函數(shù)被當(dāng)作一個(gè)方法,而不是作為一個(gè)普通函數(shù)來調(diào)用。
==對(duì)方法調(diào)用的參數(shù)和返回值的處理,和上面所描述的普通函數(shù)調(diào)用完全一致。但是,方法調(diào)用和函數(shù)調(diào)用有一個(gè)重要區(qū)別==,即:==調(diào)用上下文==。屬性訪問表達(dá)式由兩部分組成:一個(gè)對(duì)象(本例中的o)和屬性名稱(m)。在像這樣的方法調(diào)用表達(dá)式里,對(duì)象o成為調(diào)用上下文,函數(shù)體可以使用關(guān)鍵字this引用該對(duì)象。下面是一個(gè)具體的例子:
var calculator = { //對(duì)象直接量
operand1: 1,
operand2: 1,
add: function(){
//注意this關(guān)鍵字的用法,this指代當(dāng)前對(duì)象
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); //這個(gè)方法調(diào)用計(jì)算1+1的結(jié)果
calculator.result; //返回2
方法鏈:當(dāng)方法的返回值是一個(gè)對(duì)象,這個(gè)對(duì)象還可以再調(diào)用它的方法。這種方法調(diào)用序列中(通常稱為“鏈”或者“級(jí)聯(lián)”)每次的調(diào)用結(jié)果都是另外一個(gè)表達(dá)式的組成部分。比如,基于jQuery庫,我們常常會(huì)這樣寫代碼:
//找到所有的header,取得它們id的映射,轉(zhuǎn)換為數(shù)組并對(duì)它們進(jìn)行排序
$(":header").map(function(){return this.id}).get().sort();
==當(dāng)方法并不需要返回值時(shí),最好直接返回this==。如果在設(shè)計(jì)API就可以進(jìn)行“鏈?zhǔn)秸{(diào)用”風(fēng)格編程。在這種編程風(fēng)格中,只要指定一次要調(diào)用的對(duì)象即可,余下的方法都可以基于此進(jìn)行調(diào)用:
shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();
自己寫的一個(gè)demo:
function shape(){
var self = this;
self.x = 0;
self.y = 0;
self.size = 0;
self.setX = function(_x){
self.x = _x;
return self;
};
self.setY = function(_y){
self.y = _y;
return self;
};
self.setSize = function(_size){
self.size = _size;
return self;
};
self.getX = function(){return self.x; };
self.getY = function(){return self.y;};
self.getSize = function(){return self.size;};
}
var s1 = new shape();
s1.setX(1).setY(2).setSize(3);//鏈?zhǔn)綄懛?var x = s1.getX();//1
var y = s1.getY();//2
var size = s1.getSize();//3
需要注意的是,this是一個(gè)關(guān)鍵字,不是變量,也不是屬性名。javascript的語法不允許給this賦值。
和變量不同,關(guān)鍵字this沒有作用域的限制,嵌套的函數(shù)不會(huì)從調(diào)用它的函數(shù)中繼承this。如果嵌套函數(shù)作為方法調(diào)用,其this的值指向調(diào)用它的對(duì)象。如果嵌套函數(shù)作為函數(shù)調(diào)用,其this值不是全局對(duì)象(非嚴(yán)格模式下)就是undefined(嚴(yán)格模式下)。很多人誤以為調(diào)用嵌套函數(shù)時(shí),this會(huì)指向調(diào)用外層函數(shù)的上下文。如果你想訪問這個(gè)外部函數(shù)的this值,需要將this的值保存在一個(gè)變量里,這個(gè)變量和內(nèi)部函數(shù)都在同一個(gè)作用域內(nèi)。通常使用變量self來保存this,比如(理解這段話,最好先看明白如下例子):
var o = { //對(duì)象o
m: function(){ //對(duì)象中的方法m()
var self = this; //將this的值保存至一個(gè)變量中
console.log(this === o); //輸出true,this就是這個(gè)對(duì)象o
f(); //調(diào)用輔助函數(shù)f()——將f作為函數(shù)調(diào)用
function f(){ //定義一個(gè)嵌套函數(shù)
console.log(this === o);//false:this的值是全局對(duì)象或undefined
console.log(self === o);//true:self值外部函數(shù)的this值
}
}
};
o.m(); //調(diào)用對(duì)象o的方法m()
結(jié)論:
- 嵌套函數(shù)中,外部函數(shù)的this和內(nèi)部(嵌套)函數(shù)內(nèi)部的this是兩個(gè)不同的對(duì)象,并且都通過this引用該對(duì)象,如果想在嵌套函數(shù)內(nèi)部調(diào)用外部函數(shù)的this對(duì)象,必須將外部函數(shù)保存到一個(gè)變量——不然內(nèi)部函數(shù)this關(guān)鍵字只哪個(gè)呢,記住,他們都通過this關(guān)鍵字調(diào)用的。
- (內(nèi)部)函數(shù)里面的this對(duì)象指代什么,依賴上面提到的(函數(shù)被當(dāng)作什么來調(diào)用)。實(shí)際上可能應(yīng)該是所有函數(shù)里面的this對(duì)象指代什么都需要看當(dāng)前函數(shù)被當(dāng)作什么來調(diào)用。
作為構(gòu)造函數(shù)調(diào)用
==構(gòu)造函數(shù)調(diào)用和普通的函數(shù)調(diào)用以及方法調(diào)用在實(shí)參處理、調(diào)用上下文和返回值方面都有不同。==
如果構(gòu)造函數(shù)沒有形參,那么小括號(hào)是可以省略的,比如,下面這兩行代碼就是等價(jià)的。
var o = new Object();
var o = new Object;
==構(gòu)造函數(shù)調(diào)用創(chuàng)建一個(gè)新的空對(duì)象,這個(gè)對(duì)象繼承自構(gòu)造函數(shù)的prototype屬性。構(gòu)造函數(shù)試圖初始化這個(gè)新創(chuàng)建的對(duì)象,并將這個(gè)對(duì)象用做其調(diào)用上下文,因此構(gòu)造函數(shù)可以使用this關(guān)鍵字來引用新創(chuàng)建的對(duì)象。== 注意,盡管構(gòu)造函數(shù)看起來像一個(gè)方法調(diào)用,它依然會(huì)使用這個(gè)新對(duì)象作為調(diào)用上下文。也就是說,在表達(dá)式new o.m()中,調(diào)用上下文并不是o。
輔助解讀:
上面提到“這個(gè)對(duì)象繼承自”,繼承自哪里?可以通過對(duì)象.__proto__得到,如下:
function shape(){}
var s1 = new shape();
console.log(s1.__proto__ === shape.prototype);//true
console.log(s1 instanceof shape);//true
間接調(diào)用
javascript中的函數(shù)也是對(duì)象,和其他javascript對(duì)象沒什么兩樣,函數(shù)對(duì)象也可以包含方法。其中的兩個(gè)方法call()和apply()可以用來間接地調(diào)用函數(shù)。兩個(gè)方法都允許顯示指定調(diào)用所需的this值,也就是說,任何函數(shù)可以作為任何對(duì)象的方法來調(diào)用,哪怕這個(gè)函數(shù)不是那個(gè)對(duì)象的方法。兩個(gè)方法都可以指定調(diào)用的實(shí)參。call()方法使用它自由的實(shí)參列表作為函數(shù)的實(shí)參。apply()方法則要求以數(shù)組的形式傳入?yún)?shù)。
函數(shù)的實(shí)參和形參
javascript中的函數(shù)定義并未指定函數(shù)形參的類型,函數(shù)調(diào)用也未對(duì)傳入的實(shí)參做任何類型檢查。實(shí)際上,javascript函數(shù)調(diào)用甚至不檢查傳入形參的個(gè)數(shù)。下面幾節(jié)將討論當(dāng)調(diào)用函數(shù)時(shí)的形參和實(shí)參個(gè)數(shù)不匹配時(shí)出現(xiàn)的狀況,同樣說明了如何顯示測試函數(shù)實(shí)參的類型,以避免非法的實(shí)參傳入函數(shù)。
可選形參
==當(dāng)調(diào)用函數(shù)的時(shí)候傳入的實(shí)參比函數(shù)聲明時(shí)指定的形參個(gè)數(shù)要少,剩下的形參都將設(shè)置為undefined值==。因此在調(diào)用函數(shù)時(shí)形參是否可選以及是否可以省略應(yīng)該保持較好的適用性。==為了做到這一點(diǎn),應(yīng)該給省略的參數(shù)賦一個(gè)合理的默認(rèn)值==,看下面的例子:
//將對(duì)象o中可枚舉的屬性名追加至數(shù)組a中,并返回這個(gè)數(shù)組a
//如果省略a,則創(chuàng)建一個(gè)新數(shù)組并返回這個(gè)新數(shù)組
function getPropertyNames(o,/* optional */ a){
if(a === undefined) a = []; //如果為定義,則使用新數(shù)組
// a = a || []; //同上面的if語句
for(var property in o)
a.push(property);
return a;
}
//這個(gè)函數(shù)調(diào)用可以傳入1個(gè)或2個(gè)實(shí)參
var a = getPropertyNames(o) //將o的屬性存儲(chǔ)到一個(gè)新數(shù)組中
getPropertyNames(p,a); //將p的屬性追加至數(shù)組a中
需要注意的是,當(dāng)用這種可選實(shí)參來實(shí)現(xiàn)函數(shù)時(shí),需要將可選實(shí)參放在實(shí)參列表的最后。因?yàn)閖s沒法省略第一個(gè)實(shí)參并傳入第二實(shí)參的,它必須將undefined作為第一個(gè)實(shí)參顯示傳入。同樣注意在函數(shù)定義中使用注釋/*optional*/來強(qiáng)調(diào)形參是可選的。
可變長的實(shí)參列表:實(shí)參對(duì)象
當(dāng)調(diào)用函數(shù)的時(shí)候傳入的實(shí)參個(gè)數(shù)超過函數(shù)定義時(shí)的形參個(gè)數(shù)時(shí)。沒有辦法直接獲得未命名值的引用。參數(shù)對(duì)象解決了這個(gè)問題。在函書體內(nèi),標(biāo)識(shí)符arguments是指向?qū)崊?duì)象的引用,實(shí)參對(duì)象是一個(gè)類數(shù)組對(duì)象,這樣可以通過數(shù)字下標(biāo)就能訪問傳入函數(shù)的實(shí)參值,而不用非要通過名字來得到實(shí)參。
實(shí)參對(duì)象在很多地方都非常有用,下面的例子展示了使用它來驗(yàn)證實(shí)參的個(gè)數(shù),從而調(diào)用正確的邏輯,因?yàn)閖avascript本身不會(huì)這么做:
function f(x, y, z){
//首先,驗(yàn)證傳入實(shí)參的個(gè)數(shù)是否正確
if(arguments.length != 3){
throw new Error("函數(shù)f使用" + arguments.length + "個(gè)參數(shù),但它期望有3個(gè)參數(shù)");
}
//再執(zhí)行函數(shù)的其他邏輯……
}
需要注意的是,通常不必像這樣檢查實(shí)參個(gè)數(shù)。大多數(shù)情況下javascript的默認(rèn)行為是可以滿足需求的:==省略的實(shí)參都將是undefined,多出的參數(shù)會(huì)自動(dòng)省略==。
實(shí)參對(duì)象有一個(gè)重要的用處,就是讓函數(shù)可以操作任何數(shù)量的實(shí)參。下面的函數(shù)就可以接收任意數(shù)量的實(shí)參,并返回傳入實(shí)參的最大值(內(nèi)置函數(shù)Max.max()的功能與之類似):
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, 1000, 4, 5, 10000, 6);
注:arguments==并不是真正的數(shù)組,它是一個(gè)實(shí)參對(duì)象。每個(gè)實(shí)參對(duì)象都包含以數(shù)字為索引的一組元素以及l(fā)ength屬性,但它畢竟不是真正的數(shù)組??梢赃@樣理解,它是一個(gè)對(duì)象,只是碰巧具有以數(shù)字為索引的屬性。==
輔助解讀(證明arguments對(duì)象并不是數(shù)組):
function fun(){
//[object Array]
console.log(Object.prototype.toString.apply([]));
//[object Arguments]
console.log(Object.prototype.toString.apply(arguments));
}
fun();
只是一個(gè)key類似索引的對(duì)象:

緊要了。為了實(shí)現(xiàn)這種風(fēng)格的方法調(diào)用,定義函數(shù)的時(shí)候,傳入的實(shí)參都寫入一個(gè)單獨(dú)的對(duì)象之中,在調(diào)用的時(shí)候傳入一個(gè)對(duì)象,對(duì)象中的key/value對(duì)是真正需要的實(shí)參數(shù)據(jù)。下面就展示了這種風(fēng)格:
//將原始數(shù)組的length元素復(fù)制至目標(biāo)數(shù)組
//開始復(fù)制原始數(shù)組的from_start元素
//并且將其復(fù)制至目標(biāo)數(shù)組的to_start中
//要記住實(shí)參的順序并不容易
function arraycopy(/* array */ from, /* index */ from_start,
/* array */ to, /* index */ to_start,
/* integer */ length){
//邏輯代碼
}
//這個(gè)版本的實(shí)現(xiàn)效率稍微有點(diǎn)底,但你不必再去記住實(shí)參的順序
//并且from_start和to_start都默認(rèn)為0
function easycopy(args){
arraycopy(args.from,
args.from_start || 0,//注意這里設(shè)置了默認(rèn)值
argt.to,
args.to_start || 0, args.length );
}
//來看如何調(diào)用easycopy()
var a = [1, 2, 3, 4], b = [];
easycopy({from:a, to: b, length:4});//省略了兩個(gè)參數(shù)
實(shí)參類型
因?yàn)閖avascript沒有嚴(yán)格的參數(shù)類型限制,==所以我們應(yīng)當(dāng)添加一些類似的實(shí)參類型檢查邏輯,因?yàn)閷幵赋绦蛟趥魅敕欠ㄖ禃r(shí)報(bào)錯(cuò),也不愿非法值導(dǎo)致程序在執(zhí)行時(shí)報(bào)錯(cuò),相比而言,邏輯執(zhí)行時(shí)的報(bào)錯(cuò)不甚清晰且更難處理。==
//返回?cái)?shù)組(或類數(shù)組對(duì)象)a的元素的累加和
//數(shù)組a中必須為數(shù)字、null和undefined的元素都將忽略
function sum(a){
if(isArrayLike(a)){
var total = 0;
for(var i = 0; i < a.length; i++){//遍歷所有元素
var element = a[i];
if(element == null) continue;//跳過null和undefined
if(isFinite(element)) total += element;
else throw new Error("sum(): 數(shù)組a中的元素必須是一個(gè)有效數(shù)字 ");
}
return total;
}else{
throw new Error("參數(shù)a必須是數(shù)組對(duì)象");
}
}
作為值的函數(shù)
==函數(shù)不僅是一種語法。也是值,也就是說,可以將函數(shù)賦值給變量,存儲(chǔ)在對(duì)象的屬性或數(shù)組的元素中,作為參數(shù)傳入另外一個(gè)函數(shù)等。==
為了便于理解javascript中的函數(shù)時(shí)如何用做數(shù)據(jù)的以及javascript語法,來看一下這樣一個(gè)函數(shù)定義:
function square(x){ return x * x; }
這個(gè)定義創(chuàng)建一個(gè)新的函數(shù)對(duì)象,并將其賦值給變量square。==函數(shù)的名字實(shí)際上是看不見的,它(square)僅僅是變量的名字,這個(gè)變量指代函數(shù)對(duì)象。== 函數(shù)還可以賦值給其他的變量,并且仍可以正常工作:
var s = square; //現(xiàn)在s和square指代同一個(gè)函數(shù)
square(4); //16
s(4); //16
自定義函數(shù)屬性
javascript中的函數(shù)并不是原始值,而是一種特殊的對(duì)象,也就是說,函數(shù)可以擁有屬性。當(dāng)函數(shù)需要一個(gè)“靜態(tài)”變量來在調(diào)用時(shí)保持某個(gè)值不變,最方便的方式就是給函數(shù)定義屬性,而不是定義全局變量,顯然定義全局變量會(huì)讓命名空間變得更加雜亂無章。比如,假設(shè)你想寫一個(gè)返回一個(gè)唯一整數(shù)的函數(shù),不管在哪里調(diào)用函數(shù)都會(huì)返回這個(gè)整數(shù)。而函數(shù)不能兩次返回同一個(gè)值,為了做到這一點(diǎn),函數(shù)必須能夠跟蹤它每次返回的值,而且這些值的信息需要在不同的函數(shù)調(diào)用過程中持久化。可以將這些信息存放到全局變量中,但這并不是必需的,因?yàn)檫@個(gè)信息僅僅是函數(shù)本身用到的。最好將這個(gè)信息保存到函數(shù)對(duì)象的一個(gè)屬性中,下面這個(gè)例子就實(shí)現(xiàn)了這樣的函數(shù),每次調(diào)用函數(shù)都返回一個(gè)唯一的整數(shù):
//初始化函數(shù)對(duì)象的計(jì)數(shù)器屬性
//由于函數(shù)聲明被提前了,因此這里是可以在函數(shù)聲明
//之前給它的成員復(fù)制的
uniqueInteger.counter = 0;
//每次調(diào)用這個(gè)函數(shù)都回返回一個(gè)不同的整數(shù)
//它使用一個(gè)屬性來記住下一次將要返回的值
function uniqueInteger(){
console.log(uniqueInteger.counter);
return uniqueInteger.counter++;
}
new uniqueInteger();//output 0
new uniqueInteger();//output 1
new uniqueInteger();//output 2
作為命名空間的函數(shù)
前面已經(jīng)說到過函數(shù)作用域的概念:在函數(shù)中聲明的變量在整個(gè)函數(shù)體內(nèi)都是可見的(包括在嵌套的函數(shù)中),在函數(shù)的外部是不可見的。不在任何函數(shù)內(nèi)聲明的變量是全局變量,在整個(gè)javascript程序中都是可見的。在javascript中是無法聲明只在一個(gè)代碼塊內(nèi)可見的變量的(只有函數(shù)作用域,沒有塊級(jí)作用域)?;谶@個(gè)原因,我們常常簡單地定義一個(gè)函數(shù)用作臨時(shí)的命名空間,==在這個(gè)命名空間內(nèi)定義的變量都不會(huì)污染到全局命名空間==。
比如,假設(shè)你寫了一段javascript模塊代碼,這段代碼將要用在不同的javascript程序中。和大多數(shù)代碼一樣,假定這段代碼定義了一個(gè)用以存儲(chǔ)中間計(jì)算結(jié)果的變量。這樣問題就來了,當(dāng)模塊代碼放到不同的程序中運(yùn)行時(shí),你無法得知這個(gè)變量是否已經(jīng)創(chuàng)建了,如果已經(jīng)存在這個(gè)變量,那么將會(huì)和代碼發(fā)生沖突。解決辦法當(dāng)然是將代碼放入一個(gè)函數(shù)內(nèi),然后調(diào)用這個(gè)函數(shù)。這樣全局變量就變成了函數(shù)內(nèi)的局部變量:
function mymodule(){
//模塊代碼
//這個(gè)模塊所用的所有變量都是局部變量
//而不會(huì)污染全局命名空間
}
mymodule();//不要忘了還要調(diào)用這個(gè)函數(shù)
這段代碼僅僅定義了一個(gè)單獨(dú)的全局變量:名叫“mymodule”的函數(shù)。這樣還是太麻煩,可以直接定義一個(gè)匿名函數(shù),并在單個(gè)表達(dá)式中調(diào)用它:
(function(){//mymodule()函數(shù)重寫為匿名的函數(shù)表達(dá)式
//模塊代碼
}()); //結(jié)束函數(shù)定義并立即調(diào)用它
這種定義匿名函數(shù)并立即在單個(gè)表達(dá)式中調(diào)用它的寫法非常常見,已經(jīng)成為一種慣用法了。注意上面代碼的圓括號(hào)的用法,function之前的做圓括號(hào)是必須的,因?yàn)槿绻粚?,javascript解釋器會(huì)試圖將關(guān)鍵字function解析為函數(shù)聲明語句。使用圓括號(hào)javascript解析器才會(huì)解析為函數(shù)定義表達(dá)式。
輔助解讀:
實(shí)際上,如果沒有左圓括號(hào)是會(huì)報(bào)錯(cuò)的
function(){}() //SyntaxError
(function(){}()) //下面這兩種都可以
(function(){})()
p193 函數(shù)自定義屬性這段不太了解
閉包
p194
閱讀這一小節(jié)之前建議先復(fù)習(xí)《javascript權(quán)威指南》第 3.10(變量作用域)、3.10.3(作用域鏈)。
并參考:JavaScript 開發(fā)進(jìn)階:理解 JavaScript 作用域和作用域鏈
閉包的最常見形式:當(dāng)一個(gè)函數(shù)嵌套了另外一個(gè)函數(shù),外部函數(shù)將嵌套的函數(shù)對(duì)象作為返回值返回的時(shí)候就是一個(gè)閉包的形式。
文章開頭給了一個(gè)閉包的小demo,這里我復(fù)制以下:
var Counter=(function(){
//賦初值
var count=0;
//外部調(diào)用時(shí)形成閉包
function f(){
return ++count;
}
return f;
})();
Counter();//1
Counter();//2
Counter();//3
這段代碼剛好就是上面對(duì)閉包描述的代碼表示形式了。
總結(jié),閉包的特點(diǎn):
- 兩個(gè)函數(shù)嵌套;
- 調(diào)用外部函數(shù)返回內(nèi)部函數(shù)(內(nèi)部函數(shù)引用,不是執(zhí)行結(jié)果);
- 內(nèi)部函數(shù)引用外部函數(shù)變量(這是閉包的用處之一——這樣做的目的是:雖然外部函數(shù)執(zhí)行結(jié)束了,但是函數(shù)的變量并沒有被回收)
注意:上面代碼并不能簡寫成如下方式:
var Counter=(function(){
//賦初值
var count=0;
//外部調(diào)用時(shí)形成閉包
function f(){
return ++count;
}
return f;
});
//看上去和上面調(diào)用的方式差不多,但其實(shí)不然:
//上面的Counter對(duì)象保存的是一個(gè)執(zhí)行后的結(jié)果(對(duì)函數(shù)f的引用),然后多次Counter()都是調(diào)用f()函數(shù);
//而這種方式雖然也是多用調(diào)用f(),但是也多次重新調(diào)用Counter去獲取對(duì)f的引用了。
Counter()();//1
Counter()();//1
Counter()();//1
可以改成如下方式:
var Counter=(function(){
//賦初值
var count=0;
//外部調(diào)用時(shí)形成閉包
function f(){
return ++count;
}
return f;
});
var temp = Counter();//將對(duì)f的引用保留起來
//然后執(zhí)行該引用(方法)
temp();//1
temp();//2
temp();//3
函數(shù)屬性、方法和構(gòu)造函數(shù)
p 200
length屬性
函數(shù)的length屬性代表函數(shù)形參個(gè)數(shù)。
prototype屬性
call()方法和apply()方法
我們可以將call()和apply()看作是某個(gè)對(duì)象的方法,通過調(diào)用方法的形式來間接調(diào)用函數(shù)。call()和apply()的第一個(gè)實(shí)參是要調(diào)用函數(shù)的母對(duì)象,它是調(diào)用上下文,在函數(shù)體內(nèi)通過this來獲取對(duì)它的引用。要想以對(duì)象o的方法來調(diào)用函數(shù)f()(即:o.f
()的方式調(diào)用f()函數(shù)),可以這樣使用call()和apply():
function f(){
var self = this;
self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o);
f.apply(o);
注:這么調(diào)用其實(shí)就是為了改變函數(shù)f()中this的值,call()、apply()第一個(gè)參數(shù)即為這個(gè)this。
在ECMAScript 5的嚴(yán)格模式中,call()和apply()的第一個(gè)實(shí)參都會(huì)變?yōu)閠his的值,哪怕傳入的實(shí)參是原始值甚至是null或undefined。在ECMAScript 3和非嚴(yán)格模式中,傳入的null和undefined都會(huì)被全局對(duì)象代替,而其他原始值則會(huì)被相應(yīng)的包裝對(duì)象(wrapper objcet)所替代。
apply() 和 call() 方法的作用是類似的,只是參數(shù)不一樣:
如下:
function f(num1, num2){
var self = this;
self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o,1,2);//
f.apply(o,[1,2]);//
第一個(gè)參數(shù)是o,想要以該對(duì)象調(diào)用函數(shù)f();第二個(gè)參數(shù),call()是一個(gè)任意長度的參數(shù),而apply()是一個(gè)任意長度的數(shù)組(類數(shù)組也行,例如函數(shù)的 arguments 對(duì)象可以當(dāng)作apply()的第二個(gè)參數(shù))
//將對(duì)象o中名為m()的方法替換為另一個(gè)方法
//可以在調(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; //返回結(jié)果
}
}
var o = {
name: "alleanxu",
sayHello: function(msg){
console.log(msg + "," + this.name);
}
};
var temp = trace(o, "sayHello");
var res = o.sayHello("hello");
trace()函數(shù)接受兩個(gè)參數(shù),一個(gè)對(duì)象和一個(gè)方法名,它將指定的方法替換為一個(gè)新方法,這個(gè)新方法是“包裹”原始方法的另一個(gè)“泛函數(shù)”。這種動(dòng)態(tài)修改已有方法的做法有時(shí)稱做“monkey-patching”。
參考:monkey-patching(猴子補(bǔ)?。?/a>
bind()方法
bind()是在ECMAScript 5中新增的方法,但在ECMAScript 3中可以輕易模擬bind()。從名字就可以看出,這個(gè)方法的主要作用就是將函數(shù)綁定至某個(gè)對(duì)象。當(dāng)在函數(shù)f()上調(diào)用bind()方法并傳入一個(gè)對(duì)象o作為參數(shù),這個(gè)方法將返回一個(gè)新的函數(shù)。(以函數(shù)調(diào)用的方式)調(diào)用新的函數(shù)將會(huì)把原始的函數(shù)f()當(dāng)作o的方法來調(diào)用。傳入新函數(shù)的任何實(shí)參都將傳入原始函數(shù),比如:
function f(msg){
console.log(msg);
}
var o = {};
//將函數(shù)f()綁定至對(duì)象o
//擋在函數(shù)f()上調(diào)用bind()方法并傳入一個(gè)對(duì)象o作為參數(shù),這個(gè)方法將返回一個(gè)新的函數(shù)。
var result = f.bind(o);
//以函數(shù)調(diào)用的方式調(diào)用新的函數(shù)
//將會(huì)把原始函數(shù)f()當(dāng)作o的方法來調(diào)用
//傳入新函數(shù)的任何實(shí)參都將傳入原始函數(shù)
result("hello");
或者如下代碼(也演示了bind的使用) :
function f(y){ return this.x + y ;} //這個(gè)是待綁定的函數(shù)
var o = {x: 1}; //將要綁定的對(duì)象
var g = f.bind(o); //通過調(diào)用g(x)來調(diào)用o.f(x)
g(2); //3
可以通過如下代碼輕易地實(shí)現(xiàn)這種綁定:
//返回一個(gè)函數(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);
};
}
toString()方法
p204
Function()構(gòu)造函數(shù)
......
函數(shù)式編程
p206
......