- JavaScript的函式與物件一樣,都是第一級物件。
- C、Java或類似語言是以{ }來提供作用域,JavaScript則是以function區塊提供作用域。
ECMAScript中色含三類函式,每一類都有各自的特性。
函式宣告(簡稱FD)是指這樣的函式
- 有函式名
- 代碼位置在:要麼在程式級別或者直接在另外一個函式的函式體(Function Body)中
- 在進入函式上下文時創建的
- 會影響變數物件
函式宣告是以如下形式聲明的:
function exampleFunc() { ... } //要注意的是,函式宣告的句尾無需加(;)
這類函式的主要特性是:
-
只有它們可以影響變數物件(存儲在上下文的VO中)。
-
它們在執行代碼階段就已經存在了(因為FD在進入上下文階段就收集到了VO中)。
-
函式宣告在代碼中的位置:
function globalFD() { // 函式宣告可以直接在全域上下文中 function innerFD() {} // 或者在另外一個函式的函式體中 }
除了上述提到了兩個位置,其他位置均不能出現函式宣告。
函式運算式(簡稱:FE)是指這樣的函式:
- 代碼位置必須是在運算式的位置
- 函式名字可有可無
- 不會影響變數物件
- 在執行代碼階段時創建
這類函式的主要特性是:
-
它們的程式碼總是在運算式的位置
var foo = function () { ... }; //最簡單的運算式的例子,就是賦值運算式
上述程式碼將一個匿名FE賦值給了變數foo
,之後該函式就可以通過foo
被訪問了。
FE也可以有名字稱
var foo = function _foo() { ... };
有名稱的FE優點是,在FE的外部可以通過變數foo
調用函式,而在函式內部(比如遞迴調用),還可以用_foo
來調用函式。
要注意的是在foo
函式外部是無法調用_foo
的。
當FE有名字的時候,它很難與FD區分。不過,從定義還是可以區分它自,因為FE總是出現在運算式的位置。
var foo = function () { ... }; // 等於後面只能是運算是
(function foo() {...}); // 在括弧中(grouping operator)只可能是運算式
[function foo () {...}]; // 在陣列初始化中也只能是運算式
1, function foo () {...}; // 逗號操作符後只能跟運算式
!function foo () {...}; // 驚嘆號操作符也只能跟運算式
...
使用函式運算式能避免對變數物件造成"污染"!
最簡單的例子就是將函數作為參數傳遞給另外一個函數
function foo(callback) {
callback();
}
foo(function bar() {
...
});
foo(function baz() {
...
});
若將FE指定給變數,這樣變數就儲存對FE的引用,如此一來,函數就會保留在記憶體中,並在之後可以通過變數來訪問。
var foo = function _foo() { console.log("foo"); };
foo(); // output foo
到底該用函式宣告式還是函式表示式呢?
故名思義,立即函式是"創建後就馬上執行的函式""。
為什麼常在書中或其它地方看到,創建立即函式都有括弧包著函式?
從標準中來看,創建立即函式的規則是函式必須在表達式的位置並且調用它。
所以立即函式,它必須是FE,而不能是FD。
而創建運算式最簡單的方式就是使用上述提到的組操作符。因為在組操作符中只能是運算式。
標準中提到,運算式語句(ExpressionStatement)不能以左大括弧{開始,因為這樣一來就和程式碼區塊衝突了,也不能以function關鍵字開始,因為這樣又和函式宣告衝突了。
function () { ... }();
function foo() { ... }();
上述兩段程式碼都會拋出錯誤,只是原因不同。
-
第一個例子中,解譯器會以函式宣告來處理,因為它看到了是以function開始的。既然是個函式宣告,則缺少函數名(一個函式宣告其名字是必須的)。
-
第二個例子中,看上去已經有了名字(foo),應該會正確執行。然而,這裡還是會拋出語法錯誤,因為組操作符內部缺少運算式。這個例子中,函式宣告後面的()會被當組操作符來處理,而非函式調用的()。
function foo(x) { console.log(x); }(1); // 這裡只是組操作符,並非調用 foo(10); // 這裡是調用, 10
上述程式碼其實就是以下程式碼
function foo(x) { console.log(x); } // function declaration (1); // 含運算式的組操作符 (function () {}); // 另外一個組操作符包含一個函數運算式
回到創建立即函式上,如之前所述
它必須要是個函數運算式,而不能是函式宣告。
而創建運算式最簡單的方式就是使用上述提到的組操作符,因為在組操作符中只能是運算式。
如此一來解譯器將會以FE的方式來處理,這樣的函數將在執行階段創建出來,然後馬上執行,隨後被移除。
(function foo(x) {
console.log(x);
})(1); //output 1, 這樣就是函式調用,而不再是組操作符了
在下面的例子中,其括弧就不再是必須的了,因為函數本來就在運算式的位置了,解譯器自然會以FE來處理,並且會在執行程式碼階段創建該函數
var foo = {
bar : function (x) {
return x ;
}(1)
};
console.log(foo.bar); // output 1
如果要在函式創建後馬上進行函式調用,但函數不在運算式的位置時,括弧就是必須的。
除了使用括弧的方式將函數轉換成為FE之外,還有其他的方式
1, function () { ... }();
!function () {... }();
當然還有很多方式可以創建立即函式,不過,括弧是最通用也是最優雅的方式。
(function () {})();
!function () { ... }();
對於JavaScript,精簡是一種很重要的特性,因為在進入頁面後JavaScript會馬上被下載。如果JavaScript的程式碼能越小,頁面載入的時間也會變快。
比較下列程式碼
(function(){})() 16 characters
!function(){}() 15 characters
顯然,!function(){}()
比 (function(){})()
有更高的壓縮率。
var a = (function(){})()
var b = !function(){}()
上述函式沒有返回值,變數a
的值將會是undefined
,由於!undefined
的值為true
,變數b
會被設置成true
。
對於想要"否定返回值的函數"或"任何事物都必需返回非null
或非undefined
"的設計者來說,這是一項優點。
Javascript中有兩個特殊的物件:Object
與Function
,它們都可做為建構函式,用於創建物件。
在JavaScript中所有的物件都繼承自Object
原型,而Function
又充當了物件的建構函式,乍看之下有點難懂,下面用圖示來解示。
以下部份將用程式碼來解釋物件創建過程