JavaScript中的闭包的实际用法是什么?
我正在尽我最大的努力来围绕JavaScriptclosures。
我通过返回一个内部函数来获得它,它将有权访问其直接父级中定义的任何variables。
这对我有用吗? 也许我还没有把我的头脑呢。 我在网上看到的大多数例子都没有提供任何真实世界的代码,只是模糊的例子。
有人能告诉我一个真正的世界使用封闭?
例如,这是一个吗?
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();
我使用闭包来做这样的事情:
a = (function () { var privatefunction = function () { alert('hello'); } return { publicfunction : function () { privatefunction(); } } })();
正如你在那里可以看到的, a
现在是一个对象,有一个方法publicfunction
( a.publicfunction()
),它调用privatefunction
,它只存在于闭包中。 你不能直接调用privatefunction
(即a.privatefunction()
),只是publicfunction()
。
它是一个最小的例子,但也许你可以看到它的用途? 我们用这个来强制公开/私人的方法。
你给的例子是一个很好的例子。 闭包是一个抽象机制,可以让你非常干净地分离问题。 你的例子是从语义(错误报告API)分离仪器(计数调用)的情况。 其他用途包括:
-
将参数化行为传递给algorithm(经典的高阶编程):
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; } } }
-
实现异国stream量控制,如jQuery的事件处理和AJAX API。
假设你想统计用户点击网页上的一个button的次数 。
为此,您正在button的onclick
事件上触发一个函数来更新variables的计数
<button onclick="updateClickCount()">click me</button>
现在可以有许多方法,如:
1)你可以使用一个全局variables和一个函数来增加计数器 :
var counter = 0; function updateClickCount() { ++counter; // do something with counter }
但是,陷阱是, 页面上的任何脚本都可以更改计数器,而无需调用updateClickCount()
。
2)现在,您可能正在考虑在函数内部声明variables:
function updateClickCount() { var counter = 0; ++counter; // do something with counter }
但是,嘿! 每次updateClickCount()
函数时, 计数器都被重新设置为1。
3)关于嵌套函数的思考?
嵌套函数可以访问它们之上的范围。
在这个例子中,内部函数updateClickCount()
可以访问父函数countWrapper()
的计数器variables
function countWrapper() { var counter = 0; function updateClickCount() { ++counter; // do something with counter } updateClickCount(); return counter; }
如果你可以从外部访问updateClickCount()
函数,并且你还需要find一种方法来每次只执行一次counter = 0
,这可能解决了counter counter的困境。
4) closures救援! (自我调用function) :
var updateClickCount=(function(){ var counter=0; return function(){ ++counter; // do something with counter } })();
自调用函数只运行一次。 它将counter
设置为零(0),并返回一个函数expression式。
这样updateClickCount
就成了一个函数。 “精彩”部分是它可以访问父范围内的计数器。
这被称为JavaScriptclosures 。 它使一个函数具有“ 私有 ”variables成为可能。
该counter
受匿名函数的作用域保护,只能使用添加函数进行更改!
closures的更生动的例子:
<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>
是的,这是一个有用的封闭的好例子。 对warnUser的调用在其作用域中创build了calledCountvariables,并返回一个存储在warnForTamper
variables中的匿名函数。 因为仍然有一个闭包使用了calledCountvariables,所以在函数退出时它不会被删除,所以每次调用warnForTamper()
都会增加scopedvariables并提醒这个值。
我在StackOverflow上看到的最常见的问题是有人想要“延迟”使用在每个循环中增加的variables,但是因为variables是作用域的,所以对variables的每个引用都将在循环结束后导致variables的结束状态:
for (var i = 0; i < someVar.length; i++) window.setTimeout(function () { alert("Value of i was "+i+" when this timer was set" ) }, 10000);
这会导致每个警报都显示相同的值i
,即循环结束时增加的值。 解决scheme是创build一个新的闭包,一个单独的variables范围。 这可以通过使用即时执行的匿名函数来完成,该函数接收variables并将其状态存储为参数:
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)语言中,特别是,闭包在隐藏function实现的同时仍然可以揭示界面。
例如,假设您正在编写一类date实用程序方法,并且您希望允许用户按索引查找星期几名称,但不希望它们能够修改在引擎盖下使用的名称数组。
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
对象的一个属性存储,但是对于脚本的用户来说是可见的,甚至可以在需要时更改它,甚至不需要源代码。 但是,由于它是由匿名函数包含的,它返回date查找函数,所以它只能被查找函数访问,所以它现在是防篡改的。
Mozilla开发者networking上有一个关于实用闭包的部分。
闭包的另一个常见用法是将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不是语法单位,而是源代码string(不是string文字)。
有关具体示例,请参阅“ 使用函数简化我的JavaScript ”。
在这里,我有一个问候,我想多次说。 如果我创build一个闭包,我可以简单地调用该函数来logging问候语。 如果我不创build封闭的话,我必须每一次都要通过我的名字。
没有closures( 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");
closures( 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();
如果你对在面向对象的意义上实例化类的概念感到满意(即创build该类的对象),那么你接近理解闭包。
这样想:当你实例化两个Person对象时,你知道类成员variables“Name”不在实例之间共享; 每个对象都有自己的“复制”。 同样,当你创build一个闭包时, 自由variables (上面例子中的“calledCount”)被绑定到函数的“实例”。
我认为你的概念上的飞跃是受到这样一个事实的影响,即warnUser函数返回的每个函数/闭包(这是一个更高阶的函数 )闭包使用相同的初始值(0)绑定了“calledCount”,而通常在创build闭包时将不同的初始化器传递给高阶函数更有用,就像将不同的值传递给类的构造器一样。
所以,假设'calledCount'达到某个值时你想结束用户的会话; 你可能需要不同的值,取决于请求是从本地networking还是大的坏的互联网(是的,这是一个人为的例子)。 为了达到这个目的,你可以把calledCount的不同初始值传递给warnUser(即-3或者0?)。
文献中的部分问题是用来描述它们的术语(“词汇范围”,“自由variables”)。 不要让它欺骗你,closures比看起来更简单…表面看上去;-)
我喜欢Mozilla的函数工厂的例子 。
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.hackification.com/2009/02/20/closures-simplify-event-handling-code/
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 ); } }; })();
另一个很好的例子是转换器类:
function converter(toUnit, factor, offset, input) { offset = offset || 0; return [((offset+input)*factor).toFixed(2), toUnit].join(" "); } var milesToKm = converter.curry('km',1.60936,undefined); var poundsToKg = converter.curry('kg',0.45460,undefined); var farenheitToCelsius = converter.curry('degrees C',0.5556, -32); milesToKm(10); // returns "16.09 km" poundsToKg(2.5); // returns "1.14 kg" farenheitToCelsius(98); // returns "36.67 degrees C"
解释:
当你声明一个局部variables…那个variables…有一个范围。 意思是,它生活在所宣布的“词汇”范围之内。
现在…
注意variablesOFFSET – 它有它自己的范围。 现在注意到RETURN ,它也有它自己的作用域 – 这是返回的作用域,与OFFSETS作用域完全分离。 然而…不知何故…… OFFSET的值仍然可用于返回的范围(供使用)。
怎么样?
这就是CLOSURE所做的事情:它closures了原来的范围并封装它(像一个信封)。 当你看到一个函数返回一个单独的函数(或者一个单独的结果,而不是在原始范围中声明的)时,就会创build一个单独的作用域,并且这个作用域会在原始作用域上closures。
因此….封闭。 得到它?
closures的使用:
闭包是JavaScript中最强大的function之一。 JavaScript允许嵌套函数,并授予内部函数完全访问外部函数(以及外部函数可以访问的所有其他variables和函数)内定义的所有variables和函数的权限。 但是,外部函数不能访问在内部函数中定义的variables和函数。 这为内部函数的variables提供了一种安全性。 此外,由于内部函数可以访问外部函数的范围,因此如果内部函数设法超越外部函数的寿命,则外部函数中定义的variables和函数将比外部函数本身活得更长。 当内部函数以某种方式可用于外部函数之外的任何作用域时,将创build闭包。
例如:
<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>
在上面的代码中,外部函数的namevariables可以被内部函数访问,除了通过内部函数之外,没有其他的方法可以访问内部variables。 内部函数的内部variables作为内部函数的安全存储。 他们持有“坚持”,但安全的数据内部function的工作。 这些函数甚至不必被分配给一个variables,或者有一个名字。 阅读这里的细节
参考: 封闭的实际用法
在实践中,闭包可以创build优雅的devise,允许定制各种计算,延迟调用,callback,创build封装范围等。
一个数组sorting方法的例子,它接受sort-condition函数作为参数:
[1, 2, 3].sort(function (a, b) { ... // sort conditions });
映射函数作为数组的映射方法,它通过函数参数的条件映射一个新数组:
[1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6]
通常使用定义几乎无限制search条件的函数参数来实现search函数是很方便的:
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);
callback函数:
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 };
为隐藏辅助对象创build封装的作用域:
var foo = {}; (function (object) { var x = 10; object.getX = function _getX() { return x; }; })(foo); alert(foo.getX());// get closured "x" – 10
我们在前端JavaScript中编写的大部分代码都是基于事件的 – 我们定义一些行为,然后将其附加到由用户触发的事件(如点击或按键)。 我们的代码通常作为一个callback函数来附加:一个函数,它是为了响应事件而执行的。 size12,size14和size16是现在的函数,它们将正文文本分别调整为12,14和16像素。 我们可以将它们附加到button(在这种情况下,链接),如下所示:
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;
小提琴
闭包是创build生成器的一种有用的方式,按需递增顺序:
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
这个线程帮助我更好地理解封闭的工作原理。 我已经做了一些我自己的实验,并提出了这个相当简单的代码,可以帮助其他人看到如何以实际的方式使用闭包,以及如何使用不同级别的闭包来保持类似于静态的variables/或全局variables,而没有被全局variables覆盖或混淆的风险。 这样做是跟踪button点击,无论是在本地级别的每个button和一个全球级别,计算每一个button点击,有助于一个单一的数字。 注意我没有使用任何全局variables来做到这一点,这是一个练习点 – 有一个处理程序,可以应用于任何button,也有助于全球的东西。
请专家,让我知道,如果我在这里犯了任何不好的做法! 我仍然自己学习这个东西。
<!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>