昨天看了一篇文章《命名函數表達式探秘》,但我對函數表達式和聲明之間的區別還是不太清楚,所以查了些資料,翻譯了這篇文章。
文章很基礎,但是講的很清晰。
以下譯文:
函數:聲明和表達式
函數,像變量一樣,可以在代碼的任意地方定義它。
JS 提供了幾個方法去定義它們。
1.函數聲明 (Function Declaration)
2.函數表達式 (Function Expression)
3.通過調用new Function 返回。
語法
創建一個函數的基本方法是通過一個函數聲明。語法如下:
function
f(arg1, arg2, ...) {
... code ...
}
實現起來像這樣子:
1 |
function sayHi(name) { |
2 |
alert( "Hi, " +name) |
3 |
} |
4 |
5 |
sayHi( 'John' ) |
上面的例子聲明了一個帶有一個參數name,函數名為sayHi的函數,並且調用了它。
返回值
我們用return語句來返回一個值。
1 |
function sum(a, b) { |
2 |
return a+b |
3 |
} |
4 |
5 |
var result = sum(2,5) |
6 |
alert(result) |
如果函數不返回任何東西,它的結果 會是一個特殊的值,undefined。你可以從下面的代碼看到。
1 |
function getNothing() { |
2 |
// no return |
3 |
} |
4 |
5 |
var result = getNothing() |
6 |
7 |
alert(result) |
一個空的返回也是一樣:
1 |
function getNothing() { |
2 |
return |
3 |
} |
4 |
alert( getNothing() === undefined ) // true |
局部變量
一個函數可以包括變量,通過var來定義。這些變量被稱為局部變量,而且只能在函數內部使用。
1 |
function sum(a, b) { |
2 |
var sum = a + b |
3 |
4 |
return sum |
5 |
} |
函數聲明
函數的聲明的解析是在預執行(pre-execution)階段,也就是瀏覽器准備執行代碼的時候。
因此,通過函數聲明來定義函數,可以在定義前或后被調用。
下面的代碼是可以跑的:
1 |
function sayHi(name) { |
2 |
alert( "Hi, " +name) |
3 |
} |
4 |
5 |
sayHi( "John" ) |
換個順序也可以:
1 |
sayHi( "John" ) |
2 |
3 |
function sayHi(name) { |
4 |
alert( "Hi, " +name) |
5 |
} |
一個函數可以在代碼任意位置聲明。
比如,我們可能要根據條件來聲明不同的函數:
在寫這篇文章的時候(作者本人沒有給出他這這篇文章的時間——譯者注),嘗試在不同瀏覽器,火狐會拋出異常,其他兩個瀏覽器都會給出結果 2。
這是因為聲明是在代碼執行前解析的。根據規范(p.10.5),后面函數會覆蓋前面存在的同名函數。
到了執行時期,聲明會被忽略。所以if語句沒有起作用。
函數表達式
在JS里面,函數function和number、string一樣,是一階值(原文為:first-class value,有人也譯作:‘一等公民’)
只要可以放置變量的地方,都可以放置函數。在那個‘地方’用函數表達式的語法來聲明:function (arguments) {}
例如,你可以這樣使用變量:
var
f = 5
也可給f賦值一個函數表達式:
1 |
var f = function (name) { |
2 |
alert( "Hi, " + name + "!" ); |
3 |
} |
'function' 關鍵字什么時候用作 表達式,什么時候又用作 聲明?
規則很簡單
當js解析器看到function出現在主碼流( main code flow 不知如何翻譯),function被認為是聲明。
當 的一部分出現的,都會是表達式。
function
作為語句(statement)
(譯者認為,可以看能不能加分號來判斷,能加分號的都為語句(statement)。)
函數表達式會在執行流( execution flow)執行到它的時候才會創建函數。所以,函數表達式必須被執行了(此時函數才定義了)才能被調用。
01 |
var sayHi |
02 |
03 |
// sayHi() <-- can't call here, there is no sayHi yet |
04 |
05 |
if (1) { |
06 |
sayHi = function () { alert(1) } |
07 |
} else { |
08 |
sayHi = function () { alert(2) } |
09 |
} |
10 |
11 |
sayHi() |
請用函數聲明
有經驗的開發者,會將函數通過表達式來定義。
... code ... |
var f = function () { ... } |
... |
函數聲明會更短更有可讀性。
... code ... |
function f() { ... } |
... |
此外函數這樣定義可以在定義前調用。
在你真正需要用函數表達式的使用它!特別是在有條件判斷的時候定義函數。
函數是一個值
函數在JS里面是一個基本類型。我們甚至可以輸出它:
1 |
function f() { alert(1) } |
2 |
3 |
alert(f) |
上面的例子會輸出這個function,通常是輸出這個函數的源代碼
無論是函數聲明還是表達式,對函數來說,只是創建的時間不同而已。
傳遞一個函數
想所有值一樣,不管是以哪種方式定義,函數可以被賦值,作為參數傳遞到另外的函數等等。
1 |
function sayHi(name) { |
2 |
alert( "Hi, " +name) |
3 |
} |
4 |
5 |
var hi = sayHi // assign a function to another variable |
6 |
7 |
hi( "dude" ) // call the function |
函數的傳遞是引用類型。也就是說,函數會存在內存的某個位置,sayhi是它的一個引用(如果你懂指針的話)。當把sayhi賦給hi的時候,兩者都會引用到同一個函數。
函數可以接受另一個函數作為參數。
1 |
function runWithOne(f) { // runs given function with argument 1 |
2 |
f(1) |
3 |
} |
4 |
5 |
runWithOne( |
6 |
function (a){ alert(a) } |
7 |
) |
邏輯上,函數是一個行為(action)。所以傳遞一個函數就是傳遞了一個可以被其他程序啟動的行為。這個特性在js中廣泛使用。
在上面的例子中,我們創建了一個沒有名字的函數,而且沒有把它賦給其他變量。這種函數被稱為匿名函數。
就地運行
我們可以使用函數表達式創建一個函數並馬上執行它。
1 |
( function () { |
2 |
3 |
var a, b // local variables |
4 |
|
5 |
// ... // and the code |
6 |
7 |
})() |
我們涉及到局部變量的時候常用這種技巧。我們不想讓局部變量污染到全局變量,所以我們把代碼包在一個函數里面。
在代碼執行后,全局變量還是干凈的。這是一個最佳實踐。
為什么函數被包在括號里面?這是因為js只能就地運行函數表達式。
函數聲明不能像上面一樣執行。
1 |
function work() { |
2 |
// ... |
3 |
}() // syntax error |
即使我們把名字去掉,JS還是會發現function這個關鍵字,把它解析成函數聲明。
1 |
function () { // error, function declaration without name. |
2 |
// ... |
3 |
}() |
所以,只有把函數包在括號面才可行。然后解析器會認為它是語句中的一部分,把它提升為函數表達式。
如果函數明顯就是一個表達式,那么也不用包起來。
1 |
var result = function (a,b) { return a+b }(2,2) |
2 |
alert(result) // 4 |
上面的代碼,函數會被創建並且馬上執行。
下面代碼的結果是啥?為啥?
1 |
var a = 5 |
2 |
3 |
( function () { |
4 |
alert(a) |
5 |
})() |
P.S. 想清楚哦童鞋,有陷阱哦~Ok, 我已經提醒過你了。
還有一種方法去定義還是就是直接調用Function構造器:
它很少用,基本不用。
命名函數表達式
一個函數表達式可以有名字:
var f = function sayHi(name) { |
alert( "Hi, " +name) |
} |
這個語法被稱作命名函數表達式(named function expression or NFE), 但是在IE<9下表現都不正常。
在支持這個語法的瀏覽器下,這個函數的名字只可以在函數內部被訪問。
1 |
var f = function sayHi(name) { |
2 |
alert(sayHi) // outputs function |
3 |
} |
4 |
5 |
alert(sayHi) // error: undefined variable 'sayHi' |
IE在這種情況下卻創造了兩個函數對象:sayHi 和 f
1 |
var f = function g() { } |
2 |
3 |
// false in IE, error (g is undefined) in other browsers |
4 |
alert(f=== g) |
NFE 存在的目的是讓匿名函數可以遞歸。
setTimeout( function factorial( |
return n == 1 ? n : n*factorial(n-1) |
}, 100) |
函數命名
函數是行為(action),所以應該起一個動詞做名字,像get,red,calculateSum這些。
下面的情況是允許短函數名的:
1.函數在內嵌函數里面暫時使用的,和變量的邏輯一樣。
2.函數會在整個代碼里的多次被調用。一方面,你不會忘了它是干嘛用的,另一反面,你可以少些點代碼。
現實世界中,像'$','$$','$A',這些,會經常在JS函數庫里被使用。
其他情況下,還是應該用動詞,或者動詞做開頭的函數名字
總結
函數在JS里面是基本類型。它們可以根據需要被賦值,傳遞和調用。
一個不返回任何東西的函數其實返回了undefined
用動詞做函數名字。
Function Declaration | Function Expression |
---|---|
function 在主代碼流里面 |
function 作為函數表達式的一部分 |
函數在代碼執行前創建,可以在定義前被調用。 | 當代碼執行到的是創建,只能在定義后調用。 |
可以就地調用 |
轉載:
http://www.cnblogs.com/Xdoable/archive/2011/09/08/2171512.html