我正在尽最大努力理解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();

当前回答

如果你熟悉面向对象意义上的实例化类的概念(即创建该类的对象),那么你就接近理解闭包了。

这样想:当你实例化两个Person对象时,你知道类成员变量“Name”在实例之间是不共享的;每个对象都有自己的“副本”。类似地,当你创建一个闭包时,自由变量(上面例子中的'calledCount')被绑定到函数的'实例'。

我认为你的概念上的突破有点受到这样一个事实的阻碍,即由warnUser函数(另外:这是一个高阶函数)返回的每个函数/闭包都绑定'calledCount'与相同的初始值(0),而通常在创建闭包时,将不同的初始化式传递到高阶函数中更有用,就像将不同的值传递给类的构造函数一样。

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

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

其他回答

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

消除抖动

deboundation限制了函数在经过一段时间后才会被再次调用。就像“仅在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体验功能。

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

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

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

特别是在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对象的属性,但是脚本用户可以看到它,他们甚至可以根据需要更改它,甚至不需要您的源代码。但是,由于它被返回日期查找函数的匿名函数所包围,因此只能由查找函数访问,因此现在它是防篡改的。

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

我正在添加示例的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>

是的,这是一个有用闭包的好例子。对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);