function 基本用法
函式的定義
在 JavaScript 中要定義函式,有以下幾種方法:
使用 function 關鍵字宣告函式:
function add1(x, y) { return x + y; } myOutput(add1(3, 5));
使用匿名函式表示式的宣告方式來定義函式:(named function expressions, NFE)
var add2 = function (x, y) { return x + y; }; myOutput(add2(4, 3));
在例一中,這種函式的宣告方式是一般語言中常見的方式,其中 add1 就是它的函式名稱,但其實它就是一個 variable ,當要叫用該函式時,我們都很直覺就會使用 add1() 來叫用。 以這樣的觀點來看例二,那就容易理解了,add2 是一個變數,它表示一個函式,要叫用它,就使用 add2() 即可。
使用具名函式表示式來定義函式:
var add3 = function MyAdd(x, y) { return x + y; }; myOutput(add3(4, 3));
對具名函式來說,這個名稱的存取範圍僅限於函式內部,在函數之外是看不到的,所以你不可以這麼叫用:
MyAdd(4, 3)
在 JavaScript 的除錯環境的 stack traces、call stacks、breakpoints 列表中, 若是使用匿名函數,大都只會顯示 anonymous 這樣的名稱, 如果是具名函數的話,就會清楚標示該函數的名稱,這對於除錯而言是很好用功能。
注意事項:
在 JavaScript 中定義 function 主要分成二種方式:(1) function expression (2) function declaration 。 在上面幾種函式定義方法中,有出現等號(=)的方式,表示它是一個「表示式」(expression),所以就必須以「;」結尾。 如果是使用「函式宣告」(function declaration),那麼就用「{}」包起來就可以。
使用表示式來定義函式,可以更突顯出函式就是物件,它就像其他物件一樣,不是特殊的語言結構。
函式的提升(Hoisting)行為
上面函式的定義分別使用了「表示式」與「宣告」二種方式,雖然他們作用是一樣的,但是它們卻有不一樣的 hoisting 行為。
使用「宣告」方式,你可以這麼叫用,因為 JavaScript 會把「宣告」提升到其所在區域內的頂端, 所以在程式碼中看起來,就變成在函式宣告前就可以叫用該函式。
test1(); function test1() { myOutput('test1') ; }
但是如果使用「表示式」方式,則不允許這麼叫用。 因為 JavaScript 只會把變數宣告部分提升到 scope 頂端,不會提升指定部分。
test2(); // test2 is not a function var test2 = function () { myOutput('test1'); };
function 也是物件
在許多程式設計語言中,函式和物件通常被視為兩種不同的東西。但是在 JavaScript 中,「函式」其實就是包含可執行程式碼的 Function 物件。 這意味 Function 可以被動態建立,儲存在變數、陣列和物件中,也能做為函式的「傳入參數」或「傳回結果」。 當你使用 function 關鍵字宣告一個函式,其實就是在定義 Function 物件。 因此,要特別記得,只要使用 new 運算子呼叫函式,就會傳回完全初始化的 Object 物件。
Javascript 中,函式除了可以被呼叫外,它還有以下功能,是一般程式語言比較少見的:
- function 是一個物件
- function 可以有自已的屬性和方法
- function 可以指定給變數
- function 可以當做參數
- function 可以在執行時才動態建立
- function 可以被擴充,也可以被刪除
function Person(_name) { this.name = _name; this.sayHello = function () { myOutput("hello " + this.name); } myOutput(this.name); } var person = new Person("vito"); //vito person.sayHello(); //hello vito
你也可以使用 Function 建構函式來建立函數物件。
var add4 = new Function("x", "y", "return x + y;"); myOutput(add4(2, 3));
回呼模式(call back)
什麼是回呼模式
下面這個例子,f 是一個函式參數,當 Say() 這個函式被呼叫時, 在它在定義中,僅知道要去執行某些事情,但是並不知道要去執行哪個事情,必須等到執行階段才能由參數來決定。 這種情況, hello() 或 hi() 這二個函式就被稱為回呼函式(callback function)。
function Say(f) { // 要做些事情.... f(); //... } function hello() { myOutput('HELLO'); } function hi() { myOutput('HI'); } Say(hi); Say(hello);
回呼可以是一個現有的函式,也可以是個匿名函式,例如你也可以直接這樣叫用:
Say( function () { myOutput('Bye Bye'); } );
傳遞參數給回呼函式
若要傳遞參數給回呼函式,做好有數種方式,底下這個範例直接使用 arguments 變數來表示傳進來的參數值。
function Cacular(callback) { return callback(arguments[1], arguments[2]); } function Add(a, b) { return a + b; } function Sub(a, b) { return a - b; } myOutput(Cacular(Add, 10, 5)); // 15 myOutput(Cacular(Sub, 10, 5)); // 5
非同步事件監聽器
在網頁中使用「非同步事件監聽器(event listener)」就是回呼模式的一種應用。 它可以協助你針對網頁中的特定事件,設定要執行的對應函式。 例如底下這個例子中的 setTimeout() 這個方法,當指定的時間到時,就會進行回呼。
var now = new Date() var doSomething = function () { //... //Date().fo myOutput('2...' + (new Date()).format("hh:mm:ss.ms")); }; myOutput('1...' + (new Date()).format("hh:mm:ss.ms")); setTimeout(doSomething, 1000); myOutput('3...' + (new Date()).format("hh:mm:ss.ms")); //1...15:22:39.42 //3...15:22:39.42 //2...15:22:40.49
下面這個簡單範例示範,如何在網頁中執行 click 事件的監聽。
document.addEventListener("click", callback, false); var i = 0; function callback() { i++; myOutput(i); }
回傳一個函式(return a function)
既然函式本身是一個物件,所以它也可以當作某個函式的回傳值。 也就是函式並不一定只能回傳某些資料,它也可以回傳另一個函式。
var FA = function () { myOutput('FA'); return function FB() { myOutput('FB'); } }; //呼叫FA(), 取得一個函式 var fa = FA(); //FA fa(); //FB fa(); //FB fa(); //FB
閉包(Closure)
在認識「閉包」之前,先看看底下這個例子:
var setup = function () { var counter = 0; myOutput(counter); return function () { counter += 1; myOutput(counter); } }; var next = setup(); // counter = 0 next(); // 1 next(); // 2 next(); // 3
在上面這個例子中,next 經由呼叫 setup 函式,取得一個函式,像這樣子的 next 就稱為「閉包(Closure)」。
一般函式在執行完畢時,內部的區域變數會被釋放掉,但如果在執行時產生了閉包。那閉包會保留其所在巢狀函式內的變數,讓他不至於被系統釋放掉。 所以閉包除了回傳的函式本身,它還擁有一組特殊的環境變數,這些環境變數就是閉包在建立時可存取的作用域中的所有變數。
自我定義函式(self-defining function)
JavaScritp 可以將函式指派給變數,若是你又建立了一個新函式,且指派給相同的變數,那麼舊的函式將被取代,而這個變數將指向新的函式。 如下列程式碼:
var hello = function () { myOutput("hello"); hello = function () { myOutput("hello world"); } } hello(); //hello hello(); //hello world
上面例子中,第一次呼叫 hello 時,函式會用一份新的實作重新定義並覆蓋掉自已,所以第二次呼叫 hello 時,執行的內容就與第一次不同。 這種例子的使用情境,大都是該函式第一次叫用時,必須執行一些初始他工作,而之後的叫用再也不須要這些工作,就可以使用這種自我定義函式來取代掉原先的函式。 這種函式因為之後做的事比原先的少,所以又稱為懶惰函式(lazy function definition)。
想想看,底下這個例子的結果,為什麼執行結果和想像中的不太一樣。
var hello = function () { myOutput("hello"); hello = function () { myOutput("hello world"); } } var hi = hello; hi(); //hello hi(); //hello hello(); //hello world hello(); //hello world
這是因為,當第一次叫用 hi() 時,它只會重新定義 hello 這個變數的指向,但自已仍等同舊的 hello() 定義。 所以不管 hi() 叫用幾次,輸出都是 hello ,而叫用 hello() ,輸出都是 hello world 。
立即函式(immediate function)
「立即函式」只是一種表示法,它可以讓函式在定義時,立刻執行,所以也稱為自我調用(self-invoking)或自我執行(self-executing)。例如:
var counter = 0; //註1 var count = function Count() { counter++; myOutput(counter); //1 }(); //註2 (function Count() { counter++; myOutput(counter); //2 }()); //註3 (function Count() { counter++; myOutput(counter); //2 })();
- 註1:在函式後面加上一組括號 () ,這樣就會讓函式立即執行。
- 註2:如果沒有將函式 assign 給變數,那麼就要再用一組括號包起整個函式。
- 註3:這是同上面的替代語法,不過 JSLint 只支援上面那種。
立即函式的參數
你也可以傳遞參數給立即函式,例如:
(function (day) { var week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; myOutput('今天' + week[day]); //今天星期二 } (2));
上面這類例子,其參數值通常來自 global variable 。另外,如果你要同時傳遞很多個全域變數,你也可以直接傳遞全域物件 this ,以避免傳遞太多變數。 然後就可以在函式內部,藉由 this 物件與外部的環境互動。
var day = (new Date()).getDay(); (function (global) { var week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; myOutput('今天' + week[global.day]); //今天星期二 } (this));
立即函式的回傳值
你也可以在立即函式使用 return ,並將結果 assing 給一個變數。 下面例子中的二種寫法都可以,因為,立即函式若要 assign 給變數,可以去掉外層括號。
var hello = (function () { return "hello"; } ()); myOutput(hello); var hello = function () { return "hello"; } (); myOutput(hello);
但是要注意下面的程式碼,這個例子中的 hello 表示一個函式,與上面完全是不一樣的意義。
var hello = function () { return "hello"; } ;
立即函式也可以回傳函式。
var hello = (function () { var msg = "hello"; return function () { return msg } } ()); myOutput(hello());
使用立即函式來計算物件的屬性值。
如果你要定義一個物件的屬性,該屬性值在生命週期內都不會變動,但是在定義時必須先經由計算取得,這時就可以利用立即函式來包裝這些運算。
var o = { area: (function () { var pi = 3.14; var radio = 5; return pi * radio * radio; } ()), getArea: function () { return this.area; } }; myOutput(o.area); myOutput(o.getArea());
立即函式的使用時機
先看看這段程式碼,我們宣告一個 week 陣列和一個 day 變數,以協助取得要輸出的資訊。
var week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; var day = (new Date()).getDay(); myOutput('今天' + week[day]); //今天星期二
上例中,week 和 day 都是全域變數,主要用以協助計算輸出的資訊,如果後續都沒有其他作用,那麼就可以使用「立即函式」來避免這個全域變數。 或許你會說,把它們定義在一個函式裡,再叫用這個函式來,也可以減少全域變數,但最簡便的方法還是使用「立即函式」。
(function () { var week = ['星期日', '星期一','星期二','星期三','星期四','星期五','星期六']; var day = (new Date()).getDay(); myOutput('今天' + week[day]); //今天星期二 }());
沒有留言:
張貼留言