Функциональные выражения в JavaScript

    В JavaScript есть множество способов сделать одно и то же. В этом есть и хорошее, и плохое. Для новичка это точно плохо, так как ему придется не только изучить большее количество информации, но и появится больше мест для совершения потенциальных ошибок. Это может происходить при определении функций.

    Есть множество различных способов объявить функцию:

    function A() {};             // декларация функции
    var B = function () {};       // функциональное выражение
    var C = (function () {});     // функциональное выражение с оператором группировки
    var D = function foo () {};   // именованное функциональное выражение
    var E = (function () {})();   // самовызывающееся функциональное выражение
    var F = new Function();     // конструктор функции
    var G = new function() {};   // вырожденный случай: конструктор объекта
    

    В таком обилии сложно не запутаться, не так ли? Как правило, в повседневной жизни мы используем не более трех различных типов объявления функций, и это отлично работает. Однако если копнуть поглубже, то может оказаться, что большинство из нас даже не подозревает какой объём таинств и подводных камней хранит в себе операция объявления функции.

    Согласно документации ECMA синтаксис определения функции следующий:
    ДекларацияФункции:
        function Идентификатор ( Параметры ) { ТелоФункции }
    
    ФункциональноеВыражение:
        function Идентификатор (опционально)  ( Параметры ) { ТелоФункции }
    

    Хоть и выглядят эти определения вполне схожими, но между декларацией функции и функциональным выражением есть большая разница. Декларация функции (Function Declaration) создается до выполнения любого кода, в то время как функциональное выражение (Function Expression) будет создано только в момент, когда интерпретатор дойдёт до данной строки кода.

    Функциональное выражение — это объявление функции в контексте какого-либо выражения.

    Рассмотрим несколько примеров функциональных выражений:

    Оператор присваивания

    var a = function() {};
    

    Это классический пример задания функционального выражения через присваивание. Оператор присваивания ожидает справа выражение, именно поэтому функция становится частью выражения.
    Немного пофантазировав, можно придумать следующие примеры:

    var a = function() { return 1; }() + 12; // 13
    var b = function() { return 1; } + ''; // function (){return 1}
    var c = function() { return 1; } + '' - 1; //NaN
    


    Оператор группировки

    Декларируя функцию, мы не можем выполнить её сразу же, однако, обернув декларацию функции в круглые скобки — это становится возможно. Оператор группировки выражается круглыми скобками, и в данном случае он превращает декларацию функции в функциональное выражение.

    function foo() { return 1; } // undefined	
    function foo() { return 1; }(); // Uncaught SyntaxError: Expected () to start arrow function, but got '}' instead of '=>'
    (function foo() { return 1; }()) // 1
    (function foo() { return 1; })() // 1
    

    Принципиальной разницы между третьим и четвертым вариантом нет, так как в первом случае мы выполняем выражение, которое определяет и сразу же исполняет функцию, а во втором случае выполняется выражение, определяющее функцию, которая затем будет выполнена.


    Оператор запятая

    Оператор запятая вычисляет значение каждого своего операнда (слева направо) и возвращает значение последнего операнда.

    0, function() { return 1; }(); // 1
    


    Операторы (+, -, !, ~, void)

    +function() { return false; }(); // 0
    -function() { return false; }(); // -0
    !function() { return false; }(); // true
    ~function() { return false; }(); // -1
    void function() { return false; }(); //undefined
    


    Комбинированные операторы:

    !-function () { return false; }(); // true
    var c =  5 * (2 - function () {return 1}()) // 5
    var c =  5 * 2 - -~function () {return 1}() // 8
    


    Отличие именованных функциональных выражений от не именованных:


    Зачастую, имя, присвоенное функциональному выражению, оказывается избыточным в контексте остального кода, кроме случая, когда доступ к функции необходимо получить из неё же самой. Область видимости имени функционального выражения ограничена исключительно самой функцией.

    var f = function getFactorial (n) {
      return n ? n * getFactorial(n - 1) : 1;
    };
    
    f(5); // 120
    


    Важно:
    Помните, вы пишете код для людей, поэтому старайтесь избегать написания кода в стиле ниндзя. Приведённые в статье знания полезны для того, чтобы понимать внутреннее устройство языка и не растеряться в случае, если вам вдруг встретятся такие выражения в одном из проектов или на собеседовании.
    Share post

    Comments 23

      +1
      Основной смысл записи var f = function myFunc(){ } заключается в нормальных именах в стеке вызовов при отладке.
        0
        Что в ES6 уже не имело смысла из-за автоматического вывода имен анонимных функций из переменных и контекста объявления.
          0
          К сожалению, свойство name у анонимных функций все еще не выводится из имени переменной, только из явного объявления имени функции. Мелочь, конечно, но иногда все же неудобно.
        0
        Ещё именованные функции распространяются на весь код сразу, а безымянные только на тот, который идёт после объявления этой функции. Тоже вполне важное отличие, но не столь существенное, согласен.
          0
          Не на весь.
          Декларация функции — только на тот скоуп, в котором объявлена.
          Функциональное выражение — не распространяется, по имени такая функция доступна только внутри себя (кроме ИЕ8).
          +2
          Это для удобства рекурсивного вызова. По имени функция доступна внутри себя. Правда, в ИЕ8 есть проблемы — имя оказывается доступно извне.
          +2
          После прочтения статьи возникло несколько вопросов и комментариев:
          1. чем B отличается от C?
          2. почему E с G записали в объявления функций, если E — вызов, а G — объект?
          3. если B и D — разные, то чего уж там, давайте перечислять function foo() {}, function bar() {}, function buzz() {} как разные формы объявления функции. Почему нет?
          4. почему в примерах присваивания a включает результат выполнения функции, а b и c — саму функцию?
          5. Почему «оператор группировки» упоминается отдельно, если он не имеет никакого отношения к объявлению IIFE вообще?
          6. чем примеры ваших операторов, включая запятую, отличаются от того, чтобы заменить IIFE на сразу результат? Где разница между void function... и void 42?
            0
            А, ну и лямбды, которые () => 1, забыли?
              –1
              1. Наличием оператора группировки. Функционально они идентичны.
              2. G сделан как дополнительный пример использования ключевого слова function
              3. Они разные в том плане что имени может и не быть. Важно было показать что возможен и такой вариант тоже.
              4. Это же примеры, хотелось показать оба варианта. В данном случае не очень принципиально наличие вызова функции.
              5. Почему не имеет?
              6. Примеры выбраны тривиальными чтобы не нагружать статью лишним кодом. А так да, если бы этот код был в реальном проекте, то ваши замечания очень даже по делу.
              +1
              Принципиальной разницы между вторым и третим вариантом нет

              вы в жизни тоже от нуля считаете?
                0
                Спасибо за то, что внимательно читали статью, исправил! :)
                0
                Недавно пошло дикое поветрие всегда объявлять функции через var a = function () {}. Спрашиваешь зачем — мнутся что-то про хойстинг, но никто внятного ответа не дал, зачем в данном конкретном случае хойстинга надо избежать.
                  +1
                  Интересно, где вы это нашли такое. Все мои знакомые спокойно юзают function declaration.

                  Лично у меня сейчас ко всплытию только одна претензия — из-за него нельзя сделать нормальные декораторы для функций.
                    0
                    На работе и еще в паре мест, неважно.

                    Я сегодня отчаянно не выспался — подскажите, почему хойстинг мешает декораторам?

                      +3
                      let counter = 0;
                      
                      let add = function () {
                        counter++;
                      };
                      
                      @add
                      function foo() {
                      }
                      


                      Соль в том, что из-за всплытия объявление функции foo будет происходить раньше объявления и инициализации переменных counter и add (т. к. function declaration объявляются на входе в блок).
                        0
                        0
                        Вот свежий пример: habrahabr.ru/post/274887/#comment_8734559
                          0
                          Ну, там по крайней мере присваивают стрелочные функции, а не function expression. Это можно делать для оптимизации — потому что для стрелочных функций не создаются this и arguments, хотя, имхо, это экономия на спичках.

                    Only users with full accounts can post comments. Log in, please.