JavaScript中函數(shù)聲明與函數(shù)表達(dá)式的區(qū)別詳解
前言
在ECMAScript中,有兩個(gè)最常用的創(chuàng)建函數(shù)對(duì)象的方法,即使用函數(shù)表達(dá)式或者使用函數(shù)聲明。對(duì)此,ECMAScript規(guī)范明確了一點(diǎn),即是,即函數(shù)聲明 必須始終帶有一個(gè)標(biāo)識(shí)符(Identifier),也就是我們所說(shuō)的函數(shù)名,而函數(shù)表達(dá)式則可以省略。下面看看這兩者的詳細(xì)區(qū)別介紹。
什么是 Function Declaration(函數(shù)聲明)?
Function Declaration 可以定義命名的函數(shù)變量,而無(wú)需給變量賦值。Function Declaration 是一種獨(dú)立的結(jié)構(gòu),不能嵌套在非功能模塊中??梢詫⑺惐葹?Variable Declaration(變量聲明)。就像 Variable Declaration 必須以“var”開(kāi)頭一樣,F(xiàn)unction Declaration 必須以“function”開(kāi)頭。
舉例來(lái)說(shuō)
function bar() { return 3; }
ECMA 5(13.0)定義語(yǔ)法:
function Identifier ( FormalParameterList[opt] ) { FunctionBody }
函數(shù)名在自身作用域和父作用域內(nèi)是可獲取的(否則就取不到函數(shù)了)。
function bar() { return 3; } bar() //3 bar //function
什么是 Function Expression(函數(shù)表達(dá)式)?
Function Expression 將函數(shù)定義為表達(dá)式語(yǔ)句(通常是變量賦值)的一部分。通過(guò) Function Expression 定義的函數(shù)可以是命名的,也可以是匿名的。Function Expression 不能以“function”開(kāi)頭(下面自調(diào)用的例子要用括號(hào)將其括起來(lái))。
舉例來(lái)說(shuō)
//anonymous function expression var a = function() { return 3; } //named function expression var a = function bar() { return 3; } //self invoking function expression (function sayHello() { alert("hello!"); })();
ECMA 5(13.0)定義語(yǔ)法:
function Identifieropt ( FormalParameterList[opt] ) { FunctionBody }
(這個(gè)定義感覺(jué)并不完整,因?yàn)樗雎粤艘粋€(gè)條件:外圍語(yǔ)句是表達(dá)式,并且不以“function”開(kāi)頭)
函數(shù)名(如果有的話)在作用域外是不可獲取的(與 Function Declaration 對(duì)比)。
那 Function Statement 是什么?
Function Statement 有時(shí)是 Function Declaration 的另一種說(shuō)法。但是 kangax 指出,在 mozilla 中,F(xiàn)unction Statement 是 Function Declaration 的一種拓展,使得 Function Declaration 語(yǔ)句可以在任何允許使用 statement(語(yǔ)句)的地方使用。但是 Function Statement 現(xiàn)在還不是標(biāo)準(zhǔn),所以不建議應(yīng)用在產(chǎn)品開(kāi)發(fā)中。
下面我們從一些小測(cè)試開(kāi)始。猜猜以下情況都會(huì)彈出什么結(jié)果?
題 1:
function foo(){ function bar() { return 3; } return bar(); function bar() { return 8; } } alert(foo());
題 2:
function foo(){ var bar = function() { return 3; }; return bar(); var bar = function() { return 8; }; } alert(foo());
題 3:
alert(foo()); function foo(){ var bar = function() { return 3; }; return bar(); var bar = function() { return 8; }; }
題 4:
function foo(){ return bar(); var bar = function() { return 3; }; var bar = function() { return 8; }; } alert(foo());
如果你的答案不是8、3、3和 [Type Error: bar is not a function] 的話,就繼續(xù)往下讀吧……(即使答對(duì)了也要繼續(xù)讀哦)
現(xiàn)在來(lái)解釋下前面的測(cè)試。
Question 1 用了 function declaration,也就是說(shuō)它們 get hoisted(被提升)了……
等一下,什么是 Hoisting?
這里引用 Ben Cherry的話:“Function declaration和function variable(函數(shù)變量)通常會(huì)被 JavaScript 解釋器移(‘hoisted')到當(dāng)前作用域頂部”。
function declaration 被提升時(shí),整個(gè)函數(shù)體都會(huì)隨之提升,所以 Question 1 的代碼經(jīng)過(guò)解釋器解釋后是像這樣運(yùn)行的:
//**Simulated processing sequence for Question 1** function foo(){ //define bar once function bar() { return 3; } //redefine it function bar() { return 8; } //return its invocation return bar(); //8 } alert(foo());
但是,我們經(jīng)常被告訴說(shuō),return 語(yǔ)句后面的代碼是運(yùn)行不到的啊……
執(zhí)行 JavaScript 過(guò)程中,有 Context(ECMA 5 將之分解為 LexicalEnvironment、VariableEnvironment 和 ThisBinding)和 Process(一系列按序調(diào)用的語(yǔ)句)兩個(gè)概念。當(dāng)程序進(jìn)入執(zhí)行域時(shí),Declaration 會(huì)造成 VariableEnvironment。它們不同于 Statement(比如 return),也不遵循 Statement 的運(yùn)行規(guī)則。
Function Expression 會(huì)被提升嗎?
這取決于表達(dá)式。比如 Question 2 中的第一個(gè)表達(dá)式:
var bar = function() { return 3; };
等號(hào)左邊的(var bar)是 Variable Declaration。Variable Declaration 會(huì)被提升,但是 Assignment Expression(賦值表達(dá)式)不會(huì)。所以當(dāng) bar 提升時(shí),解釋器會(huì)這樣初始化:var bar = undefined。而函數(shù)定義本身不會(huì)被提升。
(ECMA 5 12.2 帶有 initialzier(初始化器)的變量是在 VariableStatement 執(zhí)行時(shí),由 AssignmentExpression 賦值的,而不是在變量被創(chuàng)建時(shí)。)
因此 Question 2 的代碼會(huì)按以下順序運(yùn)行:
//**Simulated processing sequence for Question 2** function foo(){ //a declaration for each function expression var bar = undefined; var bar = undefined; //first Function Expression is executed bar = function() { return 3; }; // Function created by first Function Expression is invoked return bar(); // second Function Expression unreachable } alert(foo()); //3
你可能會(huì)說(shuō),這還能解釋的通,但是 Question 3 的答案錯(cuò)了,我在 Firebug 運(yùn)行會(huì)報(bào)錯(cuò)。
把代碼保存在 HTML 文件中,之后在 Firefox 上運(yùn)行試試?;蛘咴?IE8、Chrome 或 Safari 控制臺(tái)中運(yùn)行。顯然 Firebug 控制臺(tái)在“global(全局)”作用域(實(shí)際并不是全局的,而是特有的“Firebug”作用域——試著在 Firebug 控制臺(tái)中運(yùn)行“this == window”你就知道了)運(yùn)行代碼時(shí),不會(huì)將函數(shù)提升。
Question 3 和 Question 1 的邏輯相似。這次是 foo 函數(shù)被提升了。
Question 4 就很簡(jiǎn)單了,根本就沒(méi)有函數(shù)提升……
可以這么說(shuō),但是如果根本沒(méi)有提升的話,TypeError 會(huì)是“bar not defined”,而不是“bar not a function”。此例中確實(shí)沒(méi)有函數(shù)提升,但是有變量提升。因此 bar 在開(kāi)始就被聲明了,但是它的值并沒(méi)有定義。其它代碼都是按順序執(zhí)行的。
//**Simulated processing sequence for Question 4** function foo(){ //a declaration for each function expression var bar = undefined; var bar = undefined; return bar(); //TypeError: "bar not defined" //neither Function Expression is reached } alert(foo());
還應(yīng)該注意什么?
官方是禁止在非功能模塊(比如 if)中使用 Function Declaration 的。但是所有瀏覽器都支持,但是各自的解釋方式不同。
例如下面的代碼段在 Firefox 3.6 中會(huì)拋錯(cuò),因?yàn)樗鼘?Function Declaration 解釋成了 Function Statement(見(jiàn)上文),所以 x 沒(méi)有定義。但是在 IE8、Chrome 5 和 Safari 5 中,會(huì)返回函數(shù) x(和標(biāo)準(zhǔn)的 Function Declaration 一樣)。
function foo() { if(false) { function x() {}; } return x; } alert(foo());
可以看出使用 Function Declaration 可能會(huì)引起混淆,那么它有什么優(yōu)點(diǎn)嗎?
你可能會(huì)說(shuō) Function Declaration 很寬松啊——如果試圖在聲明前使用函數(shù),提升確實(shí)可以修正順序,以便函數(shù)可以正確調(diào)用。但是這種寬松不利于嚴(yán)謹(jǐn)?shù)木幋a,從長(zhǎng)遠(yuǎn)的角度來(lái)看,很有可能會(huì)促進(jìn)而不是阻止意外的發(fā)生。畢竟,程序員按特定的順序排列語(yǔ)句是有原因的。
那么還有其它理由支持 Function Expression 的嗎?
你猜呢?
1)Function Declaration 感覺(jué)像是要模仿 Java 風(fēng)格的方法聲明,但是 Java 方法和 JavaScript 并不一樣。在 JavaScript 中,函數(shù)是含值的 living 對(duì)象。Java 方法僅是對(duì)元數(shù)據(jù)的存儲(chǔ)。下面的兩段代碼都定義了函數(shù),但是只有 Function Expression 看著像創(chuàng)建了對(duì)象。
//Function Declaration function add(a,b) {return a + b}; //Function Expression var add = function(a,b) {return a + b};
2)Function Expression 用處更多。Function Declaration 只能作為“statement”孤立存在。它所能做的就是創(chuàng)建一個(gè)當(dāng)前作用域下的對(duì)象變量。相比之下,F(xiàn)unction Expression(根據(jù)定義)是大型結(jié)構(gòu)的一部分。如果想要?jiǎng)?chuàng)建匿名函數(shù)、給 prototype(原型)添加函數(shù)或是將函數(shù)用作其它對(duì)象的 property(屬性),都可以用 Function Expression。每當(dāng)用高階應(yīng)用,比如 curry 或 compose,創(chuàng)建新的函數(shù)時(shí)都是在用 Function Expression。Function Expression 和 Functional Programming(函數(shù)式編程)分不開(kāi)。
//Function Expression var sayHello = alert.curry("hello!");
Function Expression 有缺點(diǎn)嗎?
Function Expression 創(chuàng)建的函數(shù)大多是匿名的。比如下面的函數(shù)是匿名的,today 只是一個(gè)匿名函數(shù)的引用:
var today = function() {return new Date()}
這會(huì)有問(wèn)題嗎?多數(shù)情況下不會(huì),但是就像 Nick Fitzgerald 指出的,調(diào)試匿名函數(shù)會(huì)很煩。他建議使用 Named Function Expressions (NFEs)作為工作區(qū):
var today = function today() {return new Date()}
但是如 Asen Bozhilov 所說(shuō)(和 Kangax 文檔)NFEs 在 IE9 以下無(wú)法正確執(zhí)行。
結(jié)論
隨意放置的 Function Declaration 具有誤導(dǎo)性,并且很少有(如果有的話)情況,用 Function Expression 給變量賦值無(wú)法替代 Function Declaration。但是如果必須使用 Function Declaration 的話,將其放在所屬作用域頂部可以減少混淆。永遠(yuǎn)不要把 Function Declaration 放在 if 語(yǔ)句中。
說(shuō)了這么多,可能在你自己的情況下,F(xiàn)unction Declaration 還是很有用的。這沒(méi)什么。死記教條是危險(xiǎn)的,并且通常會(huì)造成代碼拐彎抹角。更重要的是你理解了概念,這樣就可以根據(jù)自身情況決定用哪種方式創(chuàng)建函數(shù)。以上就是本文的全部?jī)?nèi)容了,希望此文對(duì)大家在這方面有幫助。
相關(guān)文章
bootstrap table實(shí)現(xiàn)x-editable的行單元格編輯及解決數(shù)據(jù)Empty和支持多樣式問(wèn)題
本文著重解決x-editable編輯的數(shù)據(jù)動(dòng)態(tài)添加和顯示數(shù)據(jù)為Empty的問(wèn)題,還有給表格單元格的內(nèi)容設(shè)置多樣式,使得顯示多樣化,需要的朋友可以參考下2017-08-08JavaScript設(shè)置彈出式獨(dú)立窗口頁(yè)面和window的方法舉例詳解
window.open是網(wǎng)頁(yè)中經(jīng)常遇到的彈出窗口代碼,不是網(wǎng)絡(luò)中比較反感的那類彈出代碼,下面這篇文章主要給大家介紹了關(guān)于JavaScript設(shè)置彈出式獨(dú)立窗口頁(yè)面和window的方法,需要的朋友可以參考下2024-01-01JavaScript如何動(dòng)態(tài)監(jiān)聽(tīng)DOM元素高度詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript如何動(dòng)態(tài)監(jiān)聽(tīng)DOM元素高度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-07-07通過(guò)Javascript將數(shù)據(jù)導(dǎo)出到外部Excel文檔的函數(shù)代碼
通過(guò)Javascript將數(shù)據(jù)導(dǎo)出到外部Excel文檔的函數(shù)代碼,需要的朋友可以參考下2012-06-06分享一個(gè)自己寫的簡(jiǎn)單的javascript分頁(yè)組件
這篇文章主要分享一個(gè)自己寫的簡(jiǎn)單的javascript分頁(yè)組件,效果十分不錯(cuò),代碼也很詳盡,這里推薦給小伙伴們。2015-02-02JavaScript實(shí)現(xiàn)點(diǎn)擊單元格改變背景色的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)點(diǎn)擊單元格改變背景色的方法,涉及JavaScript響應(yīng)鼠標(biāo)事件動(dòng)態(tài)操作頁(yè)面元素屬性的相關(guān)技巧,需要的朋友可以參考下2016-02-02