闭包在 JavaScript 中的实际用途是什么?

What is a practical use for a closure in JavaScript?

提问人:alex 提问时间:4/28/2010 最后编辑:Ramalex 更新时间:8/24/2022 访问量:146121

问:

我正在尽最大努力解决 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();
JavaScript 闭包术语 生成器

评论

27赞 Andy E 4/28/2010
+1 尽力而为:-)关闭一开始似乎真的很令人生畏,我知道它们是为我准备的。一旦你掌握了它们的窍门,你就会立即成为一个更好的编码员。
10赞 Skilldrick 11/23/2010
我刚刚写了一篇关于 JavaScript 闭包的博文,你可能会发现 helfpul。
1赞 Abhi 7/7/2016
@Skilldrick。链接已死...而且我发现这个实际的例子非常有帮助。youtube.com/watch?v=w1s9PgtEoJs
0赞 Ghos3t 4/6/2021
瓶盖也用于咖喱功能

答:

282赞 Francisco Soto 4/28/2010 #1

我使用闭包来做一些事情,比如:

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

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

正如你所看到的,现在是一个对象,它有一个调用 的方法 ( ),它只存在于闭包内部。您不能直接调用(即 ),只是.apublicfunctiona.publicfunction()privatefunctionprivatefunctiona.privatefunction()publicfunction()

这是一个最小的例子,但也许你可以看到它的用途?我们用它来强制执行公共/私有方法。

评论

34赞 alex 4/28/2010
啊,如果这是一个闭包,那么我在不知不觉中使用了闭包!我经常像这样将函数放在另一个函数中,然后通过返回一个对象文本来公开任何我需要的函数,就像您的示例中一样。
12赞 Adam Gent 4/28/2010
从技术上讲,您在浏览器上的 Javascript 中创建的每个函数都是一个闭包,因为窗口对象绑定到它。
11赞 qodeninja 2/4/2014
我知道这是一个老问题,但对我来说,这仍然没有提供足够的答案。为什么不直接调用函数呢?为什么需要私人函数?
7赞 Francisco Soto 2/6/2014
因为即使该示例只有一个函数,它也可能具有无法从外部访问的变量。比如说: var obj = (function () { var value = 0; return { get: function () { return value; }, set: function (val) { value = val; } } })();obj.set(20);obj.get();=> 20 等。
3赞 Francisco Soto 11/5/2014
闭包在许多情况下都非常有用,但您必须更擅长函数式编程才能获得最聪明的使用方法。这是一种简单的使用方法,大多数人都可以立即使用和理解。
21赞 Andy E 4/28/2010 #2

是的,这是一个很好的有用闭包的例子。对 warnUser 的调用在其作用域中创建变量,并返回存储在变量中的匿名函数。由于仍然有一个使用 calledCount 变量的闭包,因此在函数退出时不会删除它,因此每次调用 都会增加作用域变量并警告该值。calledCountwarnForTamperwarnForTamper()

我在 Stack Overflow 上看到的最常见的问题是有人想要“延迟”使用在每个循环时增加的变量,但由于该变量是有作用域的,因此对变量的每个引用都将在循环结束后进行,从而导致变量的结束状态:

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

评论

0赞 Andy E 4/28/2010
有趣的 -1,我想这不是“javascript 中闭包的实际用途”?
1赞 alex 4/28/2010
我发现阅读它有一些用处,所以我在投反对票之前给了 +1。
1赞 Andy E 4/28/2010
@alex:谢谢,我确实注意到了赞成票。我几乎已经习惯了 SO 的匿名反对票。这只会让我感到恼火,因为我真的很想知道我是否说了不准确或错误的话,而且它们往往会让你认为你刚刚被其他想要更好地了解自己答案的回答者否决了。幸运的是,我不是那种复仇的类型;-)
1赞 davidbuttar 9/4/2013
我认为这更像是 JavaScript 损坏块范围的解决方法。您应该能够添加 var j = i;在第一个 setTimeout 之前,并获取使用该 j 的警报。另一种解决方法是像这样使用 'with' : for (var i = 0; i < someVar.length; i++){ with({i:i}){window.setTimeout(function () {alert(“设置此计时器时 i 的值为 ”+i+“)}, 100);}}
1赞 antoine 7/16/2018
@AndyE 有趣可能不是正确的词。我刚刚注意到,人们经常使用自调用函数来解释闭包,就像这个页面上的许多答案一样。但 setTimeout 中的回调函数也是一个闭包;它可能被认为是“实际用途”,因为您可以从回调中访问一些其他局部变量。当我学习闭包时,意识到这对我很有用——闭包无处不在,而不仅仅是在街机 JavaScript 模式中。
73赞 Marcelo Cantos 4/28/2010 #3

你给出的例子是一个很好的例子。闭包是一种抽象机制,可让您非常干净地分离关注点。您的示例是将检测(计数调用)与语义(错误报告 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 的事件处理和 AJAX API。

评论

3赞 Hello71 11/5/2010
(int?)最后我检查了一下,JavaScript 是一种鸭子类型的语言。也许你在想 Java?
1赞 Marcelo Cantos 11/5/2010
@Hello71:我当时在想 JavaScript,但旧习惯很难改变。好渔获。
2赞 Natan Streppel 1/10/2014
@MarceloCantos看起来您在 counter 的实现中忘记了逗号。我编辑了你的帖子来纠正它。希望没关系:)
2赞 Marcelo Cantos 1/10/2014
@Streppel:抓得好!我非常高兴你能让我的代码变得更好。:-)
0赞 Dave2081 12/12/2014
试图理解#1...你会怎么称呼proximity_sort?
19赞 maerics 4/28/2010 #4

特别是在 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];
    };
  }())
};

请注意,数组可以简单地存储为对象的属性,但脚本的用户可以看到它,他们甚至可以根据需要更改它,甚至不需要您的源代码。但是,由于它由返回日期查找函数的匿名函数包围,因此它只能由查找函数访问,因此它现在是防篡改的。daysdateUtil

评论

2赞 itsmichaelwang 12/15/2014
这听起来可能很愚蠢,但是他们不能打开JavaScript文件本身并查看您的实现吗?
1赞 maerics 12/15/2014
@Zapurdead:是的,他们当然可以看到实现,但是如果不直接修改你的源代码,他们就无法改变实现(意外或有意)。我想您可以将其与 Java 中受保护的成员进行比较。
0赞 MasterOfTheHouse 12/21/2021
这就是为什么闭包存在的原因,如上所述@maerics......闭包可用于隐藏功能的实现,同时仍然显示接口......
1赞 stusmith 4/28/2010 #5

不久前,我写了一篇关于如何使用闭包来简化事件处理代码的文章。它将 ASP.NET 事件处理与客户端 jQuery 进行比较。

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

7赞 EdwardG 4/28/2010 #6

如果你对在面向对象的意义上实例化一个类的概念感到满意(即创建该类的对象),那么你就接近于理解闭包了。

可以这样想:当你实例化两个 Person 对象时,你知道类成员变量“Name”在实例之间不共享;每个对象都有自己的“副本”。同样,当您创建闭包时,free 变量(在上面的示例中为“calledCount”)绑定到函数的“instance”。

我认为您的概念飞跃受到以下事实的轻微阻碍:warnUser 函数(除了:这是一个高阶函数)闭包返回的每个函数/闭包都使用相同的初始值 (0) 绑定“calledCount”,而通常在创建闭包时,将不同的初始值设定项传递到高阶函数中更有用,就像将不同的值传递给类的构造函数一样。

因此,假设当 'calledCount' 达到某个值时,您希望结束用户的会话;您可能需要不同的值,具体取决于请求是来自本地网络还是来自 Big Bad Internet(是的,这是一个人为的例子)。为此,您可以将 calledCount 的不同初始值传递给 warnUser(即 -3 或 0?)。

文献的部分问题在于用于描述它们的命名法(“词汇范围”、“自由变量”)。不要让它欺骗你,闭包比看起来更简单......初步证据;-)

8赞 alex 4/28/2010 #7

Mozilla 开发者网络上有一个关于实用闭包的部分。

评论

1赞 James Parsons 8/15/2015
通过查看此内容,我看不出它是如何“实用”的,就好像我删除了整个代码仍然正常工作一样。闭合不是 nessecaryreturn function ()...
0赞 alex 8/17/2015
@James_Parsons 然后,您不能像在示例中那样将它们分配给事件处理程序。
0赞 MasterOfTheHouse 12/21/2021
闭包可用于隐藏功能的实现,同时仍显示接口。它可以帮助您创建只能使用闭包访问、更改和修改的私有成员。
5赞 outis 5/7/2010 #8

闭包的另一个常见用途是在方法中绑定到特定对象,从而允许在其他地方调用它(例如作为事件处理程序)。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”。

0赞 4 revsPaul Sweatte #9

闭包是创建生成器的有用方法,是按需递增的序列:

    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

引用

4赞 Sunny S.M 8/10/2015 #10

瓶盖的使用:

闭包是 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>

在上面的代码中,外部函数的名称变量可供内部函数访问,除了通过内部函数之外,没有其他方法可以访问内部变量。内部函数的内部变量充当内部函数的安全存储。它们保存“持久”但安全的数据,供内部函数使用。这些函数甚至不必分配给变量或具有名称。 阅读此处了解详情。

2赞 Tom 8/10/2015 #11

我喜欢Mozilla的函数工厂示例

function makeAdder(x) {

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

var addFive = makeAdder(5);

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

评论

16赞 Mohamad 11/2/2015
在我看来,这种例子并不能帮助人们理解闭包或它们有什么好处。除了作为示例之外,您曾经编写过多少次闭包来返回一个函数来添加数字?
0赞 Damodaran 2/9/2016 #12

参考资料:闭合的实际用法

在实践中,闭包可以创建优雅的设计,允许自定义各种计算、延迟调用、回调、创建封装范围等。

一个例子是数组的排序方法,它接受排序条件函数作为参数:

[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

函数应用于参数(在 apply 中应用于参数列表,在 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
2赞 Tomasz Grabowski 8/12/2016 #13

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);
    }
  };

})();
347赞 anon 8/20/2016 #14

假设您要计算用户单击网页上按钮的次数

为此,您将在按钮的 onclick 事件上触发一个函数来更新变量的计数

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

现在可能有很多方法,例如:

  1. 您可以使用全局变量和函数来增加计数器

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

    但是,问题在于页面上的任何脚本都可以更改计数器,而无需调用 updateClickCount()。


  1. 现在,您可能正在考虑在函数中声明变量:

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

    但是,嘿!每次调用函数时,计数器都会再次设置为 1。updateClickCount()


  1. 正在考虑嵌套函数

    嵌套函数可以访问“它们上方”的作用域。

    在此示例中,内部函数可以访问父函数中的计数器变量:updateClickCount()countWrapper()

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

    这本可以解决计数器困境,如果你可以从外部访问函数,并且你还需要找到一种只执行一次而不是每次执行的方法。updateClickCount()counter = 0


  1. 关闭救援!(自调用功能):

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

    自调用函数仅运行一次。它将 设置为零 (0),并返回函数表达式。counter

    这种方式变成了一个功能。“美妙”的部分是它可以访问父作用域中的计数器。updateClickCount

    这称为 JavaScript 闭包。它使函数可以具有“私有”变量。

    受匿名函数的作用域保护,只能使用该函数进行更改!counterupdateClickCount()

一个更生动的闭合例子

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


参考:JavaScript 闭包

评论

86赞 6/7/2017
这是第一个让我说“哦,这就是为什么我会使用闭包!
25赞 tyelford 6/14/2017
我刚刚阅读了关于关闭的 w3schools 页面,然后来到这里了解更多信息。这与 w3schools 页面相同:w3schools.com/js/js_function_closures.asp
4赞 Chris Halcrow 2/13/2018
好答案。请注意,闭包不一定是自调用函数,但它可以是。当闭包自调用的(即通过在函数后添加 () 立即调用)时,这意味着立即计算返回值,而不是在调用函数后返回函数并计算返回值。闭包实际上可以是另一个函数中的任何函数,其关键特征是它可以访问父函数的作用域,包括它的变量和方法。
1赞 Esteban Vargas 4/8/2020
ES6 的这个答案的有效性是什么?你可以只使用 let 并解决问题。只是好奇。
1赞 DAMIEN JIANG 5/26/2021
这个答案看起来太从 w3school 中刮来了。它甚至包含不相关的描述。,而示例代码中没有 add 函数。The counter is protected by the scope of the anonymous function, and can only be changed using the add function
7赞 Luke Schlangen 10/8/2016 #15

在这里,我有一个问候,我想说好几次。如果我创建一个闭包,我可以简单地调用该函数来记录问候语。如果我不创建闭包,我必须每次都传递我的名字。

不带闭合 (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();

评论

1赞 user2906608 12/3/2018
我不确定,但仍然没有闭包,您可以将其称为 var grretBilly= greeting(“Billy”, “Bob”);并调用 grretBilly();它仍然会做同样的事情??尽管您是否创建闭包,但这是一个不同的问题,但每次传递名称在这里都不是问题。
0赞 Muhammad Usman 3/3/2017 #16

我们用前端 JavaScript 编写的大部分代码都是基于事件的——我们定义一些行为,然后将其附加到用户触发的事件(例如单击或按键)。我们的代码通常作为回调附加:为响应事件而执行的单个函数。 size12、size14 和 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;

小提琴

评论

0赞 Donald Duck 3/3/2017
虽然此代码可以回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。
1赞 Zach Smith 5/6/2017
在我看来,这个例子可以通过标准函数在不闭包的情况下实现。我试图找到一个没有闭包就无法实现的例子
2赞 Darren Crabb 10/24/2017 #17

这个线程极大地帮助我更好地理解了闭包的工作原理。

从那以后,我自己做了一些实验,并提出了这个相当简单的代码,这可以帮助其他人了解如何以实际的方式使用闭包,以及如何在不同级别使用闭包来维护类似于静态和/或全局变量的变量,而不会有它们被覆盖或与全局变量混淆的风险。

这样可以跟踪按钮点击,包括每个按钮的本地级别和全局级别,计算每个按钮点击,从而生成单个数字。请注意,我没有使用任何全局变量来执行此操作,这就是练习的重点 - 有一个可以应用于任何按钮的处理程序,该按钮也有助于全局某些内容。

请专家,如果我在这里犯了任何不良行为,请告诉我!我自己还在学习这些东西。

<!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 and therefore acts
                       to count each button individually, whereas because
                       of the first closure totalButtonCount exists at
                       the scope just outside, it 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
                        its 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>
0赞 ShAkKiR 5/21/2018 #18

在给定的样本中,封闭的变量“counter”的值受到保护,只能使用给定的函数(递增、递减)进行更改。因为它是闭合的,

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

5赞 Abhilash KK 6/1/2018 #19

在这里,我有一个简单的闭合概念示例,我们可以在我们的电子商务网站或许多其他网站中使用它。

我正在将我的 JSFiddle 链接与示例一起添加。它包含一个由三个项目和一个购物车计数器组成的小型产品列表。

JSFiddle

// Counter closure 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 a JavaScript closure concept/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>

37赞 Mohd Asim Suhail 6/2/2018 #20

JavaScript 闭包可用于在应用程序中实现限制去抖动功能。

节流

限制对函数随时间推移可以调用的最大次数进行了限制。如“最多每 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)
  }
}

正如你所看到的,闭包有助于实现两个漂亮的功能,每个 Web 应用程序都应该具备这两个功能来提供流畅的 UI 体验功能。

2赞 Mandeep Kaur 2/6/2020 #21

解释闭包在 JavaScript 中的实际用途

当我们在另一个函数中创建一个函数时,我们正在创建一个闭包。闭包之所以强大,是因为它们能够读取和操作其外部函数的数据。每当调用函数时,都会为该调用创建一个新作用域。函数内部声明的局部变量属于该作用域,并且只能从该函数访问它们。当函数完成执行时,作用域通常会被销毁。

此类函数的简单示例如下:

function buildName(name) {
    const greeting = "Hello, " + name;
    return greeting;
}

在上面的示例中,函数 buildName() 声明了一个局部变量 greeting 并返回它。每个函数调用都会创建一个具有新局部变量的新作用域。函数执行完毕后,我们无法再次引用该范围,因此它被垃圾回收。

但是,当我们链接到该范围时呢?

让我们看一下下一个函数:

function buildName(name) {
    const greeting = "Hello, " + name + " Welcome ";
    const sayName = function() {
        console.log(greeting);
    };
    return sayName;
}

const sayMyName = buildName("Mandeep");
sayMyName();  // Hello, Mandeep Welcome

此示例中的函数 sayName() 是一个闭包。sayName() 函数有自己的局部作用域(带有变量 welcome),并且还可以访问外部(封闭)函数的作用域。在本例中,来自 buildName() 的变量 greeting。

执行 buildName 后,在这种情况下,作用域不会被销毁。sayMyName() 函数仍然可以访问它,因此它不会被垃圾回收。但是,除了闭包之外,没有其他方法可以从外部作用域访问数据。闭包充当全局上下文和外部范围之间的网关。

0赞 Krystian Wójcicki 4/6/2020 #22

我正在尝试学习闭包,我认为我创建的示例是一个实际用例。您可以运行代码段并在控制台中查看结果。

我们有两个独立的用户,他们拥有不同的数据。他们每个人都可以看到实际状态并更新它。

function createUserWarningData(user) {
  const data = {
    name: user,
    numberOfWarnings: 0,
  };

  function addWarning() {
    data.numberOfWarnings = data.numberOfWarnings + 1;
  }

  function getUserData() {
    console.log(data);
    return data;
  }

  return {
    getUserData: getUserData,
    addWarning: addWarning,
  };
}

const user1 = createUserWarningData("Thomas");
const user2 = createUserWarningData("Alex");

//USER 1
user1.getUserData(); // Returning data user object
user1.addWarning(); // Add one warning to specific user
user1.getUserData(); // Returning data user object

//USER2
user2.getUserData(); // Returning data user object
user2.addWarning(); // Add one warning to specific user
user2.addWarning(); // Add one warning to specific user
user2.getUserData(); // Returning data user object

2赞 Sundar Gautam 2/1/2021 #23

闭包有各种用例。在这里,我将解释闭包概念最重要的用法。

  • 闭包可用于创建私有方法和变量,就像面向对象的语言(如 java、c++ 等)一样。实现私有方法和变量后,窗口对象将无法访问在函数中定义的变量。这有助于数据隐藏和数据安全。
const privateClass = () => {
  let name = "sundar";
  function setName(changeName) {
    name = changeName;
  }
  function getName() {
    return name;
  }
  return {
    setName: setName,
    getName: getName,
  };
};

let javaLikeObject = privateClass(); \\ similar to new Class() in OOPS.

console.log(javaLikeObject.getName()); \\this will give sundar
javaLikeObject.setName("suresh");
console.log(javaLikeObject.getName()); \\this will give suresh

  • 另一个现实生活中的闭合例子:

创建索引 .html:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Program with Javascript</title>
  </head>
  <body>
    <p id="first"></p>
    <p id="second"></p>
    <button onclick="applyingConcepts()">Click</button>
    <script src="./index.js"></script>
  </body>
</html>

2)在索引 .js 中:

  let count = 0;
  return () => {
    document.getElementById("first").innerHTML = count++;
  };
})();

  • 在此示例中,当您单击一个按钮时,您的计数将在 p#id 上更新。 注意:您可能想知道此代码中有什么特别之处。检查时,您会注意到无法使用 window 对象更改 count 的值。这意味着您已经声明了私有变量计数,因此可以防止您的状态被客户端破坏。
0赞 Balram Singh 8/24/2022 #24

每个人都解释了闭包的实际用例:定义和几个例子。

我想贡献一个闭包用例列表:

  • 假设您想计算单击按钮的次数;闭合是最好的选择。
  • 限制和去抖动
  • 咖喱
  • 记住
  • 在异步环境中维护状态
  • 像一次这样的功能
  • setTimeouts
  • 迭代器