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

IT技术 javascript closures terminology
2021-01-18 04:07:43

我正在我最大的努力来解决 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();
6个回答

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

为此,您将在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
     }
    

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


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

    自调用函数只运行一次。它将 设置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>


参考:JavaScript 闭包

这是第一个让我说“哦,就是我会使用闭包原因!”的答案
2021-03-15 04:07:43
与此处讨论的示例相同w3schools.com/js/js_function_closures.asp
2021-03-31 04:07:43
好答案。请注意,尽管闭包不需要是自调用函数,但它可以是。当一个闭包自调用的(即通过在函数后添加() 立即调用),这意味着立即计算返回值,而不是在调用函数后返回函数稍后计算返回值闭包实际上可以是另一个函数中的任何函数,它的关键特征是它可以访问父函数的范围,包括它的变量和方法。
2021-04-02 04:07:43
updateClickCount() 仍然可以在开发者工具的控制台中访问,并且可以更改定义。例如,updateClickCount = function() {console.log('hacked');}
2021-04-02 04:07:43
我刚刚阅读了关于关闭的 w3schools 页面,然后来到这里了解更多信息。这与 w3schools 页面相同: w3schools.com/js/js_function_closures.asp
2021-04-11 04:07:43

我使用闭包来做类似的事情:

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

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

正如你在那里看到的,a现在是一个对象,带有一个调用的方法publicfunction( a.publicfunction()) privatefunction,它只存在于闭包中。不能privatefunction直接调用(即a.privatefunction()),只能调用publicfunction().

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

闭包在很多情况下都非常有用,但你必须更擅长函数式编程,才能以最聪明的方式使用它们。这是大多数人可以立即使用和理解的一种简单的使用方法。
2021-03-17 04:07:43
啊,如果这是一个闭包,那么我在不知不觉中使用了闭包!我经常把函数放在另一个这样的函数中,然后通过返回一个像你的例子中的对象文字来公开任何我需要的东西。
2021-03-18 04:07:43
因为即使示例只有一个函数,它也可能包含无法从外部访问的变量说:var obj = (function () { var value = 0; return { get: function () { return value; }, set: function (val) { value = val; } } })(); 对象集(20);对象.get(); => 20 等
2021-03-28 04:07:43
我知道这是一个老问题,但对我来说这仍然没有提供足够的答案。为什么不直接调用函数呢?为什么需要私有函数?
2021-04-02 04:07:43
从技术上讲,您在浏览器上用 Javascript 制作的每个函数都是一个闭包,因为 window 对象绑定到它。
2021-04-09 04:07:43

你举的例子是一个很好的例子。闭包是一种抽象机制,允许您非常干净地分离关注点。您的示例是将检测(计数调用)与语义(错误报告 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。

@Streppel:抓得好!我很高兴你让我的代码变得更好。:-)
2021-03-15 04:07:43
@Hello71:我在考虑 JavaScript,但旧习惯很难改掉。接得好。
2021-03-26 04:07:43
@MarceloCantos 看起来您在计数器的实现中忘记了逗号。我编辑了您的帖子以更正它。希望没关系:)
2021-03-29 04:07:43
试图理解#1...你如何称呼proximity_sort?
2021-04-07 04:07:43
( int?) 最后我检查过,JavaScript 是一种鸭子类型的语言。也许您在考虑 Java?
2021-04-10 04:07:43

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 体验功能。

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

我在 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);
@alex:谢谢,我确实注意到了投票。我几乎已经习惯了在这里进行匿名投票。这只会让我很恼火,因为我真的很想知道我是否说了不准确或错误的话,而且他们往往会让你认为你只是被其他一些想要更好地了解他们自己的答案的回答者否决了。幸运的是,我不是那种报复心强的人 ;-)
2021-03-23 04:07:43
@AndyE 有趣可能不是正确的词。我只是注意到人们经常使用自调用函数来解释闭包,就像这个页面上的许多答案一样。但是setTimeout里面的回调函数也是一个闭包;它可能被认为是“实际用途”,因为您可以从回调访问其他一些局部变量。当我学习闭包时,意识到这对我很有用 - 闭包无处不在,而不仅仅是在街机 JavaScript 模式中。
2021-03-23 04:07:43
我认为这更像是 JavaScript 损坏的块作用域的一种解决方法。你应该能够添加 var j = i; 在第一个 setTimeout 之前并获取使用该 j 的警报。另一种解决方法是像这样使用“with”: for (var i = 0; i < someVar.length; i++){ with({i:i}){window.setTimeout(function () {alert("Value of设置此计时器时,我是“+i+””)}, 100);}}
2021-03-26 04:07:43
有趣的 -1,我猜这不是“javascript 中闭包的实际用途”?
2021-03-28 04:07:43
我在阅读它时发现了一些用处,所以我在投反对票之前给了 +1。
2021-04-04 04:07:43