Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

Во-первых:

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

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

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

someProcess(myCallback);

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

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

Замыкание:

function foo(msg) {

    function bar() {
        // I can access foo 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);
}

Ответ 2

Предположим, вы хотите использовать функцию, которую вы можете использовать, чтобы вернуть уникальное значение "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>. Эта функция была создана путем вызова функции, которая содержит два значения, спрятанных в закрытии: строка префикса (переданная как аргумент) и счетчик (a 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" из области, в которой работает этот цикл. Эта переменная, вы заметите, получает инкремент через цикл (duhh, right?). Ну, каждая из этих небольших функций, созданных и назначенных в качестве обработчиков событий, будет разделять ту же самую единственную переменную "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" в собственную область закрытия, поэтому теперь каждый обработчик событий имеет свой собственный!

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

Ответ 3

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

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

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

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