2015年8月27日 星期四

JavaScript的同名函式問題及輸入參數的陣列特性

最近在工作中發現了一個以前沒有注意到的問題,找到了解決方法及解釋,所以特在此做了一個紀錄。

在Java中,很多人都應該有同名函式,不同函式簽名的多載Overload概念,但是在JavaScript中,是不能把這樣的觀念一併套用的。

我們來看一下以下的程式碼:
<html>
<body>

<script type="text/javascript">
function foo(a) {
   alert('foo(a) is called!');
}
function foo(a,b,c) {
    alert('foo(a,b,c) is called!');
}
foo('a');
</script>

</body>
</html>

如果有寫過Java但不熟JavaScript的話,很容易就會想說跳出的視窗應該是 "foo(a) is called!" 的吧,不過事實上並不是這樣的。

事實上跳出的事窗會是 "foo(a,b,c) is called!",也就是說 foo(a) 這個function跟本就沒被呼叫到,很顯然的,JavaScript並不會因為不同的方法簽名呼叫就使用不同方法簽名的方法,也就是沒有Java常見的多載(overload)特性。

其實在JavaScript中,函式的名稱只是一個類似指標(C語言)或參考(Java的reference)的查西,在每一次指定function內容及名稱時,名稱指向的位置都會改變,上述的例子中,第一次foo指向一個function及記憶體位置,而第二次又指向了另外一個function內容及記憶體位置,所以foo(a)就沒用了,最後foo代表的就是foo(a,b,c)。

在JavaScript中,呼叫方法function)時,輸入的參數是以類似陣列的方式傳進方法中的,所以其實方法簽明中的輸入參數個數並不是很重要,甚至可以傳入參數給方法簽名沒有輸入參數的方法(當然在方法簽名上標名變數名稱有利於程式易讀性及使用)。

如以下的程式碼是完全可以成功且正確的執行的:
<html>
<body>

<script type="text/javascript">
function sum() {   
   var inputArguments = sum.arguments;
   var answer = 0;
   for (i=0 ; i < inputArguments.length ; i++){
      answer = answer + inputArguments[i];
   }
   alert('輸入了' + inputArguments.length + '個數字,' + '總合為:' + answer);
}
sum(1);
sum(1,2);
sum(1,2,3);
</script>

</body>
</html>

執行後將可以看到三個視窗跳出來,分別顯示如下文字:
輸入了1個數字,總合為:1
輸入了2個數字,總合為:3
輸入了3個數字,總合為:6

可以發現,在JavaScript中,方法本身其實就像是一個物件,例如在上例中 sum() 本身就像是一個物件,可以用sum.arguments來存取輸入的參數,而輸入的參數也是像一個物件,可以存取其length及各元素的值。

而也因為 sum 就代表名為 sum 的方法,所以如果我們用方法的名稱去宣告一個新變數是會發生問題的,例如像下面這個例子,就會發生 TypeError: Cannot read property 'arguments' of undefined 的錯誤 ,因為此時sum()代表呼叫sum函式,而使用了 var sum=0 時,sum.arguments.length的sum變成指向一個sum()中的sum屬性了,所以是還沒defined的(這時真正執行的順序是var sum; --> alert(sum.arguments.length); --> alert(sum); 請參考 "Javascript的變量聲明提昇"):

<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(sum.arguments.length);  //這裡的sum是指sum()中的sum屬性,但因沒有定義而會出錯
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2); //加括號時,sum指的是名為sum的function
</script>

</body>
</html>

那正確的寫法是什麼呢?事實上argument這個屬性是可以直接在function裡使用而不用在前面特別指定function名稱的,所以這樣寫就不會報錯了:
<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(arguments.length);
       var sum = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2);
//輸出為:
//2
//0
</script>

</body>
</html>


如果這樣的話,也就是沒有下var sum=0,即沒有跟sum()的名稱衝突的話,也是可以的,此時sum就是function了
<html>
<body>

<script type="text/javascript">
function sum() {   
    try{
       alert(arguments.length);
       var sum2 = 0;
       alert(sum);
    }catch(error){
       alert(error);
    }
}
sum(1,2);
//輸出為:
//2
//function sum() {   
//    try{
//       alert(arguments.length);
//       //alert(typeof(sum));
//       var sum2 = 0;
//       alert(sum);
//    }catch(error){
//       alert(error);
//    }
//}
</script>

</body>
</html>

參考資料:
  1. Javascript同名函式
  2. The arguments array- the secret to robust functions

沒有留言 :

張貼留言