接下來會學習到不同定義的函式,首先是函式表達式和函式宣告式,接著看函式作用域與 hosting 如何運作。
JavaScript 有兩個主要特色:
函式也是物件:
- 可在執行期、程式執行的過程中動態建立
- 可以指定給變數,也可將參考複製給其他變數,可以被擴充,而且除了少數其他狀況外,也可以被刪除
- 可以作為參數傳遞給其他韓式,也可以作為其他函式的回傳值
- 可以有自己的屬性和方法
使用 Function 建構式跟使用 eval 一樣糟,因為程式碼使用字串傳遞並執行,就必須跳脫字元,如此讀寫都很不方便。
JavaScript 中沒有大括號內的區域作用域,沒有區塊範圍,區塊並不產生有效的作用域,JavaScript 只有函式作用域,任何在函式裡面宣告的 var 變數 都是區域變數。
var add = function(a, b){
return a + b;
};
具名的函式表示式,又稱匿名函式,與函式宣告式的差別是函式物件的 name 屬性會是個空字串,具名的函式表示式和函式宣告式看起來相像,但表示式在結尾需要分號,而表達式不用。
當語法不能使用宣告式就可以使用表示式,例如:將函式物件作為參數傳遞、物件內定義方法。
函式宣告式只能出現在 program code 裡面,意思就是函式本體或全域空間中,他們定義無法賦值給變數或其他屬性,或作其他函式的參數。
對函式宣告式和具名函式表達式,name 屬性都有定義。對匿名函式表達式說,name 可能為 undefined 或空字串。
name 屬性也會用來遞迴呼叫自己,或是在除錯工具中顯示函式名稱,如果這兩種狀況都沒有需要,用不具名表達式會比較簡單也不囉唆。
所有變數無論被定義在函式中哪處,都會在幕後被提升到函式最前端。
function foo(){
console.log('global foo')
}
function bar(){
console.log('global bar');
}
function hoistMe(){
console.log(typeof foo); // 'function'
console.log(typeof bar); //'undefined'
foo(); // local foo
bar(); // TypeError:bar is not function
function foo(){
console.log('local foo');
}
// 變數 foo 和實作都被提升
var bar = function(){
console.log('local bar');
}
// 僅有變數 bar 被提升,不包含實作,所以是 undefined
}
hoistMe();
函數是物件,意思就是可以作為參數傳遞給其他函式。
回呼並非是一次性的匿名函式或者全域函式,而是某個物件的方法,如果回呼的方法使用了 this 去參考其所屬的物件,this 會參考到全域物件,解決方法是除了傳遞回呼函式,額外傳遞回呼函式所屬物件,並使用 call 改變 this 的指向。
當使用 setTimeout
或 setInterval
,函數在作為變數傳遞時沒有加上括號,因為你不是想立刻執行它,而是指向它,讓 setTimeout
或 setInterval
稍後可以使用。
有個函式做了一個工作,可能是一些初始化,接著就對其回傳值工作,回傳值剛好是另一個函式:
var setup = function(){
alert(1);
return function(){
alert(2)
}
}
var my = setup();
my();
var setup = function(){
var count = 0;
return function(){
return (count+=1);
}
}
var next = setup();
next(); //1
next(); //2
next(); //3
next(); //4
函式可以動態建立,且可以指派給變數。建立新函式,並指派給同一個變數,此變數原本指向的舊函數就會被覆蓋成新的。
var scareMe = function(){
alert('boo!');
scareMe = function(){
alert('double boo!');
}
}
scareMe();
scareMe();
使用這種模式可以顯著提升效能,當函式一部份不需要的狀況下,就可以使用自我定義的函式更新實作。這個模式另一個名字叫做懶惰的函式定義,因為這種函式在第一次使用之前都沒有正確定義。
此函數的缺點在它重新定義自身之前你加到原始函式的屬性都會遺失,如果使用不同名稱,例如新函式指派給另一個變數或是物件的另一個方法,那重新定義的部分就不會執行,而原始的函式本體就會執行。
以另一個例子來說,這次
scareMe()
函式要用第一級物件的使用方式:
- 加入一個新的屬性
- 將函式物件指派給新變數
- 函式也作為方法使用
// 1.加入一個新屬性
scareMe.property = "properly"
// 2.賦值給一個不同名稱
var prank = scareMe;
// 3.作為一個方法來使用
var spooky = {
boo: scareMe
}
// 用新的名稱呼叫
prank(); //boo
prank(); //boo
console.log(prank.property); // properly
// 用方法呼叫
spooky.boo(); //boo
spooky.boo(); //boo
console.log(spooky.boo.property); // properly
// 用自我定義函式呼叫
scareMe(); //double boo!
scareMe(); //double boo!
console.log(scareMe.property); //undefined
當自我定義函式被賦值給一個新變數時,他沒有照你預期,每一次呼叫 prank();
都會印出 boo
,同時覆蓋全域的 scareMe();
函式,但 prank();
自己仍保有舊有的定義,包含 property 屬性。
這些每次的呼叫都會重新覆蓋 scareMe();
的指標,所以最終呼叫它,它擁有的是第一次就被更新的主體,會印出 double boo!
,同時也沒有 property 屬性。
資料來源:《JavaScript 設計模式》 (JavaScript Patterns) Stoyan Stefanov 歐萊禮
筆記純屬推廣及分享,如有侵權,請告知。
Please advise to remove immediately if any infringement caused.