函數物件
1.1 建立函數
建立JavaScript函數的一種不常用的方式(幾乎沒有人用)是透過new運算元來作用於Function“建構器」:
var funcName = new Function( [argname1, [... argnameN,]] body );
參數清單中可以有任意多的參數,然後緊接著是函數體,例如:
var add = new Function("x", "y", "return(x+y)"); print(add(2, 4));
將會列印結果:
6
但是,誰會用如此難用的方式來創建一個函數呢?如果函數體比較複雜,那拼接這個String要花費很大的力氣,所以JavaScript提供了一種語法糖,即通過字面量來創建函數:
function add(x, y){ return x + y; }
或:
var add = function(x, y){ return x + y; }
事實上,這樣的語法糖更容易使傳統領域的程式設計師產生誤解,function關鍵字會呼叫Function來new一個對象,並將參數表和函數體準確的傳遞給Function的建構器。
通常來說,在全域作用域(作用域將在下一節詳細介紹)內聲明一個對象,只不過是對一個屬性賦值而已,比如上例中的add函數,事實上只是為全局對象添加了一個屬性,屬性名為add,而屬性的值是一個對象,即function(x, y){return x+y;},理解這一點很重要,這條語句在語法上跟:
var str = "This is a string";
並無二致。都是給全域物件動態的增加一個新的屬性,如此而已。
為了說明函數跟其他的物件一樣,都是作為一個獨立的物件而存在於JavaScript的運行系統,我們不妨看這樣一個例子:
function p(){ print("invoke p by ()"); } p.id = "func"; p.type = "function"; print(p); print(p.id+":"+p.type); print(p());
沒有錯,p雖然引用了一個匿名函數(物件),但是同時又可以擁有屬性,完全跟其他物件一樣,運行結果如下:
function (){ print("invoke p by ()"); } func:function invoke p by ()
1.2 函數的參數
在JavaScript中,函數的參數是比較有趣的,例如,你可以將任意多的參數傳遞給一個函數,即使這個函數宣告時並未制定形式參數,例如:
function adPrint(str, len, option){ var s = str || "default"; var l = len || s.length; var o = option || "i"; s = s.substring(0, l); switch(o){ case "u": s = s.toUpperCase(); break; case "l": s = s.toLowerCase(); break; default: break; } print(s); } adPrint("Hello, world"); adPrint("Hello, world", 5); adPrint("Hello, world", 5, "l");//lower case adPrint("Hello, world", 5, "u");//upper case
函數adPrint在宣告時接受三個形式參數:要列印的字串,要列印的長度,是否轉換為大小寫的標記。但是在呼叫的時候,我們可以依序傳遞給adPrint一個參數,兩個參數,或是三個參數(甚至可以傳遞給它多於3個,沒有關係),運行結果如下:
Hello, world Hello hello HELLO
事實上,JavaScript在處理函數的參數時,與其他編譯型的語言不一樣,解釋器傳遞給函數的是一個類似陣列的內部值,叫arguments,這個在函數物件產生的時候就被初始化了。例如我們傳遞給adPrint一個參數的情況下,其他兩個參數分別為undefined.這樣,我們可以才adPrint函數內部處理那些undefined參數,從而可以向外部公開:我們可以處理任意參數。
我們透過另一個例子來討論這個神奇的arguments:
function sum(){ var result = 0; for(var i = 0, len = arguments.length; i < len; i++){ var current = arguments[i]; if(isNaN(current)){ throw new Error("not a number exception"); }else{ result += current; } } return result; } print(sum(10, 20, 30, 40, 50)); print(sum(4, 8, 15, 16, 23, 42));//《迷失》上那串神奇的数字 print(sum("new"));
函數sum沒有顯式的形參,而我們又可以動態的傳遞給其任意多的參數,那麼,如何在sum函數中如何引用這些參數呢?這裡就需要用到arguments這個偽數組了,運行結果如下:
150 108 Error: not a number exception
函數作用域
作用域的概念在幾乎所有的主流語言中都有體現,在JavaScript中,則有其特殊性:JavaScript中的變數作用域為函數體內有效,而無區塊作用域,我們在Java語言中,可以這樣定義for循環區塊中的下標變數:
public void method(){ for(int i = 0; i < obj1.length; i++){ //do something here; } //此时的i为未定义 for(int i = 0; i < obj2.length; i++){ //do something else; } }
而在JavaScript中:
function func(){ for(var i = 0; i < array.length; i++){ //do something here. } //此时i仍然有值,及I == array.length print(i);//i == array.length; }
JavaScript的函數是在局部作用域內運作的,在局部作用域內運行的函數體可以存取其外層的(可能是全域作用域)的變數和函數。 JavaScript的作用域為詞法作用域,所謂詞法作用域是說,其作用域為在定義時(詞法分析時)就確定下來的,而並非在執行時確定,如下例:
var str = "global"; function scopeTest(){ print(str); var str = "local"; print(str); } scopeTest();
運行結果是什麼?初學者很可能得出這樣的答案:
global local
而正確的結果應該是:
undefined local
因為在函數scopeTest的定義中,預先存取了未宣告的變數str,然後才對str變數進行初始化,所以第一個print(str)會回傳undifined錯誤。那為什麼函數這個時候不去存取外部的str變數呢?這是因為,在詞法分析結束後,建構作用域鏈的時候,會將函數內定義的var變數放入該鏈,因此str在整個函數scopeTest內都是可見的(從函數體的第一行到最後一行),由於str變數本身是未定義的,程式順序執行,到第一行就會回傳未定義,第二行為str賦值,所以第三行的print(str)將返回”local”。
以上是JavaScript函數物件建立、參數和作用域實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!