1 函數(shù)
每個(gè)函數(shù)都是 Function 類型的實(shí)例,且都與其他引用類型一樣具有屬性和方法。由于函數(shù)式對(duì)象,因此函數(shù)名實(shí)際上也是一個(gè)指向函數(shù)對(duì)象的指針,不會(huì)與某個(gè)函數(shù)綁定。
1.1 函數(shù)概述
1.1.1 函數(shù)定義
函數(shù)通常都是使用函數(shù)聲明語法定義的,如下:
- 使用函數(shù)聲明語法定義(必須有函數(shù)名):
function sum(num1, num2) {return num1 + num2;}
- 使用函數(shù)表達(dá)式定義(可以沒有函數(shù)名):
var sum = function(num1, num2) {return num1 + num2;};
- 使用 Function 構(gòu)造函數(shù)定義:
Function 構(gòu)造函數(shù)可以接收任意數(shù)量的參數(shù), 但后一個(gè)參數(shù)始終都被看成是函數(shù)體,而前面的參數(shù)則枚舉出了新函數(shù)的參數(shù)。
var sum = new Function("num1", "num2", "return num1 + num2"); // 不推薦
這種方式可以很直觀理解“函數(shù)是對(duì)象,函數(shù)名是指針”。
從技術(shù)上角度講,這是一個(gè)函數(shù)表達(dá)式。但這種語法會(huì)導(dǎo)致解析兩次代碼(解析常規(guī) ECMAScript 代碼,解析傳入構(gòu)造函數(shù)中的字符串),影響性能。因此,不推薦使用這種方法定義。由于函數(shù)名僅僅是指向函數(shù)的指針,因此函數(shù)名與包含對(duì)象指針的其他變量沒有什么不同。換句話說,一個(gè)函數(shù)可能會(huì)有多個(gè)名字。使用帶圓括號(hào)的函數(shù)名是調(diào)用函數(shù),使用不帶圓括號(hào)的函數(shù)名是訪問函數(shù)指針,而非調(diào)用函數(shù)。
1.1.2 函數(shù)調(diào)用——functionName(arg0, arg1,...,argN);
sayHi("Nicholas", "how are you today?");輸出結(jié)果:彈出 "Hello Nicholas,how are you today?"
1.1.3 函數(shù)返回值
任何函數(shù)、任何時(shí)候都可以通過 return 語句后跟要返回的值來是實(shí)現(xiàn)返回值。函數(shù)會(huì)在執(zhí)行完 return 語句后停止并立即退出。因此,return 之后的任何代碼都永遠(yuǎn)不會(huì)執(zhí)行。
function sum(num1, num2) {
return num1 + num2;
}
調(diào)用:var result = sum(5, 10); // result 為15
注:return 可以不帶人和返回值。則函數(shù)將返回 undefined 值。一般用在需要提前停止函數(shù)執(zhí)行,又不需要返回值的情況下。推薦:要么讓函數(shù)始終都有返回值,要么就永遠(yuǎn)都不要有返回值,否則會(huì)給代碼調(diào)試帶來不便。嚴(yán)格模式下的限制: 發(fā)生以下情況,會(huì)導(dǎo)致語法錯(cuò)誤,代碼無法執(zhí)行。不能把函數(shù)/參數(shù)命名為eval 或arguments;不能出現(xiàn)兩個(gè)命名參數(shù)同名的情況。
1.1.4 理解參數(shù)
ECMAScript 函數(shù)不介意傳遞的參數(shù)的個(gè)數(shù)及類型,即使個(gè)數(shù)與定義的個(gè)數(shù)不同,也不會(huì)報(bào)錯(cuò)。因?yàn)閰?shù)在內(nèi)部使用一個(gè)數(shù)組來表示的。函數(shù)體內(nèi)部可以通過 arguments 對(duì)象來訪問這個(gè)參數(shù)數(shù)組,從而獲取傳遞過來的每一個(gè)參數(shù)。
function sayHi(name, message) { alert("Hello " + name + "," + message);}
可以像下面這樣重寫
function sayHi() { alert("Hello " + arguments[0] + "," + arguments[1]);}
ECMAScript 函數(shù)的重要特點(diǎn):
命名的參數(shù)只提供便利,但不是必需的;
在調(diào)用時(shí),對(duì)應(yīng)參數(shù)名字不一定要一致;
aruments 對(duì)象可以與命名參數(shù)一起使用;
arguments 的值永遠(yuǎn)與對(duì)應(yīng)命名參數(shù)的值保持同步;
arguments 對(duì)象的長度由傳入函數(shù)的參數(shù)決定,而非定義函數(shù)時(shí)的命名參數(shù)個(gè)數(shù);
沒有傳遞值的命名參數(shù)自動(dòng)被賦予 undefined 值,類似于定義了變量但未初始化。
下面這個(gè)函數(shù)會(huì)在每次被調(diào)用時(shí),輸出傳入其中的參數(shù)個(gè)數(shù):
function howManyArgs() {
alert(arguments.length);
}
howManyArgs("string", 45); //2
howManyArgs(); //0
howManyArgs(12); //1
嚴(yán)格模式下:嚴(yán)格模式對(duì)如何使用 arguments 對(duì)象做出了一些限制。
首先,像前面例子中那樣的賦值會(huì)變得無效。也就是說,即使把 arguments[1] 設(shè)置為 10,num2 的值仍然還是 undefined。其次,重寫 arguments 的值會(huì)導(dǎo)致語法錯(cuò)誤(代碼將不會(huì)執(zhí)行)。
1.2 沒有重載(深入理解)
重載函數(shù)
重載函數(shù)是函數(shù)的一種特殊情況,在同一個(gè)作用域中,如果有多個(gè)函數(shù)的名字相同,但形參列表不同(參數(shù)類型不同或參數(shù)個(gè)數(shù)不同),返回值類型可同也可不同,我們稱之為重載函數(shù)。
ECMAScript 函數(shù)不能重載
ECMAScript 函數(shù)不能像傳統(tǒng)意義上那樣實(shí)現(xiàn)重載。
如果在ECMAScript 中定義了兩個(gè)名字相同的函數(shù),則該名字只屬于后定義的函數(shù),后面的會(huì)覆蓋前面的。通過檢查傳入函數(shù)中參數(shù)的類型和數(shù)量并作出不同的反應(yīng),可以模仿方法的重載。
1.3 函數(shù)聲明與函數(shù)表達(dá)式
函數(shù)聲明:率先讀取函數(shù)聲明,并使其在執(zhí)行任何代碼前可用(可訪問);解析器會(huì)在代碼開始執(zhí)行之前,通過一個(gè)名為函數(shù)聲明提升的過程,讀取并將函數(shù)聲明添加到執(zhí)行環(huán)境中。對(duì)代碼求值時(shí),JavaScript 引擎在第一遍會(huì)聲明函數(shù)并將它們放到源代碼樹的頂部。所以,即使聲明函數(shù)的代碼在調(diào)用它的代碼后面,JavaScript 引擎也能把函數(shù)聲明提升到頂部。
使用函數(shù)聲明語法定義函數(shù)(必須有函數(shù)名):
alert(sum(10,10));
function sum(num1, num2) {
return num1 + num2;
}
函數(shù)表達(dá)式:等到解析器執(zhí)行到它所在的代碼行,才會(huì)真正被解釋執(zhí)行。使用函數(shù)表達(dá)式定義函數(shù)(可以沒有函數(shù)名):
alert(sum(10,10));
var sum = function(num1, num2) {
return num1 + num2;
};
使用函數(shù)表達(dá)式定義函數(shù),如上,若在定義前通過變量訪問函數(shù),會(huì)導(dǎo)致報(bào)錯(cuò)。因?yàn)樵趫?zhí)行到函數(shù)所在的語句之前,變量 sum 中不會(huì)保存有對(duì)函數(shù)的引用;而且,第一行代碼就會(huì)導(dǎo)致“unexpected identifier”(意外標(biāo)識(shí)符)錯(cuò)誤,代碼并不會(huì)執(zhí)行到下一行。
1.4 作為值的函數(shù)
因?yàn)?ECMAScript 中的函數(shù)名本身就是變量,所以函數(shù)也可以作為值來使用。即不僅可以像傳遞參數(shù)一樣把一個(gè)函數(shù)傳遞給另一個(gè)函數(shù),也可以將一個(gè)函數(shù)作為另一個(gè)函數(shù)的結(jié)果返回。如下:
//接受兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)函數(shù),第二個(gè)是要傳遞給該函數(shù)的一個(gè)值
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1); //20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); //"Hello, Nicholas"
1.5 函數(shù)內(nèi)部屬性
函數(shù)內(nèi)部有兩個(gè)特殊的對(duì)象:arguments 和 this。
1.5.1 arguments
arguments 是一個(gè)類數(shù)組對(duì)象,包含著傳入函數(shù)中的所有參數(shù)。arguments 有一個(gè) callee 的屬性,該屬性是一個(gè)指針,指向擁有這個(gè) arguments 對(duì)象的函數(shù)。下面是非常經(jīng)典的階乘函數(shù):
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
} }
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //0
alert(factorial(5)); //0
alert(factorial(5)); //0
如上,若這個(gè)函數(shù)的執(zhí)行與函數(shù)名 factorial 緊緊耦合在了一起,當(dāng)函數(shù)名 factorial 重寫后,會(huì)對(duì)函數(shù)執(zhí)行有所影響,為了消除這種緊密耦合的現(xiàn)象,可以像下面這樣使用 arguments.callee。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0
alert(factorial(5)); //0
1.5.2 this
this 引用的是函數(shù)據(jù)以執(zhí)行的環(huán)境對(duì)象。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red" 全局作用域調(diào)用,this 引用全局對(duì)象 window,this.color => window.color
o.sayColor = sayColor; o.sayColor(); //"blue" 函數(shù)賦給了對(duì)象 o,this 引用的是對(duì)象 o,this.color => o.color
注:函數(shù)名僅僅是一個(gè)包含指針的變量而已。因此,即使是在不同環(huán)境中執(zhí)行,全局的 sayColor() 函數(shù)與 o.sayColor() 指向的仍是同一個(gè)函數(shù)。
1.5.3 caller
caller 屬性中保存著調(diào)用當(dāng)前函數(shù)的函數(shù)的引用。
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
以上代碼,會(huì)導(dǎo)致警告框中顯示 outer() 函數(shù)的源代碼。因?yàn)?outer()調(diào)用了 inter(),所以 inner.caller 就指向 outer()。如果是在全局作用域中調(diào)用當(dāng)前函數(shù),它的值為 null。為了實(shí)現(xiàn)更松散的耦合,也可以通過 arguments.callee.caller 來訪問相同的信息。
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
注:嚴(yán)格模式下,訪問 arguments.callee 會(huì)導(dǎo)致錯(cuò)誤,不能為函數(shù)的 caller 屬性賦值,否則也會(huì)導(dǎo)致錯(cuò)誤。
1.6 函數(shù)屬性和方法
ECMAScript 中的函數(shù)都是對(duì)象,因此函數(shù)也有屬性和方法。每個(gè)函數(shù)都包含兩個(gè)屬性:length 和 prototype。
length:函數(shù)希望接收的命名參數(shù)的個(gè)數(shù);
prototype:保存所有實(shí)例方法的真正所在。
1.6.1 length
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0
1.6.2 prototype
對(duì)于 ECMAScript 中的引用類型而言,prototype 是保存它們所有實(shí)例方法的真正所在。每個(gè)函數(shù)都包含兩個(gè)非繼承而來的方法:apply() 和 call()。
這兩個(gè)方法的用途都是在特定的作用域中調(diào)用函數(shù),實(shí)際上相當(dāng)于設(shè)置函數(shù)體內(nèi) this 對(duì)象的值。
1.6.2.1 apply
apply() 方法接收兩個(gè)參數(shù):一個(gè)是在其中運(yùn)行函數(shù)的作用域,另一個(gè)是參數(shù)數(shù)組。其中,第二個(gè)參數(shù)可以是 Array 的實(shí)例,也可以是 arguments 對(duì)象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 傳入 arguments 對(duì)象 }
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 傳入數(shù)組
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
注:嚴(yán)格模式下,未指定環(huán)境對(duì)象而調(diào)用函數(shù),則 this 值不會(huì)轉(zhuǎn)型為 window。除非明確把函數(shù)添加到某個(gè)對(duì)象或者調(diào)用 apply() 或 call(),否則 this 值將是 undefined。
1.6.2.2 call
call() 方法與 apply() 方法作用相同,區(qū)別僅在于接收參數(shù)的方式不同。對(duì)于 call() 而言,第一個(gè)參數(shù)是 this 值,剩下的參數(shù)都直接傳遞給函數(shù)。即使用 call() 方法時(shí),傳遞給函數(shù)的參數(shù)必須逐個(gè)列舉出來。如下:
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20
使用 apply() 還是 call() ,完全取決于你才去哪種給函數(shù)傳遞參數(shù)的方式最方便。不傳參數(shù),則哪種方法都無所謂。事實(shí)上,傳遞參數(shù)并非 apply() 和 call() 真正的用武之地;它們真正強(qiáng)大的地方是能夠擴(kuò)充函數(shù)賴以運(yùn)行的作用域。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red this=>window
sayColor.call(this); //red this=>window
sayColor.call(window); //red this=>window
sayColor.call(o); //blue this=>o
使用 call() 或 apply() 來擴(kuò)充作用域的最大好處:對(duì)象不需要與方法有任何耦合關(guān)系。不需要像前面那樣把要用的函數(shù)放到對(duì)象中,調(diào)用。
1.6.2.3 bind
這個(gè)方法會(huì)創(chuàng)建一個(gè)函數(shù)的實(shí)例,其 this 值會(huì)被綁定到傳給 bind() 函數(shù)的值。如:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
這里,sayColor()調(diào)用 bind()并傳入對(duì)象 o,創(chuàng)建了 objectSayColor()函數(shù)。
object- SayColor()函數(shù)的 this 值等于 o,因此即使是在全局作用域中調(diào)用這個(gè)函數(shù),也會(huì)看到"blue"。