JS函數類型(一)
一、函數類型
1.1 在JS中,每個函數都是Function類型的實例。而且都與其他類型一樣,具有屬性和方法。由於函數是對象,因此函數名實際上也是一個指向函數對象的指針。不會與某個函數綁定,函數通常是使用函數聲明語法定義的(函數聲明)。
function sum(num1,num2){ return num1 + num2 }
這與下面函數表達式聲明的方式相差無幾(函數表達式聲明)
var sum = function(sum1,sum2){ return num1 + num2 }
在使用函數表達式聲明函數時,沒有必要使用函數名,如上所示,通過變量sum即可引用函數。
最後一種定義函數的方法是Function構造函數,Function構造函數可以接收任意數量的參數,但最後一個參數始終被看成是函數體,而前面的參數則枚舉出了新函數的參數。如下
var sum = new Function("num1","num2","return num1 + num2");//不推薦
從技術上來講,這的確是一個函數表達式,但是不推薦這樣做,因為這種語法會導致解析兩次代碼。
函數是對象,函數名是指針。由於函數名僅僅是指向函數的指針,因此函數名與包含對象指針的變量沒有什麽不同。換句話說,一個函數可能會有多個名字。
function sum(num1,num2){ returnnum1 + num2 ; } alert(sum(10,10))//20 var anthorSum = sum; alert(anthorSum(10,10))//20 sum = null; alert(anthorSum(10,10))//20
以上代碼首先定義了一個名為sum()的函數,然後,又聲明變量anthorSum,並將其設置為與sum相等,(註意:使用不帶圓括號的函數名是訪問函數指針,而非調用函數。)此時anthorSum與sum都指向了同一個函數,所以即使將sum設置為null,仍然可以正常調用anthorSum。
1.2 沒有重載
將函數名想象成指針,也有助於理解為什麽js中沒有函數重載這個概念。
function addSum(num1){ return num1 + 100 } function addSum(num1){ return num1 + 200 } var result = addSum(100) //300
這個例子聲明了兩個同名函數,結果是後面的函數覆蓋了前面的函數。以上代碼實際上與下面代碼沒什麽區別
var addSum = function (num1){ return num1 + 100 } addSum = function (num1){ return num1 + 200 } var result = addSum(100) //300
由上可以知道,在創建第二個函數的時候,實際上覆蓋了引用第一個函數的變量。
1.3 函數聲明與函數表達式
實際上,解析器在執行環境中加載數據時,對函數聲明和函數表達式並非一視同仁,解析器會率先讀取函數聲明,並使其在執行任何代碼之前可用。至於函數表達式,則必須等到解析器執行到它所在的代碼行,才會真正被解析執行。
alert(sum(10,10))//20 function sum(num1,num2){ return num1 + num2; }
以上代碼完全可以正常運行,因為在代碼開始執行之前,解析器就通過一個名為函數聲明提升的過程,讀取並將函數聲明添加到執行環境中。對代碼求值時,js引擎在第一遍會聲明函數並將其放在源代碼樹的頂部。所以即使聲明函數的代碼在調用它的代碼之後,js引擎也能把函數聲明提升到頂部。如果將函數聲明改成等價的函數表達式時,會在執行期間導致報錯
alert(sum(10,10)) var sum = function (num1,num2){ return num1 + num2; }
以上代碼會報錯的原因在於函數位於一個初始化的語句中,而不是一個函數聲明。換句話說,在執行到函數的語句之前,變量sum不會保存有對函數的引用;而且,由於第一行代碼會報錯,實際上也不會執行到下一行。
也可以同時使用函數聲明和函數表達式
var sum = function sum(){}
1.4 作為值的函數
因為js中函數名本身就是變量,所以函數可以直接作為值來使用。也就是說,不僅可以像傳遞參數一樣把一個函數傳遞給另一個函數,而且可以將函數作為另一個函數的結果返回。
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,‘Toms‘); alert(result2) // hello,Toms
這裏的callsomeFunction函數是同用的,即無論第一個參數值傳遞進來的是什麽函數,它都會返回執行第一個參數後的結果,要訪問函數的指針而不執行函數的話,就不要帶後面的兩個圓括號。因此上面函數傳遞的是add10和getGreeting,而不是他們的函數返回結果。
當然,可以從一個函數中返回另一個函數。例如,假設有一個對象數組,我們想要根據某個對象屬性對數組進行排序,而傳遞給數組sort()方法的比較函數要接收兩個參數,即要比較的值。可是,我們需要一種方式來指明按照哪個屬性來排序。要解決這個問題,可以定義一個函數。它接收一個屬性名,然後根據這個屬性名來創建一個比較函數。下面就是這個函數的定義
function cereteComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else{ return 0; } }; }
這個函數看起來有些復雜,但實際上無非就是一個函數中嵌套了另一個函數,而且內部函數前面加了一個return操作符,在內部函數接收到propertyName參數後,它會使用方括號表示法來取得給定屬性的值,取得了想要的屬性值之後,定義比較函數就非常簡單了。上面這個函數可以向下面例子這樣使用
var data = [{name: "Zachary",age:28},{name:"Nicholas",age:29}]; data.sort(createComparisonFunction("name")); alert(data(0).name); //Nicholas data.sort(createComparisonFunction("age")); alert(data(0).name); //Zachary
這裏,我們創建了一個包含兩個對象的數組,每個對象都包含了一個name屬性和age屬性,在默認情況下,sort方法會調用每個對象的toString()方法以確定他們的順序,但得到的結果往往並不符合人類的思維習慣。因此,我們調用createComarsionFunction("age")方法返回的比較函數,這個是按照對象的age屬性進行排序。
1.5 函數內部屬性
在函數內部,有兩個特殊的對象,arguments和this,其中,arguments是一個類數組對象,包含傳入函數的所有參數,雖然arguments的主要用途是用來保存函數參數,但這個對象還有一個名叫callee的屬性,該屬性是一個指針,執向擁有這個arguments對象的函數
function factorial(num){ if(num <= 1) { return 1; } else { return num * factorial(n - 1) } }
定義階乘函數一般都會用到遞歸算法,如上所示,在函數有名字,而且名字以後也不會變得情況下,這樣定義時沒有問題的,但問題是這個函數的執行與函數名factorial緊緊耦合在了一起,為了消除這種緊密耦合的現象,可以像下面這樣使用arguments.callee。
function factorial(num){ if(num <= 1) { return 1; } else { return num * arguments.callee(n - 1) } }
在這個重寫後的函數中,沒有引用到函數名,這樣,無論函數使用什麽名字,都能正常的完成遞歸調用。
var trueFactorial = factorial; factorial = function(){ return 0 ; } alert(trueFactorial(5)) //120 factorial(5) //0
在此,變量trueFactoraial獲得了factorial的值,實際上是在另一個位置保存了函數的指針,然後,我們又將一個簡單的返回0的函數賦值給factorial變量,如果像原來的factorial()那樣,不使用arguments.callee(),調用trueFactorial()就會返回0,可是,在解除了函數體內的代碼與函數名的耦合狀態後,trueFactorial()仍然能夠正常的返回階乘,至於factorial(),他現在只是一個返回0 的函數。
函數內部的另一個特殊對象是this,this引用的是函數據以執行的環境對象,或者也可以說是this值(當在網頁的全局作用域調用函數時,this對象引用的就是window)。
window.color = "red"; var o = {color:"blue"}; function sayColor(){ alert(this.color); } sayColor(); //red o.sayColor = sayColor(); o.sayColor(); //blue
JS函數類型(一)