1.1 函數(shù)的創(chuàng)建和結(jié)構(gòu)
函數(shù)的定義:函數(shù)是JavaScript的基礎(chǔ)模塊單元,包含一組語句,用于代碼復(fù)用、信息隱蔽和組合調(diào)用。
函數(shù)的創(chuàng)建:在javaScript語言中,可以說函數(shù)是其最重要也最成功的設(shè)計。我們可以通過三種方式來創(chuàng)建函數(shù)。
① 函數(shù)聲明
② 字面量方式創(chuàng)建
③ 使用Function構(gòu)造函數(shù)創(chuàng)建
代碼示例
//01 函數(shù)聲明
//函數(shù)名稱為:f1,a和b為該函數(shù)的形式參數(shù)(形參)
function f1(a,b) {
return a + b;
}
//02 字面量創(chuàng)建函數(shù)
//使用字面量創(chuàng)建匿名函數(shù)并賦值給f2,可以通過f2來調(diào)用,a和b為該函數(shù)的形式參數(shù)(形參)
var f2 = function (a,b) {
return a + b;
};
//03 構(gòu)造函數(shù)創(chuàng)建
//f3函數(shù)為Function這個構(gòu)造函數(shù)的實(shí)例化對象,如果不傳遞參數(shù),那么創(chuàng)建出來的函數(shù)沒有任何用處。
//在創(chuàng)建實(shí)例對象的時候我們可以通過參數(shù)列表的方式來指定f3的結(jié)構(gòu)。
//構(gòu)造函數(shù)的參數(shù)中最后一個參數(shù)為函數(shù)體的內(nèi)容,其余均為函數(shù)的形參。
var f3 = new Function("a","b","return a + b");
//函數(shù)的調(diào)用
console.log(f1(1,2)); //3
console.log(f2(1,2)); //3
console.log(f3(1,2)); //3
函數(shù)的結(jié)構(gòu)
函數(shù)的一般表現(xiàn)形式為:
//函數(shù)聲明
function fn(n1,n2) {
//函數(shù)體的內(nèi)容
return n1 + n2; //返回值
}
通常,函數(shù)包括四個部分:
(1)保留字,function。
(2)函數(shù)名,這里為fn。
(3)圓括號以及包圍在圓括號中的一組參數(shù)。
(4)包括在花括號中的一組語句。
? 函數(shù)名可以被省略(稱為匿名函數(shù)),函數(shù)名可用于函數(shù)調(diào)用或者是遞歸調(diào)用,另外函數(shù)名可以被調(diào)試器和開發(fā)工具識別。
? 函數(shù)聲明時的參數(shù)為形參,可以有多個,多個參數(shù)之間使用逗號進(jìn)行分隔。
形參將在函數(shù)調(diào)用的時候被定義為函數(shù)中的局部變量,[注意]形參并不會像普通變量一樣被初始化為undefined,它們的值根據(jù)函數(shù)調(diào)用時傳入的實(shí)際參數(shù)值設(shè)置。另外,函數(shù)調(diào)用的時候并不會對實(shí)參的類型進(jìn)行檢查。
? 函數(shù)體是一組語句,它們在函數(shù)
被調(diào)用的時候執(zhí)行。函數(shù)執(zhí)行完畢后,會返回一個值。
函數(shù)的調(diào)用:函數(shù)聲明后可以通過()運(yùn)算符來進(jìn)行調(diào)用,JavaScript語言中,只有函數(shù)可以被調(diào)用。當(dāng)函數(shù)被調(diào)用的時候,如果存在參數(shù)傳遞,那么會把實(shí)參的值傳遞給形參,并按照從上到下的順序逐條執(zhí)行函數(shù)體內(nèi)部的代碼。
1.2 函數(shù)和對象的關(guān)系
JavaScript中的函數(shù)本質(zhì)上就是對象。
在使用typeof 關(guān)鍵字對數(shù)據(jù)進(jìn)行類型檢查的時候,得到的結(jié)果可能會讓我們產(chǎn)生錯覺。
var o = {};
var f = function () {};
console.log(typeof o); //object
console.log(typeof f); //function
實(shí)際上,函數(shù)和對象沒有質(zhì)的區(qū)別,函數(shù)是特殊的對象。
函數(shù)的特殊性
① 函數(shù)可以被()運(yùn)算符調(diào)用[最重要]。
② 函數(shù)可以創(chuàng)建獨(dú)立的作用域空間。
③ 函數(shù)擁有標(biāo)配的prototype屬性。
因?yàn)楹瘮?shù)本身就是對象,所以在代碼中函數(shù)可以像對象一樣被使用,凡是對象可以出現(xiàn)的地方函數(shù)都可以出現(xiàn)。
? 函數(shù)可以擁有屬性和方法。
? 函數(shù)可以保存在變量、對象和數(shù)組中。
? 函數(shù)可以作為其它函數(shù)的參數(shù)(稱為函數(shù)回調(diào))。
? 函數(shù)可以作為函數(shù)的返回值進(jìn)行返回。
函數(shù)和對象的原型鏈結(jié)構(gòu)
我們可以通過下面列出的簡單示例代碼來分析對象的原型鏈結(jié)構(gòu)。
//字面量方式創(chuàng)建普通的對象
var o = {name:"文頂頂",age:"18"};
//關(guān)于普通對象的結(jié)構(gòu)研究
console.log("① 打印o對象\n",o);
console.log("② 打印o.__proto__\n",o.__proto__);
console.log("③ 打印o.__proto__ === Object.prototype\n",o.__proto__ === Object.prototype)
console.log("④ 打印o.constructor\n",o.constructor);
console.log("⑤ 打印o.constructor === Object\n",o.constructor === Object);
通過對該代碼的運(yùn)行和打印分析,可以得到下面的圖示。
我們也可以使用同樣的方式來分析函數(shù)對象的原型鏈結(jié)構(gòu)。
//使用構(gòu)造函數(shù)Function 來創(chuàng)建函數(shù)(對象)
var f = new Function("a","b","return a + b");
//調(diào)用函數(shù),證明該函數(shù)是合法可用的
console.log(f(2, 3)); //得到打印結(jié)果5
//關(guān)于函數(shù)對象的結(jié)構(gòu)研究
console.log("① 打印函數(shù)對象\n",f);
console.log("② 打印f.__proto__\n",f.__proto__);
console.log("③ 打印f.__proto__===Function.prototype\n",f.__proto__===Function.prototype);
console.log("④ 打印f.constructor\n",f.constructor);
console.log("⑤ 打印f.constructor === Function\n",f.constructor === Function);
//注意
console.log(f.hasOwnProperty("constructor")); //檢查constructor是否為函數(shù)f的實(shí)例成員(false)
console.log(f.__proto__.hasOwnProperty("constructor")); //true
順便貼出研究Function原型結(jié)構(gòu)的代碼
//說明:下面三行代碼表明Function的原型對象指向一個空函數(shù)
console.log(Function.prototype); //? () { [native code] } 是一個空函數(shù)
console.log(typeof Function.prototype); //function
console.log(Object.prototype.toString.call(Function.prototype)); //[object Function]
//檢查Function.prototype的原型鏈結(jié)構(gòu)
//Function.prototype是一個空函數(shù),是一個對象,而對象均由構(gòu)造函數(shù)實(shí)例化產(chǎn)生
//檢查Function.prototype的構(gòu)造函數(shù)以及原型對象
console.log(Function.prototype.constructor === Function);
//注意:按照一般邏輯實(shí)例對象的__proto__(原型對象)應(yīng)該指向創(chuàng)建該實(shí)例對象的構(gòu)造函數(shù)的原型對象
//即此處應(yīng)該表現(xiàn)為Function.prototype.__proto__--->Function.prototype.constructor.prototype
//似乎可以得出推論:Function.prototype.__proto__ === Function.prototype == 空函數(shù) 但這是錯誤的
console.log(Function.prototype.__proto__ === Object.prototype);
通過對函數(shù)對象原型結(jié)構(gòu)的代碼探索,可以得到下圖的原型鏈結(jié)構(gòu)圖(注:原型鏈并不完整)

函數(shù)的其它隱藏細(xì)節(jié)
① 函數(shù)天生的prototype屬性
每個函數(shù)對象在創(chuàng)建的時候會隨配一個prototype屬性,即每個函數(shù)在創(chuàng)建之后就天生擁有一個與之相關(guān)聯(lián)的原型對象,這個關(guān)聯(lián)的原型對象中擁有一個constructor屬性,該屬性指向這個函數(shù)。
簡單描述下就是:
function f(){ //......} //聲明函數(shù)
//函數(shù)聲明被創(chuàng)建后,默認(rèn)擁有prototype屬性--->原型對象(空對象)

這里需要注意的是,很多人容易被自己的經(jīng)驗(yàn)誤導(dǎo),認(rèn)為新創(chuàng)建的函數(shù)對象身上除prototype實(shí)例屬性外,還擁有constructor這個實(shí)例屬性,因?yàn)槲覀兘?jīng)??吹?code>f.constructor類似的代碼,其實(shí)這里使用的constructor屬性是從原型鏈中獲取的,其實(shí)是f構(gòu)造函數(shù)關(guān)聯(lián)原型對象上面的屬性,即Function.prototype.constructor。
備注:在ECMAScript標(biāo)準(zhǔn)中函數(shù)創(chuàng)建相關(guān)章節(jié)有這樣一句話:NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.解釋了給新創(chuàng)建函數(shù)添加prototype屬性的意義在于便于該函數(shù)作為構(gòu)造函數(shù)使用。
② 函數(shù)何以能夠被調(diào)用
我們已經(jīng)理解了函數(shù)本身就是對象,但又區(qū)別于普通對象,最大的區(qū)別在于函數(shù)可以被調(diào)用,()被稱為調(diào)用運(yùn)算符。
?? 函數(shù)可以被調(diào)用的原因在于JavaScript創(chuàng)建一個函數(shù)對象時,會給該對象設(shè)置一個“調(diào)用”屬性。當(dāng)JavaScript調(diào)用一個函數(shù)時,可以理解為調(diào)用該函數(shù)的“調(diào)用”屬性。
1.3 函數(shù)的調(diào)用和this參數(shù)
函數(shù)名后面跟上()表明這是一個函數(shù)調(diào)用。
調(diào)用運(yùn)算符:是跟在任何產(chǎn)生一個函數(shù)值的表達(dá)式之后的一對圓括號。圓括號內(nèi)可以包含N(N>=0)個用逗號分隔開的表達(dá)式,每個表達(dá)式產(chǎn)生一個參數(shù)值。每個參數(shù)值被賦予函數(shù)聲明時定義的形式參數(shù)名。
函數(shù)的調(diào)用
JavaScript中有四種調(diào)用函數(shù)的模式
① 對象方法調(diào)用模式
② 普通函數(shù)調(diào)用模式
③ 構(gòu)造函數(shù)調(diào)用模式
③ 上下文的調(diào)用模式
除了聲明函數(shù)時定義的形參外,每個函數(shù)還接收兩個附加的參數(shù),分別是this和arguments。其中arguments用于存儲函數(shù)調(diào)用時接收到的實(shí)際參數(shù),this的值則取決于函數(shù)的調(diào)用模式,下面分別講解。
普通函數(shù)調(diào)用模式
當(dāng)函數(shù)并不作為其他對象的屬性,直接使用調(diào)用運(yùn)算符來調(diào)用時,我們認(rèn)為它使用普通函數(shù)調(diào)用模式。
<script>
//01 聲明函數(shù)fn
function fn() {
console.log(this);
}
//02 以普通函數(shù)調(diào)用模式來調(diào)用fn函數(shù)
fn(); //this被綁定到全局對象window
</script>
備注:在我們看來上面的調(diào)用方式非常簡單清楚,而且this的指向也沒有任何問題。但JSON的作者Douglas Crockford指出這是JavaScript語言設(shè)計上的一個錯誤。因?yàn)榘裻his直接綁定給全局變量的方式?jīng)]有考慮到函數(shù)作為內(nèi)部函數(shù)(在其它函數(shù)內(nèi)部聲明的函數(shù))使用過程中需要共享外部對象訪問權(quán)的問題。
他指出正確的語言設(shè)計應(yīng)該是,當(dāng)內(nèi)部函數(shù)被調(diào)用時,函數(shù)內(nèi)的this應(yīng)該和外部函數(shù)的this保持一致,即這個this應(yīng)該被綁定到外部函數(shù)的this變量。無疑,這值得思考和討論。
對象方法調(diào)用模式
對象是鍵值對的集合,對象可以擁有屬性和方法。
當(dāng)函數(shù)被保存為對象的屬性時,我們稱之為方法。
對象的方法需要通過對象.方法()或者是對象[方法]()的方式進(jìn)行調(diào)用。
以對象方法的模式來對函數(shù)進(jìn)行調(diào)用,函數(shù)內(nèi)部的this被綁定給該對象。
//01 字面量的方式創(chuàng)建對象
//02 o對象中擁有name屬性和showName方法
var o = {
name:"文頂頂",
showName:function () {
console.log(this);
console.log(this.name); //文頂頂
}};
//03 以對象方法調(diào)用模式來調(diào)用showName函數(shù)
o.showName(); //this被綁定到o對象
?? this到對象的綁定發(fā)生在方法調(diào)用的時候。
構(gòu)造函數(shù)調(diào)用模式
構(gòu)造函數(shù):如果一個函數(shù)創(chuàng)建出來之后,我們總是希望使用new 前綴來調(diào)用它,那這種類型的函數(shù)就被稱為構(gòu)造函數(shù)。構(gòu)造函數(shù)和普通函數(shù)本質(zhì)上沒有任何區(qū)別,開發(fā)者總是約定以函數(shù)名首字母大寫的方式來人為進(jìn)行區(qū)分。
如果以構(gòu)造函數(shù)的方式來調(diào)用函數(shù),那么在調(diào)用時,默認(rèn)會創(chuàng)建一個連接到該構(gòu)造函數(shù)原型對象上面的新對象,同時讓this綁定到該新對象上。
//01 聲明構(gòu)造函數(shù)Person
function Person() {
console.log(this);
}
//02 以構(gòu)造函數(shù)的方式來調(diào)用Person
new Person(); //this被綁定到Person的實(shí)例化對象
?? 構(gòu)造函數(shù)調(diào)用方式也會改變函數(shù)中return語句的行為,如果顯示的return語句后面跟著的不是對象類型的數(shù)據(jù),那么默認(rèn)返回this綁定的新對象。
上下文的調(diào)用模式
上下文的調(diào)用模式,即使用apply或則call方法來調(diào)用函數(shù)。
因?yàn)镴avaScrip是一門函數(shù)式的面向?qū)ο缶幊陶Z言,所有JavaScript中的函數(shù)本質(zhì)上是對象,也因此函數(shù)也可以擁有方法。使用上下文模式對函數(shù)進(jìn)行調(diào)用的時候,函數(shù)內(nèi)部的this根據(jù)參數(shù)傳遞的情況進(jìn)行綁定。
//聲明函數(shù)f
function f(a,b) {
console.log(a, b, a+b);
console.log(this); //使用上下文模式調(diào)用時,this被綁定給o對象
console.log(this.name); //wendingding
}
//字面量的方式創(chuàng)建對象
var o = {name:"wendingding"};
//使用apply和call方法來調(diào)用函數(shù)
f.apply(o, [1,2]);
f.call(o,3,4);
console.log(f.hasOwnProperty("apply")); //false
console.log(Function.prototype.hasOwnProperty("apply"));//true
? apply和call方法調(diào)用函數(shù),函數(shù)內(nèi)部的this綁定給第一個參數(shù)。
? apply和call方法定義于Function的原型對象上,所以所有的函數(shù)都可訪問。
? apply和call方法作用基本相同,參數(shù)傳遞的形式有所差別。
1.4 函數(shù)的參數(shù)(arguments)
函數(shù)調(diào)用時,會完成實(shí)際參數(shù)對形式參數(shù)的賦值工作。
當(dāng)實(shí)際參數(shù)的個數(shù)和形式參數(shù)的個數(shù)不匹配時,并不會導(dǎo)致運(yùn)行錯誤。
如果實(shí)際參數(shù)的數(shù)量過多,那么超出的那些參數(shù)會被忽略。
如果實(shí)際參數(shù)的數(shù)量不足,那么缺失的那些參數(shù)會被設(shè)置為undefined。
JavaScript在進(jìn)行函數(shù)調(diào)用時不會對參數(shù)進(jìn)行任何的類型檢查。
在函數(shù)的內(nèi)部,我們總是可以獲得一個免費(fèi)配送的arguments參數(shù)。
arguments用于接收函數(shù)調(diào)用時傳入的實(shí)際參數(shù),它被設(shè)計成一個類似于數(shù)組的結(jié)構(gòu),擁有l(wèi)ength屬性,但因?yàn)樗皇且粋€真正的數(shù)組所以不能使用任何數(shù)組對應(yīng)的方法。
arguments參數(shù)的存在,使得我們可以編寫一些無須指定形參個數(shù)的函數(shù)。
下面提供一份示例代碼用于對傳入的所有參數(shù)進(jìn)行累加計算。
<script>
function sum() {
var sum = 0;
var count = arguments.length;
for (var i = 0; i < count; i++) {
sum += arguments[i];
}
return sum;
}
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 89)); //125
</script>
1.5 函數(shù)的返回值
函數(shù)的調(diào)用:調(diào)用一個函數(shù)會暫停當(dāng)前代碼的執(zhí)行,把控制權(quán)和參數(shù)傳遞給正被調(diào)用的函數(shù)。當(dāng)一個函數(shù)被調(diào)用的時候,它會先根據(jù)實(shí)際參數(shù)來對函數(shù)的形式參數(shù)進(jìn)行初始化,然后從函數(shù)體中的第一個語句開始執(zhí)行并遇到關(guān)閉函數(shù)體的 } 時結(jié)束。然后把控制權(quán)交還給調(diào)用該函數(shù)的上下文。
函數(shù)的返回值:函數(shù)體中return語句可以用來讓函數(shù)提前返回。當(dāng)retun語句被執(zhí)行時,函數(shù)會立即返回而不再執(zhí)行余下的語句,return語句后面跟上返回的具體數(shù)據(jù),可以是任意類型(包括函數(shù))。
? 函數(shù)總是會有一個返回值,如果沒有使用return語句指定,那么將總是返回
undefined。
? 函數(shù)的返回值還和它的調(diào)用方式有關(guān)系,如果使用new也就是也構(gòu)造函數(shù)的方式來調(diào)用,若函數(shù)體中沒有通過return語句顯示的返回一個對象類型的數(shù)據(jù),則
默認(rèn)返回this(新創(chuàng)建的實(shí)例對象)。
?? JavaScript不允許在return關(guān)鍵字和表達(dá)式之間換行。
