Что практического использования для закрытия в JavaScript? - PullRequest
247 голосов
/ 28 апреля 2010

Я пытаюсь изо всех сил пытаться обернуть голову вокруг замыканий JavaScript.

Я получаю это, возвращая внутреннюю функцию, она будет иметь доступ к любой переменной, определенной в ее непосредственном родителе.

Где это будет полезно для меня? Возможно, я еще не совсем обдумал это. Большинство примеров, которые я видел в Интернете , не предоставляют никакого реального кода, просто смутные примеры.

Может ли кто-нибудь показать мне реальное использование замыкания?

Это, например, этот?

var warnUser = function (msg) {
    var calledCount = 0;
    return function() {
       calledCount++;
       alert(msg + '\nYou have been warned ' + calledCount + ' times.');
    };
};

var warnForTamper = warnUser('You can not tamper with our HTML.');
warnForTamper();
warnForTamper();

Ответы [ 20 ]

210 голосов
/ 28 апреля 2010

Я использовал замыкания для таких вещей, как:

a = (function () {
    var privatefunction = function () {
        alert('hello');
    }

    return {
        publicfunction : function () {
            privatefunction();
        }
    }
})();

Как вы можете видеть, a теперь является объектом с методом publicfunction (a.publicfunction()), который вызывает privatefunction, который существует только внутри замыкания. Вы можете НЕ позвонить privatefunction напрямую (т.е. a.privatefunction()), просто publicfunction().

Это минимальный пример, но, может быть, вы можете увидеть его применение? Мы использовали это для применения публичных / приватных методов.

140 голосов
/ 19 августа 2016

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

<button onclick="updateClickCount()">click me</button>  

Теперь может быть много подходов, таких как:

1) Вы можете использовать глобальную переменную и функцию для увеличения счетчика :

var counter = 0;

function updateClickCount() {
    ++counter;
    // do something with counter
}

Но подводный камень в том, что любой скрипт на странице может изменить счетчик, не вызывая updateClickCount().


2) Теперь вы можете подумать об объявлении переменной внутри функции:

function updateClickCount() {
    var counter = 0;
    ++counter;
    // do something with counter
}

Но, эй! Каждый раз, когда вызывается функция updateClickCount(), счетчик снова устанавливается на 1.


3) Думаете о Вложенных функциях ?

Вложенные функции имеют доступ к области видимости над ними.
В этом примере внутренняя функция updateClickCount() имеет доступ к переменной счетчика в родительской функции countWrapper()

function countWrapper() {
    var counter = 0;
    function updateClickCount() {
    ++counter;
    // do something with counter
    }
    updateClickCount();    
    return counter; 
}

Это могло бы решить дилемму счетчика, если бы вы могли добраться до функции updateClickCount() извне, и вам также нужно найти способ выполнить counter = 0 только один раз, а не каждый раз.


4) Закрытие на помощь! (функция самовозбуждения) :

 var updateClickCount=(function(){
    var counter=0;

    return function(){
     ++counter;
     // do something with counter
    }
})();

Функция самовывоза запускается только один раз. Он устанавливает counter в ноль (0) и возвращает выражение функции.

Таким образом, updateClickCount становится функцией. «Замечательная» часть заключается в том, что он может получить доступ к счетчику в родительской области.

Это называется закрытием JavaScript . Это позволяет функции иметь переменные " private ".

counter защищен областью действия анонимной функции и может быть изменен только с помощью функции добавления!

Более живой пример закрытия:

  <script>
    var updateClickCount=(function(){
    var counter=0;

    return function(){
    ++counter;
     document.getElementById("spnCount").innerHTML=counter;
    }
  })();
</script>

<html>
 <button onclick="updateClickCount()">click me</button>
  <div> you've clicked 
    <span id="spnCount"> 0 </span> times!
 </div>
</html>
64 голосов
/ 28 апреля 2010

Пример, который вы приводите, превосходен. Замыкания - это механизм абстракции, который позволяет очень аккуратно разделять задачи. Ваш пример - это случай отделения инструментов (подсчета вызовов) от семантики (API сообщения об ошибках). Другое использование включает в себя:

  1. Передача параметризованного поведения в алгоритм (классическое программирование высшего порядка):

    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
  2. Имитация объектно-ориентированного программирования:

    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
    
  3. Реализация экзотического управления потоком, такого как обработка событий jQuery и API AJAX.

18 голосов
/ 28 апреля 2010

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

Наиболее распространенная проблема, которую я вижу в StackOverflow, - это когда кто-то хочет «задержать» использование переменной, которая увеличивается в каждом цикле, но поскольку переменная находится в области видимости, то каждая ссылка на переменную будет после окончания цикла, в результате чего конечное состояние переменной:

for (var i = 0; i < someVar.length; i++)
    window.setTimeout(function () { 
        alert("Value of i was "+i+" when this timer was set" )
    }, 10000);

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

for (var i = 0; i < someVar.length; i++)
    (function (i) {
        window.setTimeout(function () { 
            alert("Value of i was "+i+" when this timer was set" )
        }, 10000);
    })(i); 
14 голосов
/ 28 апреля 2010

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

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

var dateUtil = {
  weekdayShort: (function() {
    var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    return function(x) {
      if ((x != parseInt(x)) || (x < 1) || (x > 7)) {
        throw new Error("invalid weekday number");
      }
      return days[x - 1];
    };
  }())
};

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

14 голосов
/ 02 июня 2018

Я знаю, что очень поздно отвечаю на этот вопрос, но это может помочь любому, кто все еще ищет ответ в 2018 году.

Закрытия Javascript можно использовать для реализации throttle и debounce функциональности в вашем приложении.

1010 * Троттлинг *:

Регулирование налагает ограничение на максимальное количество раз, которое функция может быть вызвана с течением времени. Как в «выполнять эту функцию не чаще, чем раз в 100 миллисекунд».

Код:

const throttle = (func, limit) => {
  let isThrottling
  return function() {
    const args = arguments
    const context = this
    if (!isThrottling) {
      func.apply(context, args)
      isThrottling = true
      setTimeout(() => isThrottling = false, limit)
    }
  }
}

дребезг

Демонстрация накладывает ограничение на функцию, которая не будет вызываться снова, пока определенное количество времени не прошло без ее вызова. Как в «выполнить эту функцию, только если прошло 100 миллисекунд без ее вызова.»

Код:

const debounce = (func, delay) => {
  let debouncing
  return function() {
    const context = this
    const args = arguments
    clearTimeout(debouncing)
    debouncing = setTimeout(() => func.apply(context, args), delay)
  }
}

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

Надеюсь, это кому-нибудь поможет.

6 голосов
/ 28 апреля 2010
5 голосов
/ 08 октября 2016

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

Без закрытия (https://jsfiddle.net/lukeschlangen/pw61qrow/3/):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";
  console.log(message);
}

greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");

С закрытием (https://jsfiddle.net/lukeschlangen/Lb5cfve9/3/):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";

  return function() {
    console.log(message);
  }
}

var greetingBilly = greeting("Billy", "Bob");
var greetingLuke = greeting("Luke", "Schlangen");

greetingBilly();
greetingBilly();
greetingBilly();
greetingLuke();
greetingLuke();
greetingLuke();
5 голосов
/ 07 мая 2010

Другим распространенным использованием замыканий является привязка this в методе к определенному объекту, что позволяет вызывать его в другом месте (например, в качестве обработчика событий).

function bind(obj, method) {
    if (typeof method == 'string') {
        method = obj[method];
    }
    return function () {
        method.apply(obj, arguments);
    }
}
...
document.body.addEventListener('mousemove', bind(watcher, 'follow'), true);

Всякий раз, когда происходит событие перемещения мыши, вызывается watcher.follow(evt).

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

foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}

становится

fooer = function (x) {
    return function (...) {A x B}
}

где A и B - не синтаксические единицы, а строки исходного кода (не строковые литералы).

См. " Упорядочение моего JavaScript с помощью функции " для конкретного примера.

4 голосов
/ 28 апреля 2010

Если вы знакомы с концепцией создания экземпляра класса в объектно-ориентированном смысле (т. Е. Для создания объекта этого класса), то вы близки к пониманию замыканий.

Подумайте об этом так: когда вы создаете два экземпляра объекта Person, вы знаете, что переменная члена класса "Name" не используется совместно экземплярами; каждый объект имеет свою собственную «копию». Точно так же, когда вы создаете замыкание, свободная переменная (в названном выше примере 'namedCount') привязывается к 'экземпляру' функции.

Я думаю, что ваш концептуальный скачок слегка затруднен тем фактом, что каждая функция / замыкание, возвращаемое функцией warnUser (за исключением: это функция высшего порядка ), связывает 'namedCount' с тем же начальным значением (0), тогда как часто при создании замыканий более полезно передавать различные инициализаторы в функцию более высокого порядка, так же, как и передавать различные значения в конструктор класса.

Итак, предположим, что когда selectedCount достигает определенного значения, вы хотите завершить сеанс пользователя; вам могут потребоваться разные значения для этого в зависимости от того, поступает ли запрос из локальной сети или из-за плохого интернета (да, это надуманный пример). Чтобы достичь этого, вы могли бы передавать различные начальные значения для namedCount в warnUser (т.е. -3 или 0?).

Часть проблемы с литературой - номенклатура, используемая для их описания («лексическая область», «свободные переменные»). Не позволяйте этому обмануть вас, замыкания проще, чем кажется ... prima facie; -)

...