1. 程式人生 > >JS函數類型(一)

JS函數類型(一)

根據 能夠 null 一個 num 數位 環境 OS AS

一、函數類型

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){
   return
num1 + 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函數類型(一)