有兩種方法可以在JavaScript中創建函數,即「函數聲明」和「函數表達式」。我相信這是Doug Crockford最好的解釋,他指出,除非「函數」是給定行上的第一組字符,否則您正在執行函數表達式(不是聲明)。
功能聲明是挑剔的生物。當你看到它們時你會認出它們。他們是這樣的:
function foo() { /* ... */ }
他們總是一個名字(它的報應)和名字被局部範圍在其下函數聲明爲詞法環境。所以如果你在全局上下文中執行一個函數聲明,那麼這個函數可以通過它的全局名稱被引用。如果你在一個函數中執行它,那麼該函數的名字只能在該函數中被引用,並且在該函數中聲明的任何函數都可以被引用。
我認爲這種聲明函數的方法中最重要的方面就是函數初始化會被提升到當前詞法上下文的頂部。因此,你應該永遠,永遠使用函數聲明中的條件,比如這個:
//DON'T DO THIS!
if (x) {
function foo() { return 1; }
} else {
function foo() { return 2; }
}
foo(); //will always be 2, regardless of the value of x.
函數表達式略有不同。通常情況下,他們直接分配給一個變量,像這樣:
var foo = function() { /* ... */ };
這是幾乎相同的函數聲明,除了上述初始化不懸掛。所以你可以做到以下幾點:
var foo;
if (x) {
foo = function() { return 1; };
} else {
foo = function() { return 2; };
}
foo(); //will be 1 or 2, depending on the truthy-ness of x.
那麼,回到原來的問題。函數表達式也可以有一個名稱,儘管它不是必需的,並且它沒有作用於聲明函數的上下文(與函數聲明一樣)。相反,它將範圍限定在函數自己的詞彙上下文中。這在某些情況下非常有用。我個人最喜歡的是這個模式:
(function foo() {
//Do something.
setTimeout(foo, 1000);
}());
foo; //undefined
因爲單詞「功能」前的括號,這是一個函數表達式和名稱僅供內部作用域。但沒關係,因爲我們只需要在內部調用它(通過setTimeout()
)。結果是該函數將立即執行一次,然後在執行完成後每秒鐘左右重新執行一次。這比使用setInterval()
更安全,因爲它會在重新計劃自身之前一直等待執行完畢,從而防止可能導致錯誤執行和/或「支配」JavaScript線程的重疊。
從本質上講,命名函數表達式的使用是有限的,但是當你需要它時,它是非常強大的。
你的名字和你的地址有什麼區別? –
除了名稱/地址類比之外,實際上可以在bar()的作用域內調用bar(),但在bar()的作用域外,必須使用foo()。 – jeremy
@HotLicks - 這種比喻毫無意義。 'foo'和'bar'在這裏都是清晰的名字。他們都提到相同的*地址*。這只是在新功能的範圍內纔可用。 –