代码之家  ›  专栏  ›  技术社区  ›  alex

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

  •  238
  • alex  · 技术社区  · 15 年前

    我是 trying 我最难理解的是javascript闭包。

    我通过返回一个内部函数得到这个结果,它将有权访问在其直接父级中定义的任何变量。

    这对我有什么用?也许我还没有完全清醒过来。大部分的 examples I have seen online 不要提供任何真实的代码,只是一些模糊的例子。

    有人能给我演示一下闭包的实际用途吗?

    例如,这是一个吗?

    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 回复  |  直到 6 年前
        1
  •  203
  •   chris Frisina    10 年前

    我用闭包做过如下事情:

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

    如你所见, a 现在是一个对象,有一个方法 publicfunction ( a.publicfunction() )打电话来 privatefunction ,它只存在于闭包中。你可以 不是 呼叫 私有函数 直接(即 a.privatefunction() ) publicfunction() .

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

        2
  •  119
  •   GorvGoyl    9 年前

    假设,你想 计算用户单击按钮的次数 在网页上。
    为此,您将触发 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
    }
    

    但是,嘿!每一次 更新ickCount() 函数被调用 计数器再次设置为1。


    3)思考 嵌套函数 ?

    嵌套函数可以访问它们上面的作用域。
    在本例中,内部函数 更新ickCount() 有权访问父函数中的计数器变量 countWrapper()

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

    如果你能找到 更新ickCount() 从外部执行函数,还需要找到执行的方法 counter = 0 只有一次,不是每次。


    4) 结束救援!(自调用函数) :

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

    自调用函数只运行一次。它设置了 counter 返回一个函数表达式。

    这种方式 updateClickCount 成为一种功能。“美妙的”部分是它可以访问父范围中的计数器。

    这叫做 javascript闭包 . 它使函数有可能 私有的 “变量。

    这个 柜台 受匿名函数的作用域保护,并且只能使用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>
    
        3
  •  62
  •   Marcelo Cantos    10 年前

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

        4
  •  18
  •   Andy E    15 年前

    是的,这是一个有用的结束的好例子。对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); 
    
        5
  •  13
  •   maerics    10 年前

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

        6
  •  8
  •   Mohd Asim Suhail    7 年前

    我知道我回答这个问题的时候已经很晚了,但这可能有助于任何人在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)
        }
      }
    }
    

    去抖动 :

    去噪限制了一个函数在没有被调用的情况下经过一定时间后才被再次调用。就像“只有在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体验功能。

    我希望它能帮助别人。

        7
  •  6
  •   Dan Dascalescu    11 年前
        8
  •  5
  •   Community CDub    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);
    

    每当鼠标移动事件发生时, 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不是语法单位,而是源代码字符串(不是字符串文本)。

    见“ Streamlining my javascript with a function “举个具体的例子。

        9
  •  5
  •   Luke Schlangen    9 年前

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

    没有结束( 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();
    
        10
  •  4
  •   Edward Garson    15 年前

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

    这样想:当您实例化两个人对象时,您知道类成员变量“name”不是在实例之间共享的;每个对象都有自己的“copy”。类似地,当您创建闭包时, 自由变量 (“calledCount”在上面的示例中)已绑定到函数的“instance”。

    我认为,warnuser函数返回的每个函数/闭包(旁白:这是 高阶函数 )闭包用相同的初始值(0)绑定“calledCount”,而通常在创建闭包时,将不同的初始值设定项传递给高阶函数更为有用,这与将不同的值传递给类的构造函数非常相似。

    因此,假设当“calledCount”达到某个值时,您希望结束用户的会话;根据请求是来自本地网络还是来自大而坏的Internet,您可能需要不同的值(是的,这是一个人为的示例)。为了实现这一点,您可以将calledcount的不同初始值传递给warnuser(即-3或0?).

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

        11
  •  2
  •   Abhilash KK    7 年前

    这里我有一个简单的闭包概念的例子,我们可以在我们的电子商务网站或其他许多网站上使用。 我将在示例中添加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>
        12
  •  1
  •   stusmith    15 年前

    我曾经写过一篇文章,介绍如何使用闭包来简化事件处理代码。它比较了asp.net事件处理和客户端jquery。

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

        13
  •  1
  •   Tom    10 年前

    我喜欢Mozilla的函数工厂 example .

    function makeAdder(x) {
    
        return function(y) {
            return x + y;
        };
    }
    
    var addFive = makeAdder(5);
    
    console.assert(addFive(2) === 7); 
    console.assert(addFive(-5) === 0);
    
        14
  •  1
  •   Tomasz Grabowski    9 年前

    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 );
    
        }
      };
    
    })();
    
        15
  •  0
  •   Sunny S.M    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>
    

    在上面的代码中,内部函数可以访问外部函数的name变量,除了通过内部函数之外,没有其他方法可以访问内部变量。内部函数的内部变量作为内部函数的安全存储。它们保存“持久的”,但安全的数据,供内部函数使用。这些函数甚至不必赋给变量,也不必有名字。 阅读 here 细节

        16
  •  0
  •   Merlin    8 年前

    参考文献: Practical usage of closures

    实际上,闭包可以创建优雅的设计,允许定制各种计算、延迟调用、回调、创建封装范围等。

    数组的sort方法示例,它接受sort condition函数作为参数:

    [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
    

    函数应用于参数(应用中的参数列表和调用中的定位参数):

    (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
    
        17
  •  0
  •   Muhammad Usman vramu    8 年前

    我们在前端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;
    

    Fiddle

        18
  •  0
  •   Paul Sweatte    8 年前

    闭包是创建 ,按需递增的序列:

        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
    

    工具书类

        19
  •  0
  •   Darren Crabb    7 年前

    这个线程极大地帮助我更好地理解了闭包是如何工作的。此后,我自己做了一些实验,并提出了这个相当简单的代码,它可以帮助其他人了解如何以实际的方式使用闭包,以及如何在不同级别使用闭包来维护类似于静态和/或全局变量的变量,而不必冒m被覆盖或与全局变量混淆。这样做的目的是跟踪每个按钮在本地和全局级别上的单击,计算每个按钮的单击次数,从而形成一个图形。注意,我并没有使用任何全局变量来实现这一点,这是练习的一个要点——拥有一个可以应用于任何按钮的处理程序,该按钮也可以对全局的内容做出贡献。

    请专家们,如果我在这里犯了什么错误,一定要告诉我!我自己还在学。

    <!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>
    
        20
  •  0
  •   ShAkKiR    7 年前

    在给定的示例中,封闭变量“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
    推荐文章