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

ale*_*lex 257 javascript closures terminology

我正在最大努力围绕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();
Run Code Online (Sandbox Code Playgroud)

Fra*_*oto 223

我用闭包来做以下事情:

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

    return {
        publicfunction : function () {
            privatefunction();
        }
    }
})();
Run Code Online (Sandbox Code Playgroud)

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

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

  • 啊,如果这是一个闭包,那么我在不知情的情况下使用了闭包!我经常将函数放在另一个函数中,然后通过返回像示例中的对象文字来公开任何我需要公开的函数. (23认同)
  • 从技术上讲,您在浏览器上使用Javascript创建的每个函数都是一个闭包,因为窗口对象绑定到它. (9认同)
  • 我知道这是一个老问题,但对我来说,这仍然没有提供足够的答案.为什么不直接调用该函数?你为什么需要私人功能? (9认同)
  • 因为即使示例只有一个函数,它也可能包含*不能从外部访问的变量.说:var obj =(function(){var value = 0; return {get:function(){return value;},set:function(val){value = val;}}})(); obj.set(20); obj.get(); => 20等 (5认同)
  • 闭包在很多情况下都非常有用,但你必须更擅长函数式编程,才能以最聪明的方式使用它们。这是大多数人可以立即使用和理解的一种简单的使用方法。 (3认同)

Jer*_*yal 170

假设您想要计算用户点击网页上按钮的次数.
为此,您将触发onclick按钮事件的函数 以更新变量的计数

<button onclick="updateClickCount()">click me</button>  
Run Code Online (Sandbox Code Playgroud)

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

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

var counter = 0;

function updateClickCount() {
    ++counter;
    // do something with counter
}
Run Code Online (Sandbox Code Playgroud)

但是,缺点是页面上的任何脚本都可以更改计数器,而无需调用updateClickCount().


2)现在,您可能正在考虑在函数内声明变量:

function updateClickCount() {
    var counter = 0;
    ++counter;
    // do something with counter
}
Run Code Online (Sandbox Code Playgroud)

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


3)考虑嵌套函数

嵌套函数可以访问它们"上方"的范围.
在此示例中,内部函数updateClickCount()可以访问父函数中的计数器变量countWrapper()

function countWrapper() {
    var counter = 0;
    function updateClickCount() {
    ++counter;
    // do something with counter
    }
    updateClickCount();    
    return counter; 
}
Run Code Online (Sandbox Code Playgroud)

这可以解决相反的困境,如果你可以updateClickCount()从外部达到这个功能,你还需要找到一种方法,counter = 0不是每次只执行一次.


4)关闭救援!(自我调用功能):

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

    return function(){
     ++counter;
     // do something with counter
    }
})();
Run Code Online (Sandbox Code Playgroud)

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

这种方式updateClickCount成为一种功能."精彩"部分是它可以访问父范围中的计数器.

这称为JavaScript闭包.它使函数具有" 私有 "变量成为可能.

counter是由匿名函数的范围内保护,并且只能使用add函数来改变!

Closure更生动的例子:

<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>
Run Code Online (Sandbox Code Playgroud)

  • 这是第一个让我说"哦,*那就是为什么我会使用闭包!"的答案. (36认同)
  • 我刚刚阅读关于闭包的w3schools页面,然后来到这里获取更多信息.这与w3schools页面相同:https://www.w3schools.com/js/js_function_closures.asp (14认同)
  • 你让我的一天:) (5认同)
  • 好答案。请注意,尽管闭包不需要*是自调用函数,但*可以*是。当闭包是自调用时(即,通过在函数后加()立即调用),这意味着将立即计算返回值,而不是返回* function *并在函数之后*再计算返回值被调用。闭包实际上可以是另一个函数中的任何函数,其主要特征是它可以访问父函数的范围,包括其变量和方法。 (2认同)

Mar*_*tos 66

你给出的例子是一个很好的例子.闭包是一种抽象机制,允许您非常干净地分离关注点.您的示例是从语义(错误报告API)中分离检测(计数调用)的情况.其他用途包括:

  1. 将参数化行为传递给算法(经典的高阶编程):

    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 模拟面向对象编程:

    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 实现奇特的流控制,例如jQuery的事件处理和AJAX API.

  • (`int`?)最后我查了一下,JavaScript是一种鸭子式的语言.也许你在想Java? (3认同)
  • @MarceloCantos看起来你在计数器的实现中忘记了一个逗号.我编辑了你的帖子来纠正它.希望没关系:) (2认同)
  • @Streppel:好抓!我很高兴你能让我的代码变得更好.:-) (2认同)

Moh*_*ail 22

我知道我在回答这个问题时已经很晚了,但它可能会帮助那些仍然在2018年寻找答案的人.

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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

辩解:

去抖对一个函数设置了一个限制,直到一段时间过去而没有调用它为止.如"只有在没有被调用的情况下经过100毫秒才执行此函数".

码:

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

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

我希望它能帮助别人.


And*_*y E 18

是的,这是有用关闭的一个很好的例子.对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);
Run Code Online (Sandbox Code Playgroud)

这将导致每个警报显示相同的值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); 
Run Code Online (Sandbox Code Playgroud)


mae*_*ics 14

特别是在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];
    };
  }())
};
Run Code Online (Sandbox Code Playgroud)

请注意,该days数组可以简单地存储为dateUtil对象的属性,但随后它将对脚本的用户可见,他们甚至可以根据需要更改它,甚至不需要您的源代码.但是,由于它由匿名函数包含,它返回日期查找功能,因此只能通过查找功能访问它,因此它现在是防篡改的.

  • 这可能听起来很愚蠢,但他们不能只打开JavaScript文件并查看您的实现吗? (2认同)

Edw*_*son 7

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

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

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

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

文献的部分问题是用于描述它们的命名法(“词法范围”、“自由变量”)。不要让它愚弄你,闭包比看起来更简单......表面上看;-)


ale*_*lex 6

Mozilla开发者网络上有一节关于实用闭包的内容.


out*_*tis 5

闭包的另一个常见用途是将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);
Run Code Online (Sandbox Code Playgroud)

每当一个mousemove事件触发时,都会watcher.follow(evt)被调用.

闭包也是高阶函数的重要部分,允许通过参数化不同部分来重写多个相似函数作为单个高阶函数的非常常见的模式.作为一个抽象的例子,

foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}
Run Code Online (Sandbox Code Playgroud)

fooer = function (x) {
    return function (...) {A x B}
}
Run Code Online (Sandbox Code Playgroud)

其中A和B不是语法单元,而是源代码字符串(不是字符串文字).

有关具体示例,请参阅" 使用函数简化我的javascript ".


Luk*_*gen 5

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

没有关闭(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");
Run Code Online (Sandbox Code Playgroud)

有一个闭包(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();
Run Code Online (Sandbox Code Playgroud)


Abh*_* KK 5

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

我在示例中添加了我的 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();
    }
  })
}
Run Code Online (Sandbox Code Playgroud)
.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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)