<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          JavaScript函數創建的細節

          時間:?2017-12-07閱讀:?789標簽:?函數

          如果你曾經了解或編寫過JavaScript,你可能已經注意到定義函數的方法有兩種。即便是對編程語言有更多經驗的人也很難理解這些差異。在這篇博客的第一部分,我們將深入探討函數聲明和函數表達式之間的差異。這篇文章將不包括不同類型的函數之間的差異(箭頭函數,async函數,普通函數等等),而是關注我們定義它的方式。 如果你理解挪威語,在這鏈接上還有一個視頻包括了相同的內容.

          這個系列的下一篇文章見: 關于變量定義的細節.


          兩種方法

          在Javascript中我們去定義函數的兩種方法分別是聲明和表達式:

          function myDeclaredFunction () {
            console.log('This is a function declaration');
          }
          

          const myFunctionExpression = function () {
            console.log('This is a function expression');
          };
          

          我們看到區分它們的一個地方是變量聲明和缺少標識符名稱。但是我們要深入挖掘,看看語法和用法上有什么區別。


          函數聲明

          在JavaScript中,定義函數的一種方法是在頂層或塊中,以關鍵字“Function”開始聲明。這種定義是作為JavaScript語法的一部分。在語言中, 語法即定義什么詞在什么的位置是合法的。

          函數聲明是一種函數式聲明(類似于“詞性”標注,如在自然語言中的名詞,動詞,等等),在語法中允許作為聲明列表選項的子集,這是聲明列表的一部分,也是語句塊的一部分等等。這些都是技術性和羅嗦的術語。但你可以這樣理解;如果一行在一個塊“{ }”中或者程序頂部以關鍵字function開頭,你可能有一個函數聲明。

          在語法上,我們說函數式聲明必須有一個名字,或者在文法上將叫標識符。 有一個例外是當函數被export default 導出的時候。但是在其他地方,你必須為你的函數聲明命名。

          通過這些規則,我們可以這樣舉例:

          // 這是一個函數聲明
          function myDeclaredFunction () { }
          
           // 這是一個函數聲明
          function myDeclaredFunction () { // block
             // 這是一個函數聲明
             function myDeclaredInnerFunction () { }
          }
          
          // 這是一個函數聲明 (無標識符)
          export default function () { }
          
          // 這不是一個有效的函數聲明和Javascript
          function () { }
          
          // 這不是一個函數聲明
          let func = function () { }
          

          用語法說明看起來比較復雜,但是相信我,大部分情況下你直接掃視代碼,能夠知道什么是聲明。在大部分情況,如果以后以function 開頭,并且沒有包裹在圓括號里面,它就是一個函數聲明。


          函數表達式

          如果我們了解函數聲明是函數以function關鍵字開頭定義在聲明列別選項中,那么我們應該知道所有的其他函數都是表達式。函數被let,var和const定義的都是表達式。函數被定義為其他函數的參數是表達式,函數定義為一個對象屬性中的值是表達式,事實上,函數定義在類語法糖中也是表達式。

          我們看到函數聲明大部分情況都需要標識符(default export 例外)。對于表達式來說也是一樣的嗎?不,函數表達式可以是匿名的(參考表達式的命名和匿名)。我們在上一個部分已經看到一個函數表達式的例子,不過我們可以有更多的例子:

          // 一個函數表達式
          let foo = function () {};
          
          // 和上面一樣,不過有命名的
          const bar = function name () {};
          
          // 箭頭函數也是表達式
          const baz = () => {};
          
          // 這是一個他被括號包裹了的情況,使它成了函數表達式
          (function () { });
          
          // 在上面我們可以看到,在聲明列表選項里面沒有一個以 function關鍵字開頭
          // 但是有一個例外,那就是作為setTimeout的參數時候。
          setTimeout(
            // This is a function expression
            function () { }
          );
          
          const obj = {
            // 函數表達式.
            foo () { }
          
            // 我們把它寫的的簡明一點也許更清楚:
            bar: function () { }
          };
          
          class MyClass {
            // 也是函數表達式.
            foo () { }
          }
          
          // 類只不過是語法糖,但是我們把它精簡下會更容易理解
          
          function MyClass () { }
          // 現在我們可以更清楚的看到這就是表達式
          Foo.prototype.foo = function foo () { };
          

          總結起來,我們可以說,在可以擁有像字符串和數字這樣值的地方,你擁有一個函數表達式。 我們已經看到語法指定我們是否有聲明或表達式。但這有什么不同嗎?這真的有關系嗎?事實證明,在某些情況下,這很重要。讓我們進一步挖掘。


          聲明與提升

          最顯著的區別,影響最大的是提升。函數聲明被提升到頂層,或者在函數中有一個函數聲明,提升到函數的頂端。在任何情況下,函數聲明都可以在聲明的地方使用它們。

          // 完全合法和有效的javascript
          console.log(add(40, 2));
          
          function add(a, b) {
            return a + b;
          }
          

          這實際上是一樣的:

          // 運行時提升到頂部
          function add(a, b) {
            return a + b;
          }
          
          console.log(add(40, 2));
          

          在JavaScript中,提升是一個常見的事情,所有變量聲明都會被提升,我們很快就會看到。你可能會問自己,為什么有人會這樣做?可能有多種原因。多數情況下,像在編程中一樣,是主觀的。在JavaScript中,很長一段時間以來,人們一直在按優先級對代碼進行優先排序。代碼越重要,它就排的越高。這樣做的一個效果是,在打開文件時,描述模塊的函數最容易看到。這在很大程度上依賴于提升,因為所有輔助函數都會在文件中進一步定義。這種做法正在逐漸消失,但仍有開發人員喜歡這種做法。

          在使用函數表達式進行變量聲明時也會發生提升。我們不會詳細討論這里的內容,但是JavaScript中的所有變量聲明都被提升了,但是不同的是,變量沒有賦值。因此,與前一個例子相比,這是行不通的:

          console.log(add(40, 2));
          // 會引起一個ReferenceError。我們聲明了add,但它是 undefined
          // 額外知識: 在頂部之間得區域和我們賦值給變量的范圍,我們稱作暫時性死區。
          
          const add = function add(a, b) {
            return a + b;
          };
          


          表達式的匿名和命名

          正如我們已經看到的,函數聲明在語法上不能匿名。但是表達式可以。有些時候可以方便的使用它們作為一個參數,比如“map”或“filter”之類的函數:

          // 匿名函數表達式
          const events = [1, 2, 3, 4].filter(function (i) {
            return i % 2 === 0;
          });
          
          // 或者有些人偏向于箭頭函數
          const events = [1, 2, 3, 4].filter(i => i % 2 === 0);
          

          但和其他便利性一樣,也可能有太多的東西。匿名函數可能導致難以調試,因為它混淆我們的堆棧跟蹤。嵌套的匿名函數很難瀏覽。

          我們可以通過在匿名函數中拋出錯誤來查看沒有名稱的堆棧跟蹤的輸出:

          try {
            (function() {
              throw new Error('Error');
            })();
          } catch (e) {
            console.log(e.stack);
          }
          

          會導致類似的事情:

          @file:///test.js:3:11
          @file:///test.js:2:4
          test.js:6:3
          

          如你所見。它還具有行和列的信息,但我們知道source-maps和轉換,源碼位置并不總是值得信賴。看下命名的函數::

          try {
            (function myFunction () {
              throw new Error('Error');
            })();
          } catch (e) {
            console.log(e.stack);
          }
          

          …我們得到:

          myFunction@file:///test.js:3:11
          @file:///test.js:2:13
          

          現在,在以后的JavaScript版本中,函數可以從變量名中推斷名稱:

          let myInferredFunction = function () { };
          console.log(myInferredFunction.name);
          // => myInferredFunction
          
          // 也可以是箭頭函數
          let myInferredFunction = () => { };
          console.log(myInferredFunction.name);
          // => myInferredFunction
          
          // Actual function name take precedence
          const myInferredFunction = function namedFunction () { };
          console.log(myInferredFunction.name);
          // => namedFunction
          

          在示例中要注意的幾件事。我們看到JavaScript中的函數就是我們所說的“一等公民”。我們通過函數表達式的例子看到了這一點。我們把函數作為為值。在JavaScript函數中是對象(但稱為可調用對象的特殊類型),因此它們具有屬性。這些屬性之一是‘名稱’,一個getter獲取器對應的函數名稱。引擎使用“堆棧中的名稱”蹤跡錯誤,但我們可以直接訪問它。

          我們還看到函數標識符優先于推斷名稱。雖然推斷命名是完全有效的,但是,當傳遞匿名函數作為參數時,我們無法推斷出名稱。這意味著我們仍然需要考慮匿名和調試的問題。在最近的趨勢中,使用箭頭函數隨處可見。但箭頭函數總是匿名的,除非名稱被推斷出來了。


          遞歸和引用推斷函數

          雖然調試可以通過推斷函數名獲得幫助,但是推斷和正確命名之間是有區別的。它可能更抽象,但在某些情況下可以創建實際的bug,并且很難清除。當使用遞歸函數時。我們可以把遞歸函數概括為調用它們自身的函數。

          遞歸在編程工具鏈中是一個有價值的工具。把一個問題分解成子集,然后在一個可能無限的范圍內解決一個問題。但是函數表達式上的推斷名稱可能會導致問題:變量可以被函數自身的外部重寫:

          let fibonacci = function (num) {
            if (num <= 1) return 1;
            return fibonacci(num - 1) + fibonacci(num - 2);
          }
          let fibCopy = fibonacci;
          fibonacci = function () {
            throw new Error('No, way!');
          }
          console.log(fibCopy(3));
          // => Error: No, way!
          

          但是,如果我們寫了一個命名的函數表達式(或函數聲明),這個方法仍然能像預期的那樣工作:

          let fibonacci = function fibonacci (num) {
            if (num <= 1) return 1;
            return fibonacci(num - 1) + fibonacci(num - 2);
          }
          let fibCopy = fibonacci;
          fibonacci = function () {
            throw new Error('No, way!');
          }
          console.log(fibCopy(3));
          // => 3
          // (如預期的那樣)
          

          正如最后一個示例所示,在斐波那契表達式的第二個調用遞歸調用中,函數指向到了實際上的命名函數表達式,而不是我們后面分配的那個會引起破壞的新函數。這意味著即使我們在外部作用域中重新賦值,當我們再次調用斐波那契函數,它仍指向正確的值。

          這是因為當我們把fibonacci設置為函數的標識符時,我們覆蓋了(重寫,優先)前面的變量。并且不再使用實際的變量,即使我們在外部作用域中重新分配了變量也無關緊要。我們只是重新分配變量,而不是指向在fibonacci函數里面。(譯者注:即函數聲明,在遞歸調用時引擎能夠正確識別本身的函數名,而匿名函數可能會被錯誤地推斷為外部定義的變量)

          這似乎是人為有意的。不過當使用遞歸,這實際上是一個合法的bug。如果發生了,可能很難排查。


          總結

          我們現在已經看到分配函數的方法有兩種。對于函數聲明,會把內容一起提升,對于函數表達式,不會把內容提升,但是如果我們把它賦值為變量,它的引用可能會被提升(譯者注:undefined)。表達式可能是匿名的,可以命名,可以隱式的從變量中活得命名。未命名的函數會容易導致難以調試,他會混淆堆棧追蹤。

          我們還看到,使用對函數表達式使用變量名與使用正真命名函數不同。在遞歸函數中,我們最終可以引用錯誤的值。

          可以同時使用函數聲明和表達式,這取決于風格和偏好。函數聲明帶來的提升能力,會讓你在頂部有更多的重要方法(當你第一次打開文件時可見),輔助函數在后面進一步定義,但是其他人喜歡自上而下地順序閱讀。這是個人的和主觀的,但現在你知道了差異和取舍,并且能夠做出一個最適合你的決定。(譯者注:一般而言,應該盡量先定義后使用)

          (完)


          原文中發現有些不錯的評論:
          Amarpreet Singh:

          有很多關于函數提升的訛傳。并不是函數聲明移動到頂部,因此可以定義之前訪問。在創建階段,編譯器把函數定義儲備在堆內,因此函數可以在定義前訪問。函數表達式在另一方面就像變量賦值,在代碼執行前,它們是未定義的。原文鏈接翻譯鏈接

          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/230

          你也許不知道的javascript高級函數

          高階函數是對其他函數進行操作的函數,可以將它們作為參數或通過返回它們。簡單來說,高階函數是一個函數,它接收函數作為參數或將函數作為輸出返回。例如Array.prototype.map,Array.prototype.filter,Array.prototype.reduce 都是一些高階函數。

          理解與使用JavaScript中的回調函數

          在JavaScript中,函數是第一類對象,這意味著函數可以像對象一樣按照第一類管理被使用。既然函數實際上是對象:它們能被“存儲”在變量中,能作為函數參數被傳遞,能在函數中被創建,能從函數中返回。

          JavaScript中的構造函數

          ECMAScript 中,構造函數與其他函數的唯一區別,就在于調用它們的方式不同。不過,構造函數畢竟也是函數,不存在定義構造函數的特殊語法。任何函數,只要通過 new 操作符來調用

          10個非常實用的Js工具函數

          生成一周時間new Array 創建的數組只是添加了length屬性,并沒有實際的內容。通過擴展后,變為可用數組用于循環,類型判斷判斷核心使用Object.prototype.toString,這種方式可以準確的判斷數據類型。

          Javascript之尾調用

          尾調用是函數式編程的一個重要的概念,本篇文章就來學習下尾調用相關的知識。有說過在一個函數中輸出一個函數,則這個函數可以被成為高階函數。本文的主角尾調用和它類似,如果一個函數返回的是另一個函數的調用結果,那么就被稱為尾調用。

          高階函數

          filter用于對數組進行過濾。 它創建一個新數組,新數組中的元素是通過檢查指定數組中符合條件的所有元素。注意:filter()不會對空數組進行檢測、不會改變原始數組

          JavaScript 函數式編程導論

          近年來,函數式編程(Functional Programming)已經成為了JavaScript社區中炙手可熱的主題之一,無論你是否欣賞這種編程理念,相信你都已經對它有所了解。即使是前幾年函數式編程尚未流行的時候

          javascript中的匿名方法(函數)是什么?

          方法(method)是通過對象調用的javascript函數。也就是說,方法也是函數,只是比較特殊的函數。JavaScript中的匿名方法即匿名函數是沒有函數名稱的函數。

          JavaScript中怎么調用函數?

          JavaScript怎么調用函數?其實在JavaScript中函數有4種調用方式。下面本篇文章就來給大家介紹一下JavaScript函數的4種調用方式,希望對大家有所幫助。

          CSS的var()函數怎么用?

          CSS中的var()函數可用于插入自定義屬性(有時稱為“css變量”)的值,而不是插入其他屬性值的任何部分。隨著sass,less預編譯的流行,css也隨即推出了變量定義var函數。var()函數,就如同sass和less等預編譯軟件一樣,可以定義變量并且進行對應的使用。

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网