Каковы варианты использования для замыканий / функций обратного вызова в JavaScript? - PullRequest
20 голосов
/ 12 апреля 2010

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

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

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

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

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

Ответы [ 3 ]

33 голосов
/ 12 апреля 2010

Во-первых:

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

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

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

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);
}
14 голосов
/ 12 апреля 2010

Допустим, вам нужна функция, которую вы можете использовать для возврата уникального значения "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» в собственную область замыкания, поэтому теперь у каждого обработчика событий есть свой собственный!

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

5 голосов
/ 12 апреля 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...