Каковы преимущества использования анонимных функций вместо именованных функций для обратных вызовов и параметров в коде событий JavaScript?

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

Что-то, что мне всегда казалось странным, заключается в том, что, несмотря на кошмар читабельности, который является гнездом вложенных обратных вызовов JavaScript, во многих примерах и руководствах я очень редко вижу использование предопределенных именованных функций в качестве аргументов обратного вызова. Я - программист на Java каждый день, и отказ от стереотипных уловок имен Enterprise-y для блоков кода одна из вещей, которые мне нравятся в работе с языком с большим выбором функциональных IDE, заключается в том, что использование значимых, если длинные, имена могут сделать смысл и смысл кода намного более понятным, не делая его более трудным для фактической производительности. Так почему бы не использовать тот же подход при написании кода JavaScript?

Думая об этом, я могу выдвинуть аргументы как за, так и против этой идеи, но моя наивность и новизна в языке мешают мне сделать какие-либо выводы о том, почему это было бы хорошо на техническом уровне.

Плюсы:

  • Гибкость. Асинхронная функция с параметром обратного вызова может быть достигнута одним из множества различных путей кода, и может потребоваться написать именованную функцию для учета каждого возможного крайнего случая.
  • Скорость. Это сильно влияет на менталитет хакера. Прикрепите вещи к нему, пока он не заработает.
  • Все остальные делают это
  • Меньшие размеры файлов, даже если это тривиально, но каждый бит имеет значение в сети.
  • Проще АСТ? Я бы предположил, что анонимные функции генерируются во время выполнения, и поэтому JIT не будет работать с отображением имени в инструкции, но я просто догадываюсь на этом этапе.
  • Быстрая отправка? Не уверен насчет этого тоже. Снова догадываюсь

Минусы:

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

В связи с тем, что многие современные браузеры демонстрируют способность выполнять код JavaScript гораздо быстрее, чем раньше, я не могу понять, как может быть необходим какой-либо тривиальный вид повышения производительности, который можно получить с помощью анонимных обратных вызовов. Кажется, что если вы находитесь в ситуации, когда использование именованной функции возможно (предсказуемое поведение и путь выполнения), то не было бы никаких причин не делать этого.

Так есть ли какие-то технические причины или ошибки, о которых я не знаю, которые делают эту практику настолько распространенной по какой-то причине?

6 ответов

Решение

Я использую анонимные функции по трем причинам:

  1. Если имя не нужно, потому что функция вызывается только в одном месте, то зачем добавлять имя в любое пространство имен, в котором вы находитесь.
  2. Анонимные функции объявляются встроенными, а встроенные функции имеют преимущества в том, что они могут обращаться к переменным в родительских областях. Да, вы можете поместить имя в анонимную функцию, но обычно это бессмысленно, если она объявлена ​​как встроенная. Таким образом, у inline есть существенное преимущество, и если вы делаете это inline, у вас мало причин для того, чтобы называть это имя.
  3. Код кажется более автономным и читаемым, когда обработчики определены прямо в коде, который их вызывает. Вы можете читать код почти последовательно, вместо того, чтобы искать функцию с таким именем.

Я стараюсь избегать глубокого вложения анонимных функций, потому что это может быть волосатым для понимания и чтения. Обычно, когда это происходит, есть лучший способ структурировать код (иногда с помощью цикла, иногда с таблицей данных и т. Д.), И именованные функции обычно тоже не являются решением проблемы.

Думаю, я бы добавил, что если обратный вызов начинает получать больше, чем 15-20 строк, и ему не нужен прямой доступ к переменным в родительской области видимости, я хотел бы дать ему имя и разбить его на это собственная именованная функция, объявленная в другом месте. Здесь определенно есть точка читабельности, где нетривиальная функция, которая становится длинной, просто более удобна в обслуживании, если она помещена в свой собственный именованный модуль. Но большинство обратных вызовов, с которыми я сталкиваюсь, не такие длинные, и я считаю, что их легче читать, чтобы они оставались встроенными.

Я предпочитаю именованные функции сам, но для меня это сводится к одному вопросу:

Буду ли я использовать эту функцию где-нибудь еще?

Если ответ да, я называю / определяю его. Если нет, передайте это как анонимную функцию.

Если вы используете его только один раз, нет смысла собирать им глобальное пространство имен. В современных сложных интерфейсах число именованных функций, которые могли бы быть анонимными, быстро растет (легко более 1000 в действительно сложных проектах), что приводит к (относительно) значительному увеличению производительности за счет предпочтения анонимных функций.

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

Еще одна заметка об именах. Привычка определять длинные имена действительно повредит вашему размеру файла. Возьмите следующий пример.

Предположим, что обе эти функции выполняют одно и то же:

function addTimes(time1, time2)
{
    // return time1 + time2;
}

function addTwoTimesIn24HourFormat(time1, time2)
{
    // return time1 + time2;
}

Второй говорит вам точно, что он делает в названии. Первое более неоднозначно. Тем не менее, есть 17 символов различий в названии. Скажем, функция вызывается 8 раз по всему коду, это 153 дополнительных байта, которые ваш код не должен был иметь. Не колоссально, но если это привычка, экстраполяция этих функций на 10 или даже 100 с легкостью будет означать разницу в загрузке на несколько КБ.

Опять же, однако, ремонтопригодность должна быть сопоставлена ​​с преимуществами производительности. Это боль при работе со скриптовым языком.

Немного опоздал на вечеринку, но некоторые еще не упоминали аспекты функций, анонимные или нет...

На команды Anon нелегко сослаться в гуманоидных разговорах о коде. Например, "Джо, не могли бы вы объяснить, что делает алгоритм в этой функции... Какой... 17-й анонимной функции в функции fooApp.... Нет, не той! 17-й!"

Anon-функции также являются анонимными для отладчика. (Дух!) Поэтому трассировка стека отладчика, как правило, просто показывает знак вопроса или аналогичный, что делает его менее полезным, когда вы установили несколько точек останова. Вы достигли точки останова, но обнаружили, что прокручиваете окно отладки вверх / вниз, чтобы выяснить, где, черт возьми, вы находитесь в своей программе, потому что эй, функция вопросительного знака просто не делает этого!

Опасения по поводу загрязнения глобального пространства имен действительны, но их легко устранить, назвав ваши функции как узлы в вашем собственном корневом объекте, например "myFooApp.happyFunc = function ( ...) { ... }; ".

Функции, которые доступны в глобальном пространстве имен или как узлы в вашем корневом объекте, как описано выше, могут быть вызваны из отладчика напрямую, во время разработки и отладки. Например, в командной строке консоли выполните "myFooApp.happyFunc(42)". Это чрезвычайно мощная возможность, которая не существует (изначально) в скомпилированных языках программирования. Попробуйте это с помощью функции anon.

Anon-функции можно сделать более читабельными, назначив их переменной var, а затем передав переменную в качестве обратного вызова (вместо встраивания). Например: var funky = function ( ...) { ... };. JQuery('# Отис') нажмите (фанк);

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

Анонимные функции полезны, потому что они помогают вам контролировать, какие функции доступны.

Подробнее: если имени нет, вы не можете переназначить его или изменить где-либо, кроме того места, где оно было создано. Хорошее практическое правило заключается в том, что если вам не нужно повторно использовать эту функцию где-либо, неплохо подумать, не лучше ли анонимная функция предотвратить вмешательство где-либо.

Пример: если вы работаете над большим проектом с большим количеством людей, что, если у вас есть функция внутри более крупной функции, и вы как-то ее называете? Это означает, что любой, кто работает с вами и также редактирует код в более крупной функции, может делать что-то для этой меньшей функции в любое время. Что, если вы назвали его, например, «добавить», а кто-то переназначил «добавить» для числа вместо этого в той же области? Тогда все это сломается!

PS - Я знаю, что это очень старый пост, но на этот вопрос есть гораздо более простой ответ, и я бы хотел, чтобы кто-то так выразился, когда я сам, как новичок, искал ответ - Надеюсь, вы согласны с возрождением старая ветка!

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

(function recursion(iteration){
    if (iteration > 0) {
      console.log(iteration);
      recursion(--iteration);
    } else {
      console.log('done');
    }
})(20);

console.log('recursion defined? ' + (typeof recursion === 'function'));

http://jsfiddle.net/Yq2WD/

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

Привет, меня зовут Джейсон ИЛИ привет, меня зовут???? Вы выбираете.

Что ж, для ясности рассуждений ниже приведены все анонимные функции / выражения функций в моей книге:

var x = function(){ alert('hi'); },

indexOfHandyMethods = {
   hi: function(){ alert('hi'); },
   high: function(){
       buyPotatoChips();
       playBobMarley();
   }
};

someObject.someEventListenerHandlerAssigner( function(e){
    if(e.doIt === true){ doStuff(e.someId); }
} );

(function namedButAnon(){ alert('name visible internally only'); })()

Плюсы:

  • Это может уменьшить некоторую путаницу, особенно в рекурсивных функциях (где вы могли бы (хотя на самом деле, так как arguments.callee устарела) по-прежнему использовать именованную ссылку для последнего примера внутренне), и дает понять, что функция только когда-либо срабатывает в этом место.

  • Выигрыш в коде: в примере с литералом объекта, которому в качестве методов назначены анон-функции, было бы глупо добавлять больше мест для поиска и клевки для логики в вашем коде, когда весь смысл этого литерала объекта состоит в том, чтобы добавить некоторые связанные функции в то же самое удобное место. Однако, когда я объявляю открытые методы в конструкторе, я склонен определять встроенные помеченные функции, а затем назначать их ссылками на this.sameFuncName. Это позволяет мне использовать одни и те же методы внутри себя без "этого". Это приводит к тому, что порядок определения не имеет значения, когда они называют друг друга.

  • Полезно для предотвращения ненужного загрязнения глобального пространства имен - однако внутренние пространства имен никогда не должны быть так широко заполнены или обрабатываться несколькими командами одновременно, поэтому этот аргумент кажется мне немного глупым.

  • Я согласен с обратными вызовами при настройке коротких обработчиков событий. Глупо искать 1–5-строчную функцию, тем более что с помощью JS и подъема функций определения могут оказаться где угодно, даже не в одном и том же файле. Это может произойти случайно, не нарушая ничего, и нет, вы не всегда можете контролировать это. События всегда приводят к срабатыванию функции обратного вызова. Нет смысла добавлять дополнительные ссылки в цепочку имен, которые необходимо сканировать, просто для того, чтобы перепроектировать простые обработчики событий в большой кодовой базе, и проблема трассировки стека может быть решена путем абстрагирования триггеров событий в методы, которые регистрируют полезную информацию при отладке. режим включен и запускает триггеры. На самом деле я начинаю строить целые интерфейсы таким образом.

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

Минусы:

  • Функции Anon не могут использовать преимущества функции подъема. Это серьезная разница. Я склонен использовать большое преимущество подъема, чтобы определить свои собственные явно названные функции и конструкторы объектов в нижней части и перейти к определению объекта и типу основного цикла прямо вверху. Я считаю, что это облегчает чтение кода, когда вы правильно называете свои переменные и получаете общее представление о том, что происходит до Ctrl-Fing, для деталей, только когда они важны для вас. Подъем также может быть огромным преимуществом в сильно управляемых событиями интерфейсах, где наложение строгого порядка того, что доступно, когда может кусать вас в задницу. Подъем имеет свои собственные предостережения (например, круговой ссылочный потенциал), но это очень полезный инструмент для организации и создания разборчивого кода при правильном использовании.

  • Четкость /Debug. Абсолютно они время от времени слишком сильно используются, и это может затруднить отладку и удобочитаемость кода. Например, кодовые базы, которые в значительной степени зависят от JQ, могут стать серьезной PITA для чтения и отладки, если вы не инкапсулируете почти неизбежные недопустимые и массивно перегруженные аргументы $ soup разумным способом. Например, метод наведения JQuery является классическим примером чрезмерного использования анон-функций, когда вы помещаете в него две анон-функции, поскольку первому таймеру легко предположить, что это стандартный метод присвоения приемника событий, а не один метод, перегруженный для назначения. обработчики для одного или двух событий. $(this).hover(onMouseOver, onMouseOut) это намного понятнее, чем две аноновые функции.

Другие вопросы по тегам