Что такое практическое использование для закрытия в JavaScript?

Я попытка мне труднее всего обернуть голову вокруг закрытия 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 ответов


я использовал закрытие, чтобы делать такие вещи, как:

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

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

Как видите, a теперь объект, с методом publicfunction ( a.publicfunction() ), который называет privatefunction, который существует только внутри закрытия. Ты можешь!--11-->не вызов privatefunction напрямую (т. е. a.privatefunction() ), всего publicfunction().

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


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

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

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

  <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>

пример вы даете замечательный. Закрытие - это механизм абстракции, который позволяет очень четко отделять проблемы. Ваш пример-это случай отделения инструментария (подсчета вызовов) от семантики (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.


Да, это хороший пример полезного закрытия. Вызов warnUser создает calledCount переменная в своей области и возвращает анонимную функцию, которая хранится в warnForTamper переменной. Поскольку по-прежнему существует закрытие, использующее переменную calledCount, она не удаляется при выходе функции, поэтому каждый вызов 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); 

в языке 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 объект, но тогда он будет виден пользователям скрипта, и они могут даже изменить его, если захотят, даже не нуждаясь в вашем исходном коде. Однако, поскольку он заключен анонимной функцией, которая возвращает функцию поиска даты, он доступен только функцией поиска, поэтому теперь он защищен от подделки.



другое общее использование для закрытия-привязать 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);

всякий раз, когда событие mousemove события, 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 с помощью функции " для конкретного примера.


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

без закрытия (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();

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

подумайте об этом так: когда вы создаете два объекта Person, вы знаете, что переменная-член класса "Name" не является общей между экземплярами; каждый объект имеет свою собственную "копию". Аналогично, когда вы создаете закрытие,свободная переменная ('calledCount' в вашем примере выше) - это привязан к "экземпляру" функции.

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

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

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


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

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

регулирование:

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

Debouncing:

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

код:

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

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

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


некоторое время назад я написал статью о том, как закрытие можно использовать для упрощения кода обработки событий. Он сравнивает ASP.NET обработка событий на стороне клиента jQuery.

http://www.hackification.com/2009/02/20/closures-simplify-event-handling-code/


Мне нравится фабрика функций Mozilla пример.

function makeAdder(x) {

    return function(y) {
        return x + y;
    };
}

var addFive = makeAdder(5);

console.assert(addFive(2) === 7); 
console.assert(addFive(-5) === 0);

шаблон модуля JavaScript использует замыкания. Его приятный шаблон позволяет вам иметь что-то похожее на "общественные" и "частные" Вары.

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();

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

Jsfiddle

//Counter clouser implemented function;
var CartCouter = function(){
	var counter = 0;
  function changeCounter(val){
  	counter += val
  }
  return {
  	increment: function(){
    	changeCounter(1);
    },
    decrement: function(){
    changeCounter(-1);
    },
    value: function(){
    return counter;
    }
  }
}

var cartCount = CartCouter();
function updateCart(){
	document.getElementById('cartcount').innerHTML = cartCount.value();
  }

var productlist = document.getElementsByClassName('item');
for(var i = 0; i< productlist.length; i++){
	productlist[i].addEventListener('click',function(){
  	if(this.className.indexOf('selected')<0){
    		this.className += " selected";
        cartCount.increment();
        updateCart();
    } else{
    	this.className = this.className.replace("selected", "");
      cartCount.decrement();
      updateCart();
    }
  })
}
.productslist{
  padding:10px;
}
ul li{
  display: inline-block;
  padding: 5px;
  border: 1px solid #ddd;
  text-align: center;
  width: 25%;
  cursor: pointer;
}
.selected{
  background-color: #7CFEF0;
  color: #333;
}
.cartdiv{
  position: relative;
  float:right;
  padding: 5px;
  box-sizing: border-box;
  border: 1px solid #f1f1f1;
}
<div>
<h3>
Practical Use of JavaScript Closure consept/private variable.
</h3>
<div class="cartdiv">
    <span id="cartcount">0</span>
</div>
<div class="productslist">
    <ul >
    <li class="item">Product 1</li>
     <li class="item">Product 2</li>
     <li class="item">Product 3</li>
    </ul>

</div>
</div>

использование замыканий:

закрытие-одна из самых мощных функций JavaScript. JavaScript позволяет вложить функции и предоставляет внутренней функции полный доступ ко всем переменным и функциям, определенным внутри внешней функции (и ко всем другим переменным и функциям, к которым имеет доступ внешняя функция). Однако, внешняя функция не имеет доступа к переменным и функции, определенные внутри функции. Это обеспечивает своего рода безопасность переменных внутренней функции. Кроме того, поскольку внутренняя функция имеет доступ к области внешней функции, переменные и функции, определенные во внешней функции, будут жить дольше, чем сама внешняя функция, если внутренней функции удастся выжить за пределами жизни внешней функции. Закрытие создается, когда внутренняя функция каким-то образом становится доступной для любой области вне внешней функции.

пример :

<script>
var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
console.log(pet.getName());                  // Vivie

console.log(pet.setName("Oliver"));   
console.log(pet.setSex("male"));
console.log(pet.getSex());                   // male
console.log(pet.getName());                  // Oliver
</script>

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


ссылки: практическое использование замыканий

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

пример метода сортировки массивов, который принимает в качестве аргумента функцию условия сортировки:

[1, 2, 3].sort(function (a, b) {
    ... // sort conditions
});

отображение функционалов как метод отображения массивов, который отображает новый массив по условию функционала аргумент:

[1, 2, 3].map(function (element) {
   return element * 2;
}); // [2, 4, 6]

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

 someCollection.find(function (element) {
        return element.someProperty == 'searchCondition';
    });

кроме того, мы можем отметить применение функционалов как, например, метод forEach, который применяет функцию к массиву элементов:

[1, 2, 3].forEach(function (element) {
    if (element % 2 != 0) {
        alert(element);
    }
}); // 1, 3

функция применяется к аргументам (к списку аргументов-in apply, а к расположенным аргументам-in call):

(function () {
  alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

отложено звонки:

var a = 10;
    setTimeout(function () {
      alert(a); // 10, after one second
    }, 1000);

функции обратного вызова:

var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // callback, which will be called deferral ,
  // when data will be ready;
  // variable "x" here is available,
  // regardless that context in which,
  // it was created already finished
  alert(x); // 10
};

создание инкапсулированной области с целью сокрытия вспомогательных объектов:

var foo = {};
(function (object) {
  var x = 10;
  object.getX = function _getX() {
    return x;
  };
})(foo);
alert(foo.getX());// get closured "x" – 10

большая часть кода, который мы пишем в интерфейсном JavaScript, основана на событиях - мы определяем некоторое поведение, а затем прикрепляем его к событию, которое инициируется пользователем (например, щелчок или нажатие клавиши). Наш код обычно прикрепляется как обратный вызов: одна функция, которая выполняется в ответ на событие. size12, объекта14, и size16 теперь функции, которые будет изменять текст на 12, 14 и 16 пикселей, соответственно. Мы можем прикрепить их к кнопкам (в этом случае ссылки) как следует:

function makeSizer(size) {
    return function() {
    document.body.style.fontSize = size + 'px';
    };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

Скрипка


закрытие-полезный способ создания генераторы, последовательность увеличивается по требованию:

    var foobar = function(i){var count = count || i; return function(){return ++count;}}

    baz = foobar(1);
    console.log("first call: " + baz()); //2
    console.log("second call: " + baz()); //3

различия суммируются следующим образом:

Anonymous functions                                    Defined functions

Cannot be used as a method                             Can be used as a method of an object

Exists only in the scope in which it is defined        Exists within the object it is defined in

Can only be called in the scope in which it is defined Can be called at any point in the code

Can be reassigned a new value or deleted               Cannot be deleted or changed

ссылки


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

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

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Closures on button presses</title>
<script type="text/javascript">

window.addEventListener("load" , function () {
    /*
    grab the function from the first closure,
    and assign to a temporary variable 
    this will set the totalButtonCount variable
    that is used to count the total of all button clicks

    */
    var buttonHandler = buttonsCount(); 

    /*
    using the result from the first closure (a function is returned) 
    assign and run the sub closure that carries the 
    individual variable for button count and assign to the click handlers 
    */
    document.getElementById("button1").addEventListener("click" , buttonHandler() );
    document.getElementById("button2").addEventListener("click" , buttonHandler() );
    document.getElementById("button3").addEventListener("click" , buttonHandler() );

    // Now that buttonHandler has served its purpose it can be deleted if needs be
    buttonHandler = null;
});



function buttonsCount() {
    /* 
        First closure level 
        - totalButtonCount acts as a sort of global counter to count any button presses
    */
    var totalButtonCount = 0;

    return  function () {
        //second closure level
        var myButtonCount = 0;

        return function (event) {
            //actual function that is called on the button click
            event.preventDefault();
            /*  
               increment the button counts.
               myButtonCount only exists in the scope that is 
               applied to each event handler, therefore acts 
               to count each button individually whereas because 
               of the first closure totalButtonCount exists at 
               the scope just outside, so maintains a sort 
               of static or global variable state 
            */

            totalButtonCount++;
            myButtonCount++;

            /* 
                do something with the values ... fairly pointless 
                but it shows that each button contributes to both 
                it's own variable and the outer variable in the 
                first closure 
            */
            console.log("Total button clicks: "+totalButtonCount);
            console.log("This button count: "+myButtonCount);
        }
    }
}

</script>
</head>

<body>
    <a href="#" id="button1">Button 1</a>
    <a href="#" id="button2">Button 2</a>
    <a href="#" id="button3">Button 3</a>
</body>
</html>

в данном примере значение вложенной переменной 'counter' защищено и может быть изменено только с помощью заданных функций (increment, decrement). потому что это в закрытии,

var MyCounter= function (){
    var counter=0;
    return {
    	increment:function () {return counter += 1;},
        decrement:function () {return counter -= 1;},
        get:function () {return counter;}
    };
};

var x = MyCounter();
//or
var y = MyCounter();

alert(x.get());//0
alert(x.increment());//1
alert(x.increment());//2

alert(y.increment());//1
alert(x.get());// x is still 2