首页 文章

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

提问于
浏览
233

我是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 回答

  • 0

    我用闭包来做这样的事情:

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

    正如你在那里看到的那样, a 现在是一个对象,方法 publicfunctiona.publicfunction() )调用 privatefunction ,它只存在于闭包内 . 您可以 NOT 直接调用 privatefunction (即 a.privatefunction() ),只需 publicfunction() .

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

  • 5

    假设您要在网页上 count the number of times user clicked a button .
    为此,您将在 onclick 事件按钮上触发一个函数来更新变量的计数

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

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

    1)您可以使用 global variable 和函数来增加 counter

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

    但是,陷阱是 any script on the page can change the counter, without calling updateClickCount() .


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

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

    但是,嘿!每次调用 updateClickCount() 函数时, counter is set to 1 again.


    3)想到 Nested functions

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

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

    如果您可以从外部到达 updateClickCount() 函数,并且您还需要找到一种方法来执行 counter = 0 ,而不是每次都执行 counter = 0 ,这可以解决相反的困境 .


    4) Closure to the rescue! (self-invoking function)

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

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

    这样 updateClickCount 成为一个功能 . "wonderful"部分是它可以访问父范围中的计数器 .

    这被称为 JavaScript closure . 它使函数具有“私有”变量成为可能 .

    counter 受匿名函数范围的保护,只能使用add函数进行更改!

    More lively example on 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>
    
  • 6

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

    • 将参数化行为传递给算法(经典的高阶编程):
    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
    • 模拟面向对象编程:
    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
    
    • 实现奇异的流控制,例如jQuery的事件处理和AJAX API .
  • 199

    是的,这是有用关闭的一个很好的例子 . 对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);
    
  • 6

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

  • 0
  • 0

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

    Javascript闭包可用于在您的应用程序中实现 throttledebounce 功能 .

    Throttling

    节流放了一个限制为一个函数可以随时间调用的最大次数 . 如“每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)
        }
      }
    }
    

    Debouncing

    去抖对一个函数设置了一个限制,直到一段时间过去而没有调用它为止 . 正如“只有在没有被调用的情况下经过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

    闭包的另一个常见用途是将方法中的 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不是语法单元,而是源代码字符串(不是字符串文字) .

    有关具体示例,请参阅“Streamlining my javascript with a function” .

  • 4

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

    没有闭包(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();
    
  • 13

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

    可以这样想:当您实例化两个Person对象时,您知道实例之间不共享类成员变量"Name";每个对象都有自己的'copy' . 类似地,当您创建闭包时,自由变量(上例中的'calledCount')绑定到函数的'instance' .

    我认为你的概念上的飞跃受到一个事实的影响,因为warnUser函数返回的每个函数/闭包(除了:这是一个高阶函数)闭包用相同的初始值(0)绑定'calledCount',而通常在创建闭包时它是将不同的初始化器传递给高阶函数更有用,就像将不同的值传递给类的构造函数一样 .

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

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

  • 1

    在这里,我有一个简单的闭包概念示例,我们可以在我们的电子商务网站或许多其他网站中使用它 . 我正在添加我的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>
    
  • 102

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

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

  • 5

    我喜欢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);
    
  • 0

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

    Use of Closures :

    闭包是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变量可以被内部函数访问,除了通过内部函数之外没有其他方法可以访问内部变量 . 内部函数的内部变量充当内部函数的安全存储 . 它们为内部函数提供了"persistent"但又安全的数据 . 甚至不必将功能分配给a变量,或者有一个名字 . 阅读here了解详情

  • 0

    参考:Practical usage of closures

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

    数组的排序方法示例,它接受排序条件函数作为参数:

    [1, 2, 3].sort(function (a, b) {
        ... // sort conditions
    });
    

    将函数映射为数组的map方法,该函数根据函数参数的条件映射新数组:

    [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中,在定义的参数中 - 在调用中):

    (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
    
  • 1

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

  • 1

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

    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
    

    References

  • 18

    这个主题帮助我更好地理解了闭包的工作原理 . 我已经做了一些自己的实验,并提出了这个相当简单的代码,可以帮助其他人看到如何以实用的方式使用闭包,以及如何在不同的级别使用闭包来维护类似于静态的变量/或全局变量,没有被覆盖或与全局变量混淆的风险 . 它的作用是跟踪每个按钮和全局级别的本地级别的按钮点击次数,计算每个按钮点击次数,从而为单个数字做出贡献 . 注意我没有使用任何全局变量来执行此操作,这是练习的重点 - 具有可应用于任何按钮的处理程序,该按钮也有助于全局 .

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

    <!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>
    
  • 62

    在给定的示例中,封闭变量'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
    

相关问题