Визначте функцію в межах іншої функції в JavaScript


83
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

У мене є функція, структура якої схожа на вищезазначену. Я хочу абстрагувати завдання 3 до функції, bar()але я хочу обмежити доступ до цієї функції лише в межах foo(a).

Щоб досягти того, що я хочу, чи правильно переходити до наступного?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

Якщо вищезазначене правильно, чи bar()перевизначається кожен раз, коли foo(a)викликається? (Мене турбує розтрата ресурсів центрального процесора тут.)


1
Перевірте, чи варто це самостійно: jsperf.com Я думаю, це залежить від task3.
tomByrer

1
@tomByer - +1 за пропозицію інструменту
tamakisquare

Відповіді:


123

Так, те, що у вас там, є правильним. Деякі примітки:

  • barстворюється під час кожного виклику функції foo, але:
    • У сучасних браузерах це дуже швидкий процес. (Деякі двигуни можуть скомпілювати код для нього лише один раз, а потім повторно використовувати цей код з іншим контекстом кожного разу; движок Google V8 [у Chrome та в інших місцях] робить це в більшості випадків.)
    • І залежно від того, що barце робить, деякі двигуни можуть визначити, що вони можуть його "вбудувати", повністю виключаючи виклик функції. V8 це робить, і я впевнений, що це не єдиний двигун, який це робить. Звичайно, вони можуть це зробити, лише якщо це не змінює поведінку коду.
  • Вплив на продуктивність, якщо такий є, barкожного разу створюється, буде сильно відрізнятися залежно від механізмів JavaScript. Якщо barце тривіально, воно буде коливатися від невизначуваного до досить невеликого. Якщо ви не телефонуєте fooтисячі разів поспіль (наприклад, з mousemoveобробника), я б про це не хвилювався. Навіть якщо ви, я б турбувався про це лише тоді, коли побачив проблему на повільніших двигунах. Ось тестовий випадок, що включає операції DOM , який передбачає, що вплив є, але тривіальним (можливо, змитим матеріалом DOM). Ось тестовий приклад, який робить чисті обчислення, що показує набагато більший вплив, але, чесно кажучи, ми говоримо про різницю в мікросекундах, оскільки навіть збільшення на 92% на щось, що займає мікросекунди все ще дуже, дуже швидко. Поки / якщо ви не побачили реального впливу, це не те, про що слід турбуватися.
  • barбуде доступний лише зсередини функції, і він має доступ до всіх змінних та аргументів для цього виклику функції. Це робить цей дуже зручний шаблон.
  • Зверніть увагу, що оскільки ви використовували декларацію функції , не має значення, куди ви покладете декларацію (верхню, нижню чи середню - якщо вона знаходиться на верхньому рівні функції, а не всередині оператора керування потоком, який є синтаксична помилка), вона визначається перед запуском першого рядка поетапного коду.

Thx за вашу відповідь. То ви хочете сказати, що це нікчемний показник ефективності? (враховуючи, що копія barстворюється на кожен виклик foo)
tamakisquare

2
@ahmoo: Щодо продуктивності JavaScript, відповідь майже завжди: це залежить. :-) Це залежить від того, на якому двигуні буде працювати і як часто ви будете телефонувати foo. Якщо ви не телефонуєте fooтисячі разів поспіль (наприклад, не в mousemoveобробнику), то я б зовсім не хвилювався з цього приводу. І зауважте, що деякі двигуни (наприклад, V8) все одно вбудовують код, повністю виключаючи виклик функції, за умови, що це не змінить того, що відбувається таким чином, що може бути виявлено зовні.
TJ Crowder,

@TJCrowder: чи можете ви прокоментувати відповідь Робріха? чи таке рішення запобігає розвазі під час bar()кожного дзвінка? також, чи допоможе використання foo.prototype.barфункції визначення функції будь-якому?
rkw

4
@rkw: Створення функції один раз, як це робить відповідь Робріха, є корисним способом уникнути витрат на її створення під час кожного дзвінка. Ви втрачаєте той факт, що barмає доступ до змінних та аргументів для виклику foo(все, що ви хочете, щоб воно працювало, ви повинні це передати), що може дещо ускладнити ситуацію, але в критичній для продуктивності ситуації, коли побачивши справжню проблему, ви можете виконати такий рефакторинг, щоб перевірити, чи вирішує проблему. Ні, використання foo.prototypeнасправді не допомогло б (з одного боку, barбільше не було б приватним).
TJ Crowder,

@ahmoo: Додано тестовий кейс. Цікаво, що я отримую інші результати від тестування Гуффи, я думаю, що його функція може бути занадто простою. Але я все ще не думаю, що продуктивність повинна бути проблемою.
TJ Crowder,

15

Для цього і потрібні закриття.

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (закриття) містить посилання на бар, і лише посилання на innerfoo повертається з анонімної функції, яка викликається лише один раз для створення закриття.

До бару таким чином не можна дістатися ззовні.


1
Цікаво. У мене обмежений вплив JavaScript, тому закриття для мене щось нове. Тим не менш, ти позначив для мене початкову точку вивчення закриття. Дякую.
tamakisquare

Як часто ви використовуєте закриття для роботи зі змінними / функціональними сферами? Наприклад, якщо у вас є 2 функції, які потребують доступу до тих самих 3-х змінних, чи могли б ви оголосити 3 змінні в закритті разом з 2-ма функціями, а потім повернути 2-і функції?
doubleOrt

8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

Закриття зберігає область bar()вмісту, повертаючи нову функцію із самовиконуючоїся анонімної функції, встановлює більш видиму область foo(). Анонімна функція самовиконання запускається рівно один раз, тому є лише один bar()екземпляр, і кожне виконання foo()буде використовувати її.


Цікаво. Тоді я повинен шукати закриття. Дякую.
tamakisquare

Як часто ви використовуєте закриття для роботи зі змінними / функціональними сферами? Наприклад, якщо у вас є 2 функції, які потребують доступу до тих самих 3-х змінних, чи могли б ви оголосити 3 змінні в закритті разом з 2-ма функціями, а потім повернути 2 функції?
doubleOrt

Як часто я використовую закриття? Весь час. Якби у мене було 3 змінні, необхідні 2 функціям: 1. (найкраще) передати 3 змінні в обидві функції - функції можна визначити один раз і лише один раз. 2. (добре) створити 2 функції, де змінні виходять за межі обох. Це закриття. (В основному відповідь тут.) На жаль, функції перевизначаються для кожного нового використання змінних. 3. (погано) не використовуйте функції, просто один великий довгий метод робить обидві речі.
Робріх

5

Так, це чудово працює.

Внутрішня функція не відтворюється кожного разу, коли ви вводите зовнішню функцію, але вона перепризначається.

Якщо ви протестуєте цей код:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

вона покаже 1, 2, 1, 2, що не 1, 2, 2, 2.


Дякую за вашу відповідь. Чи слід перепризначення demo()кожного разу test()називати проблемою продуктивності? Чи залежить це від складності demo()?
tamakisquare

1
Я зробив перевірку продуктивності: jsperf.com/inner-function-vs-global-function Висновок полягає в тому, що, як правило, це не проблема продуктивності (оскільки будь-який код, який ви вводите у функції, займе набагато більше часу, ніж створення функції сам), але якщо вам знадобиться додатковий рівень продуктивності, вам доведеться писати інший код для різних браузерів.
Guffa,

Thx за те, що ви витрачаєте час на створення тесту та, а також ділитесь своїми балами щодо ефективності. Цінується.
tamakisquare

Ви сказали "внутрішня функція не відтворюється кожного разу" з великою впевненістю. Відповідно до специфікації , це; чи оптимізує це двигун, це залежить від двигуна. (Я сподіваюся, більшість з них.) Мені заінтриговано побачити, що ваш і мій тестові результати мають такі різні результати: jsperf.com/cost-of-creating-inner-function Незважаючи на це, я думаю, що продуктивність є проблемою.
TJ Crowder,

@TJCrowder: Ну, так, це деталь реалізації, але оскільки сучасні двигуни Javascript компілюють код, він не буде перекомпілювати функцію кожного разу, коли їй призначено. Причина того, що результати тестів ефективності відрізняються, полягає в тому, що вони перевіряють різні речі. Мій тест порівнює глобальні функції з локальними, тоді як ваш тест порівнює локальну функцію із вбудованим кодом. Вбудовування коду, звичайно, буде швидшим, ніж виклик функції, це загальний прийом оптимізації.
Guffa,

0

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

https://jsperf.com/nested-functions-vs-not-nested-2/1

Це на Chrome 76, macOS.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.