JavaScript 函數(shù)深入學(xué)習(xí)

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ì)象:argumentsthis。

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"。

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

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

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