函數聲明和函數表達式的區別

来源:http://www.cnblogs.com/ityiwei/archive/2016/08/25/5807897.html
-Advertisement-
Play Games

原文:Angus Croll 譯文:伯樂線上專欄作者 - 古魯伊 鏈接:http://web.jobbole.com/87534/ 點擊 → 瞭解如何加入專欄作者 我們從一些小測試開始。以下情況都會彈出什麼結果? 題 1: function foo(){ function bar() { retur ...


原文:Angus Croll

譯文:伯樂線上專欄作者 - 古魯伊

鏈接:http://web.jobbole.com/87534/

點擊 → 瞭解如何加入專欄作者

 

我們從一些小測試開始。以下情況都會彈出什麼結果?

 

題 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] 的話,就繼續往下讀吧……(即使答對了也要繼續讀哦)

 

什麼是 Function Declaration(函數聲明)?

 

Function Declaration 可以定義命名的函數變數,而無需給變數賦值。Function Declaration 是一種獨立的結構,不能嵌套在非功能模塊中。可以將它類比為 Variable Declaration(變數聲明)。就像 Variable Declaration 必須以“var”開頭一樣,Function Declaration 必須以“function”開頭。

 

e.g.

 

function bar() {

    return 3;

}

 

ECMA 5(13.0)定義語法:

 

function Identifier ( FormalParameterList[opt] ) { FunctionBody }

 

函數名在自身作用域和父作用域內是可獲取的(否則就取不到函數了)。

 

function bar() {

    return 3;

}

 

bar() //3

bar  //function

 

什麼是 Function Expression(函數表達式)?

 

Function Expression 將函數定義為表達式語句(通常是變數賦值)的一部分。通過 Function Expression 定義的函數可以是命名的,也可以是匿名的。Function Expression 不能以“function”開頭(下麵自調用的例子要用括弧將其括起來)。

 

e.g.

 

//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)定義語法:

 

function Identifieropt ( FormalParameterList[opt] ) { FunctionBody }

 

(這個定義感覺並不完整,因為它忽略了一個條件:外圍語句是表達式,並且不以“function”開頭)

 

函數名(如果有的話)在作用域外是不可獲取的(與 Function Declaration 對比)。

 

那 Function Statement 是什麼?

 

Function Statement 有時是 Function Declaration 的另一種說法。但是 kangax 指出,在 mozilla 中,Function Statement 是 Function Declaration 的一種拓展,使得 Function Declaration 語句可以在任何允許使用 statement(語句)的地方使用。但是 Function Statement 現在還不是標準,所以不建議應用在產品開發中。

 

現在來解釋下前面的測試。

 

Question 1 用了 function declaration,也就是說它們 get hoisted(被提升)了……

 

等一下,什麼是 Hoisting?

 

這裡引用 Ben Cherry的話:“Function declaration和function variable(函數變數)通常會被 JavaScript 解釋器移(‘hoisted’)到當前作用域頂部”。

 

function declaration 被提升時,整個函數體都會隨之提升,所以 Question 1 的代碼經過解釋器解釋後是像這樣運行的:

 

//**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());

 

但是,我們經常被告訴說,return 語句後面的代碼是運行不到的啊……

 

執行 JavaScript 過程中,有 Context(ECMA 5 將之分解為 LexicalEnvironment、VariableEnvironment 和 ThisBinding)和 Process(一系列按序調用的語句)兩個概念。當程式進入執行域時,Declaration 會造成 VariableEnvironment。它們不同於 Statement(比如 return),也不遵循 Statement 的運行規則。

 

Function Expression 會被提升嗎?

 

這取決於表達式。比如 Question 2 中的第一個表達式:

 

var bar = function() {

    return 3;

};

 

等號左邊的(var bar)是 Variable Declaration。Variable Declaration 會被提升,但是 Assignment Expression(賦值表達式)不會。所以當 bar 提升時,解釋器會這樣初始化:var bar = undefined。而函數定義本身不會被提升。

 

(ECMA 5 12.2 帶有 initialzier(初始化器)的變數是在 VariableStatement 執行時,由 AssignmentExpression 賦值的,而不是在變數被創建時。)

 

因此 Question 2 的代碼會按以下順序運行:

 

//**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

 

你可能會說,這還能解釋的通,但是 Question 3 的答案錯了,我在 Firebug 運行會報錯。

 

把代碼保存在 HTML 文件中,之後在 Firefox 上運行試試。或者在 IE8、Chrome 或 Safari 控制臺中運行。顯然 Firebug 控制台在“global(全局)”作用域(實際並不是全局的,而是特有的“Firebug”作用域——試著在 Firebug 控制臺中運行“this == window”你就知道了)運行代碼時,不會將函數提升。

 

Question 3 和 Question 1 的邏輯相似。這次是 foo 函數被提升了。

 

Question 4 就很簡單了,根本就沒有函數提升……

 

可以這麼說,但是如果根本沒有提升的話,TypeError 會是“bar not defined”,而不是“bar not a function”。此例中確實沒有函數提升,但是有變數提升。因此 bar 在開始就被聲明瞭,但是它的值並沒有定義。其它代碼都是按順序執行的。

 

//**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());

 

還應該註意什麼?

 

官方是禁止在非功能模塊(比如 if)中使用 Function Declaration 的。但是所有瀏覽器都支持,但是各自的解釋方式不同。

 

例如下麵的代碼段在 Firefox 3.6 中會拋錯,因為它將 Function Declaration 解釋成了 Function Statement(見上文),所以 x 沒有定義。但是在 IE8、Chrome 5 和 Safari 5 中,會返回函數 x(和標準的 Function Declaration 一樣)。

 

function foo() {

    if(false) {

        function x() {};

    }

    return x;

}

alert(foo());

 

可以看出使用 Function Declaration 可能會引起混淆,那麼它有什麼優點嗎?

 

你可能會說 Function Declaration 很寬鬆啊——如果試圖在聲明前使用函數,提升確實可以修正順序,以便函數可以正確調用。但是這種寬鬆不利於嚴謹的編碼,從長遠的角度來看,很有可能會促進而不是阻止意外的發生。畢竟,程式員按特定的順序排列語句是有原因的。

 

那麼還有其它理由支持 Function Expression 的嗎?

 

你猜呢?

 

1)Function Declaration 感覺像是要模仿 Java 風格的方法聲明,但是 Java 方法和 JavaScript 並不一樣。在 JavaScript 中,函數是含值的 living 對象。Java 方法僅是對元數據的存儲。下麵的兩段代碼都定義了函數,但是只有 Function Expression 看著像創建了對象。

 

//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”孤立存在。它所能做的就是創建一個當前作用域下的對象變數。相比之下,Function Expression(根據定義)是大型結構的一部分。如果想要創建匿名函數、給 prototype(原型)添加函數或是將函數用作其它對象的 property(屬性),都可以用 Function Expression。每當用高階應用,比如 curry 或 compose,創建新的函數時都是在用 Function Expression。Function Expression 和 Functional Programming(函數式編程)分不開。

 

//Function Expression

var sayHello = alert.curry("hello!");

 

Function Expression 有缺點嗎?

 

Function Expression 創建的函數大多是匿名的。比如下麵的函數是匿名的,today 只是一個匿名函數的引用:

 

var today = function() {return new Date()}

 

這會有問題嗎?多數情況下不會,但是就像 Nick Fitzgerald 指出的,調試匿名函數會很煩。他建議使用 Named Function Expressions (NFEs)作為工作區:

 

var today = function today() {return new Date()}

 

但是如 Asen Bozhilov 所說(和 Kangax 文檔)NFEs 在 IE9 以下無法正確執行。

 

結論

 

隨意放置的 Function Declaration 具有誤導性,並且很少有(如果有的話)情況,用 Function Expression 給變數賦值無法替代 Function Declaration。但是如果必須使用 Function Declaration 的話,將其放在所屬作用域頂部可以減少混淆。永遠不要把 Function Declaration 放在 if 語句中。

 

說了這麼多,可能在你自己的情況下,Function Declaration 還是很有用的。這沒什麼。死記教條是危險的,並且通常會造成代碼拐彎抹角。更重要的是你理解了概念,這樣就可以根據自身情況決定用哪種方式創建函數。我希望此文在這方面有幫助。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先必須要說的是,this的指向在函數定義的時候是確定不了的,只有函數執行的時候才能確定this到底指向誰,實際上this的最終指向的是那個調用它的對象(這句話有些問題,後面會解釋為什麼會有問題,雖然網上大部分的文章都是這樣說的,雖然在很多情況下那樣去理解不會出什麼問題,但是實際上那樣理解是不准確的 ...
  • 上一篇談new關鍵字也是給這一篇寫關於原型的文章買個伏筆,我對原型的理解可能會有偏差,如有錯誤,望指正一定修改,望各位道友如果想真正的理解原型的概念一定要再看完各方言論再回歸教材。 言歸正傳談原型,首先原型是一個對象,無論什麼時候,只要創建了一個函數,就會按照規則創建一個Prototype屬性,這個 ...
  • [1]通用形式 [2]首尾索引 [3]奇偶索引 [4]範圍索引 ...
  • HTML5 支持直接在瀏覽器中播放音頻和視頻文件,不需要使用Abode Flash這樣的插件。 1. 使用 video 元素 可以用video 元素在網頁里嵌入視頻內容。 其基本用法如下: 此例顯示效果如下: 如果瀏覽器不支持video元素或者無法播放視頻那麼備用內容(開始和結束標簽之內的內容)就會 ...
  • 原型和閉包算是JavaScript中最常見,最難以理解,最容易被當做問題的兩個部分,當然還有它們的延伸,如作用域鏈,繼承等等吧,我最近也是各種看,各種翻,記錄點自己的心得,寫寫總會讓自己的理解更深一些。(跟標題的關係不大啦,就感慨句,每次總感覺自己懂了,再翻還是收穫滿滿) 先談一下JavaScrip ...
  • 從網上好了好久,沒找到自己要的,自己寫了一下。 ...
  • 前言: 昨天的文章中簡單的介紹了Egret白鷺引擎從安裝到基本的使用配置等問題,今天著重介紹H5小游戲開發的起步階段,如Wing面板的使用,素材的處理,類的說明,開始佈局等等。 整體概況: 根據上一篇文章的提示,我們找到index.html,將幀率data-frame-rate修改成60。根據設計圖 ...
  • 截止到ES6,共有6種聲明變數的方法,分別是var 、function以及新增的let、const、import和class; 我們通常的賦值方法是: es6給我們提供了一種嶄新賦值方式:解構賦值; 例如我們需要聲明3個變數,我們用傳統的賦值方式和解構賦值做一個比較; es5: es6: 是不是很方 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...