What is a practical use for a closure in JavaScript?
我正在尽最大的努力围绕着JavaScript闭包。
通过返回一个内部函数,它可以访问在其直接父级中定义的任何变量。
这对我有何用处?也许我还没完全明白。我在网上看到的大多数例子都没有提供任何真实的代码,只是一些模糊的例子。
有人能给我展示一个闭包的真实世界吗?
例如,这是一个吗?
1 2 3 4 5 6 7 8 9 10 11 12 | var warnUser = function (msg) { var calledCount = 0; return function() { calledCount++; alert(msg + ' You have been warned ' + calledCount + ' times.'); }; }; var warnForTamper = warnUser('You can not tamper with our HTML.'); warnForTamper(); warnForTamper(); |
我用闭包来做如下事情:
1 2 3 4 5 6 7 8 9 10 11 | a = (function () { var privatefunction = function () { alert('hello'); } return { publicfunction : function () { privatefunction(); } } })(); |
如您所见,
这是一个很小的例子,但也许你能看到它的用途?我们使用它来强制公共/私有方法。
假设您想计算用户在网页上单击按钮的次数。为此,您正在触发
1 | <button onclick="updateClickCount()">click me</button> |
现在有很多方法,比如:
1)您可以使用全局变量和函数来增加计数器:
1 2 3 4 5 6 | var counter = 0; function updateClickCount() { ++counter; // do something with counter } |
但问题是,页面上的任何脚本都可以在不调用
2)现在,您可能正在考虑在函数内部声明变量:
1 2 3 4 5 | function updateClickCount() { var counter = 0; ++counter; // do something with counter } |
但是,嘿!每次调用
3)考虑嵌套函数?
嵌套函数可以访问它们上面的作用域。在本例中,内部函数
1 2 3 4 5 6 7 8 9 | function countWrapper() { var counter = 0; function updateClickCount() { ++counter; // do something with counter } updateClickCount(); return counter; } |
如果你能从外部达到
4)救援结束!(自调用函数):
1 2 3 4 5 6 7 8 | var updateClickCount=(function(){ var counter=0; return function(){ ++counter; // do something with counter } })(); |
自调用函数只运行一次。它将
这样,
这被称为JavaScript闭包。它使函数有可能有"私有"变量。
更生动的关闭示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var updateClickCount=(function(){ var counter=0; return function(){ ++counter; document.getElementById("spnCount").innerHTML=counter; } })(); <html> <button onclick="updateClickCount()">click me</button> you've clicked <span id="spnCount"> 0 </span> times! </html> |
你举的例子很好。闭包是一种抽象机制,允许您非常清楚地分离关注点。您的示例是一个将检测(计数调用)与语义(错误报告API)分离的案例。其他用途包括:
将参数化行为传递到算法中(经典的高阶编程):
1 2 3 | function proximity_sort(arr, midpoint) { arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; }); } |
模拟面向对象编程:
1 2 3 4 5 6 7 8 9 | function counter() { var a = 0; return { inc: function() { ++a; }, dec: function() { --a; }, get: function() { return a; }, reset: function() { a = 0; } } } |
实现奇特的流控制,例如jquery的事件处理和Ajax API。
是的,这是一个有用的闭包的好例子。对warnuser的调用在其作用域中创建
我在StackOverflow上看到的最常见的问题是,有人希望"延迟"使用在每个循环上增加的变量,但由于变量的作用域是限定的,因此对变量的每个引用都将在循环结束后进行,从而导致变量的结束状态:
1 2 3 4 | for (var i = 0; i < someVar.length; i++) window.setTimeout(function () { alert("Value of i was"+i+" when this timer was set" ) }, 10000); |
这将导致每个警报显示相同的EDOCX1值(10),即循环结束时增加的值。解决方案是创建一个新的闭包,一个单独的变量范围。这可以使用一个即时执行的匿名函数来完成,该函数接收变量并将其状态存储为参数:
1 2 3 4 5 6 | 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); |
尤其是在JavaScript(或任何EcmaScript)语言中,闭包在隐藏功能实现的同时仍然显示接口方面非常有用。
例如,假设您正在编写一个日期实用程序方法类,您希望允许用户按索引查找工作日名称,但不希望他们能够修改您在软篷下使用的名称数组。
1 2 3 4 5 6 7 8 9 10 11 | 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]; }; }()) }; |
注意,
我知道我回答这个问题非常晚,但这可能有助于2018年仍在寻找答案的任何人。
Javascript闭包可以用于在应用程序中实现节流和去缓冲功能。
节流:
限制将限制设置为一段时间内可以调用函数的最大次数。如"每100毫秒最多执行一次此函数"。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 | 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毫秒后才执行此函数,而不调用它。"
代码:
1 2 3 4 5 6 7 8 9 | 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体验功能。
我希望它能帮助别人。
在Mozilla开发者网络中有一个关于实用闭包的章节。
在这里,我有一个问候,我想说几次。如果我创建一个闭包,我可以简单地调用该函数来记录问候语。如果我不创建闭包,我必须每次都传递我的名字。
没有关闭(https://jsfiddle.net/lukeschlangen/pw61qrow/3/):
1 2 3 4 5 6 7 8 9 10 11 | 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://jsfidle.net/lukeschlangen/lb5cfve9/3/):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
闭包的另一个常见用途是将方法中的
1 2 3 4 5 6 7 8 9 10 | 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事件触发时,调用
闭包也是高阶函数的重要组成部分,允许通过参数化不同部分将多个类似函数重写为单个高阶函数,这是非常常见的模式。作为一个抽象的例子,
1 2 3 | foo_a = function (...) {A a B} foo_b = function (...) {A b B} foo_c = function (...) {A c B} |
变成
1 2 3 | fooer = function (x) { return function (...) {A x B} } |
其中a和b不是语法单位,而是源代码字符串(不是字符串文本)。
有关具体示例,请参见"使用函数简化我的javascript"。
如果您对以面向对象的方式实例化类的概念感到满意(即创建该类的对象),那么您就接近理解闭包了。
这样想:当您实例化两个人对象时,您知道类成员变量"name"不会在实例之间共享;每个对象都有自己的"copy"。同样,当您创建一个闭包时,自由变量("calledCount"在上面的示例中)被绑定到函数的"instance"上。
我认为你的概念上的飞跃受到以下事实的阻碍:warnuser函数返回的每个函数/闭包(除此之外:这是一个高阶函数)闭包都用相同的初始值(0)绑定"calledCount",而在创建闭包时,将不同的初始值设定项传递到高阶函数中更有用,这在很大程度上取决于ike将不同的值传递给类的构造函数。
因此,假设当"calledCount"达到某个值时,您希望结束用户的会话;您可能需要不同的值,这取决于请求是来自本地网络还是来自大的坏Internet(是的,这是一个人为的示例)。为了实现这一点,可以将calledCount的不同初始值传递给warnuser(即-3或0?).
文献的部分问题是用来描述它们的术语("词汇范围"、"自由变量")。不要让它愚弄你,闭包比看起来更简单…表面上的;
这里我有一个简单的关闭概念的例子,我们可以在我们的电子商务站点或其他许多站点中使用它。我将在示例中添加JSfiddle链接。它包含一个包含3个项目和一个购物车柜台的小产品列表。
杰西德
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //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(); } }) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | .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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Practical Use of JavaScript Closure consept/private variable. <span id="cartcount">0</span> <ul > <li class="item">Product 1 </li> <li class="item">Product 2 </li> <li class="item">Product 3 </li> </ul> |
我喜欢Mozilla的函数工厂示例。
1 2 3 4 5 6 7 8 9 10 11 | function makeAdder(x) { return function(y) { return x + y; }; } var addFive = makeAdder(5); console.assert(addFive(2) === 7); console.assert(addFive(-5) === 0); |
前一段时间我写了一篇关于如何使用闭包来简化事件处理代码的文章。它将ASP.NET事件处理与客户端jquery进行比较。
http://www.hackization.com/2009/02/20/closures-simply-event-handling-code/
封口的使用:
闭包是JavaScript最强大的功能之一。javascript允许函数嵌套,并允许内部函数完全访问外部函数(以及外部函数可以访问的所有其他变量和函数)内定义的所有变量和函数。但是,外部函数不能访问内部函数中定义的变量和函数。这为内部函数的变量提供了一种安全性。此外,由于内部函数可以访问外部函数的范围,因此,如果内部函数能够在外部函数的寿命之外继续存在,则外部函数中定义的变量和函数将比外部函数本身寿命更长。当内部函数以某种方式可用于外部函数之外的任何范围时,就会创建一个闭包。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
在上面的代码中,内部函数可以访问外部函数的名称变量,除了内部函数之外,没有其他方法可以访问内部变量。内部函数的内部变量充当内部函数的安全存储。它们保存着"持久的"但又安全的数据,供内部函数使用。函数甚至不必分配给变量,也不必有名称。请阅读此处了解详细信息
javascript模块模式使用闭包。这是一个很好的模式,允许你拥有类似于"公共"和"私有"的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 ); } }; })(); |
参考:闭包的实际使用
实际上,闭包可以创建优雅的设计,允许定制各种计算、延迟调用、回调、创建封装范围等。
例如,数组的sort方法接受sort condition函数作为参数:
1 2 3 | [1, 2, 3].sort(function (a, b) { ... // sort conditions }); |
映射函数作为按函数参数条件映射新数组的数组的映射方法:
1 2 3 | [1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6] |
通常,使用定义几乎无限搜索条件的函数参数来实现搜索函数比较方便:
1 2 3 | someCollection.find(function (element) { return element.someProperty == 'searchCondition'; }); |
此外,我们可以注意到应用功能,例如,将函数应用于元素数组的foreach方法:
1 2 3 4 5 | [1, 2, 3].forEach(function (element) { if (element % 2 != 0) { alert(element); } }); // 1, 3 |
函数应用于参数(应用中的参数列表和调用中的定位参数列表):
1 2 3 | (function () { alert([].join.call(arguments, ';')); // 1;2;3 }).apply(this, [1, 2, 3]); |
延迟呼叫:
1 2 3 4 | var a = 10; setTimeout(function () { alert(a); // 10, after one second }, 1000); |
回调函数:
1 2 3 4 5 6 7 8 9 10 | 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 }; |
为隐藏辅助对象而创建封装范围:
1 2 3 4 5 6 7 8 | var foo = {}; (function (object) { var x = 10; object.getX = function _getX() { return x; }; })(foo); alert(foo.getX());// get closured"x" – 10 |
Much of the code we write in front-end JavaScript is event-based — we define some behavior, then attach it to an event that is triggered by the user (such as a click or a keypress). Our code is generally attached as a callback: a single function which is executed in response to the event.
size12, size14, and size16 are now functions which will resize the body text to 12, 14, and 16 pixels, respectively. We can attach them to buttons (in this case links) as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; |
小提琴
这个线程极大地帮助我更好地理解闭包是如何工作的。从那以后,我自己做了一些实验,并提出了这个相当简单的代码,它可以帮助其他人了解闭包是如何实际使用的,以及如何在不同级别使用闭包来维护与静态和/或全局变量类似的变量,而不会有被全局变量覆盖或混淆的风险。易物。它所做的是跟踪按钮的单击,无论是在本地级别还是全局级别,计算每个按钮的单击次数,从而得出一个数字。注意,我没有使用任何全局变量来实现这一点,这是一种练习点——拥有一个可以应用于任何按钮的处理程序,这些按钮也可以对全局做出贡献。
请专家们,如果我在这里犯了什么错误,一定要告诉我!我自己还在学这些东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | <!doctype html> <html> <head> <meta charset="utf-8"> Closures on button presses <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); } } } </head> <body> Button 1 Button 2 Button 3 </body> </html> |
在给定的样本中,封闭变量"counter"的值受到保护,只能使用给定的函数(递增、递减)进行更改。因为它是封闭的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
闭包是创建生成器的一种有用方法,一种按需递增的序列:
1 2 3 4 5 | 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 |
差异总结如下:
1 2 3 4 5 6 7 8 9 | 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 |
工具书类
- AS3基础:功能