Каковы варианты использования функций закрытия / обратного вызова в JavaScript?

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

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

Как человек, пишущий код, какие эвристики или подсказки мне следует иметь в виду, определяя, когда использовать обратные вызовы / замыкания?

Я ищу не общее заявление «Замыкания делают код более безопасным», а скорее список практических примеров или практических правил, когда обратные вызовы являются правильной идеей.

Презентация Крокфорда: http://www.yuiblog.com/blog/2010/04/08/video-crockonjs-5/


person Christopher Altman    schedule 12.04.2010    source источник


Ответы (3)


Во-первых:

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

Обратные вызовы также могут быть закрытием, но не всегда.

Это обратный вызов:

someProcess(myCallback);

function myCallback() {
    alert('Done...');
}

function someProcess(callback) {
    // does stuff...
    // ...
    callback();
}

Закрытие:

function foo(msg) {

    function bar() {
        // I can access foo's scope
        // (i.e. bar can access everything that foo can access)
        alert(msg);
    }

    return bar;

}

foo('hello')(); // alerts "hello"

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

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

var myElements = [ /* DOM Collection */ ];

for (var i = 0; i < 100; ++i) {
    myElements[i].onclick = function() {
        alert( 'You clicked on: ' + i );
    };
}

Это не сработает. К моменту щелчка по элементу переменная i равна 99. Чтобы это работало правильно, мы могли бы использовать замыкание для захвата значения i:

function getHandler(n) {
    return function() {
        alert( 'You clicked on: ' + n );
    };
}

for (var i = 0; i < 100; ++i) {
    myElements[i].onclick = getHandler(i);
}
person James    schedule 12.04.2010
comment
встречный пример классический :). - person Anshul; 07.09.2014

Допустим, вам нужна функция, которую можно использовать для возврата уникального значения id, которое будет использоваться при создании новых элементов DOM. Теперь, в чем-то вроде Java, вы можете создать класс с внутренним частным счетчиком, а затем иметь метод, который добавляет счетчик к некоторой строке префикса. Что ж, в Javascript:

var getId = (function() {
  var counter = 0;
  return function() {
    return "prefix" + counter++;
  };
})();

Теперь переменная "getId" связана с функцией, которая создана другой функцией и создана таким образом, что у нее есть постоянная переменная для использования между вызовами. Точно так же, если бы я хотел иметь семейство функций "getId" (скажем, по одной для каждого типа элемента DOM, который я мог бы добавить), я мог бы сделать это:

var getIdFunc = function(prefix) {
  var counter = 0;
  return function() {
    return prefix + counter++;
  };
};
var getId = {
  'div': getIdFunc('div'),
  'span': getIdFunc('span'),
  'dl': getIdFunc('dl'),
  // ...
};

Теперь я могу вызвать getId.div(), чтобы получить новое значение «id» для нового <div>. Функция была создана путем вызова функции, которая предоставляет два значения, спрятанных в закрытии: строку префикса (переданную как аргумент) и счетчик (var, объявленный в области закрытия).

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

О, и вот совет, который поможет вам избежать StackOverflow, если вы попробуете это: эта проблема возникает постоянно:

for (var i = 0; i < 10; ++i) {
  var id = "foo" + i;
  var element = document.getElementById(id);
  element.onclick = function() {
    alert("hello from element " + i);
  };
}

В чем проблема? Ну, переменная «i», на которую ссылается эта функция, - это «i» из области, в которой выполняется этот цикл. Вы заметите, что эта переменная увеличивается в цикле (да, да?). Что ж, каждая из этих маленьких функций, созданных и назначенных в качестве обработчиков событий, будет совместно использовать одну и ту же единственную переменную "i" в области закрытия. Ой! Решение - сделать что-то вроде этого:

for (var i = 0; i < 10; ++i) {
  var id = "foo" + i;
  var element = document.getElementById(id);
  element.onclick = (function(iCopy) {
    return function() {
      alert("hello from element " + iCopy);
    };
  })(i);
}

Мы делаем копию внешнего «i» в собственную область замыкания, так что теперь у каждого обработчика событий есть свой собственный!

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

person Pointy    schedule 12.04.2010

Эта запись от Mozilla может дать ответ, почему и когда используются замыкания.

Кроме того, см. Этот набор примеров (особенно раздел «Что можно сделать с помощью замыканий?») со следующими примерами):

  • Пример 1: setTimeout со ссылками на функции
  • Пример 2: Связывание функций с методами экземпляра объекта
  • Пример 3: инкапсуляция связанных функций

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

person DVK    schedule 12.04.2010