JavaScript闭包的实际用途是什么?

尝试我最难把我的头围绕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();
140286 次浏览

我已经使用闭包做了如下的事情:

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


return {
publicfunction : function () {
privatefunction();
}
}
})();

正如你在那里看到的,a现在是一个对象,带有一个方法publicfunction (a.publicfunction()),该方法调用privatefunction,它只存在于闭包中。你可以直接调用privatefunction(即a.privatefunction()),只需要publicfunction()

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

是的,这是一个有用闭包的好例子。对warnUser的调用在其作用域中创建calledCount变量,并返回一个匿名函数,该函数存储在warnForTamper变量中。因为仍然有一个使用calledCount变量的闭包,它不会在函数退出时被删除,所以每次调用warnForTamper()都会增加作用域变量并提醒该值。

我在Stack Overflow上看到的最常见的问题是有人想要“延迟”;使用一个在每次循环时增加的变量,但由于变量是有作用域的,因此对该变量的每次引用都是在循环结束后,从而导致变量的结束状态:

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);

你举的例子很好。闭包是一种抽象机制,允许您非常清晰地分离关注点。您的示例是将插装(计数调用)与语义(错误报告API)分离的例子。其他用途包括:

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

    function proximity_sort(arr, midpoint) {
    arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
    
  2. Simulating object oriented programming:

    function counter() {
    var a = 0;
    return {
    inc: function() { ++a; },
    dec: function() { --a; },
    get: function() { return a; },
    reset: function() { a = 0; }
    }
    }
    
  3. Implementing exotic flow control, such as jQuery's Event handling and AJAX APIs.

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

我曾经写过一篇关于如何使用闭包来简化事件处理代码的文章。它比较ASP。NET事件处理到客户端jQuery。

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

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

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

我认为你的概念上的突破受到以下事实的阻碍:warnUser函数(另外:那是高阶函数)闭包返回的每个函数/闭包都绑定'calledCount'与相同的初始值(0),而通常在创建闭包时,将不同的初始化式传递给高阶函数更有用,就像将不同的值传递给类的构造函数一样。

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

文献的部分问题是用来描述它们的命名法(“词汇范围”,“自由变量”)。不要让它欺骗你,闭包比看起来要简单得多…初步证据;-)

Mozilla开发者网络中有一个关于实际的闭包的部分。

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

具体示例参见“用一个函数简化我的javascript”。

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

    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

参考文献

闭包的使用:

闭包是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变量可以被内部函数访问,并且除了通过内部函数之外没有其他方法访问内部变量。内部函数的内部变量充当内部函数的安全存储。它们为内部函数保存了“持久”但安全的数据。这些函数甚至不需要赋值给变量,也不需要有名称。

.

.

我喜欢Mozilla的函数工厂示例

function makeAdder(x) {


return function(y) {
return x + y;
};
}


var addFive = makeAdder(5);


console.assert(addFive(2) === 7);
console.assert(addFive(-5) === 0);

参考:闭包的实际使用

在实践中,闭包可以创建优雅的设计,允许自定义各种计算、延迟调用、回调、创建封装的作用域等。

一个例子是数组的sort方法,它接受sort条件函数作为参数:

[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中应用于实参列表,在call中应用于定位实参):

(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

JavaScript模块模式使用闭包。它漂亮的图案让你拥有类似“公共”的东西;和“;private"变量。

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);
}
};


})();

假设,你想在一个网页上计算用户点击按钮的次数

为此,你在button的onclick事件上触发一个函数来更新变量的计数

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

现在有很多方法,比如:

  1. 你可以使用全局变量和一个函数来增加计数器:

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

    但是,这个陷阱是页面上的任何脚本都可以更改计数器,而无需调用updateClickCount()


  1. 现在,你可能在考虑在函数内部声明变量:

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

    但是,嘿!每次updateClickCount()函数被调用时,Counter再次被设置为1。 . c函数都会被调用


  1. Thinking about 嵌套函数?

    嵌套函数可以访问范围"above"他们。

    在这个例子中,内部函数updateClickCount()可以访问父函数countWrapper()中的计数器变量:

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

    这可以解决计数器困境,如果你可以从外部到达updateClickCount()函数,你还需要找到一种方法,只执行counter = 0一次,而不是每次。


  1. < p > 结束救援!(self-invoking函数):

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

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

    这样updateClickCount就变成了一个函数。“wonderful"部分原因是它可以访问父作用域中的计数器。

    这被称为JavaScript关闭。它使得函数可以有&;__abc1 &;变量。

    counter受匿名函数的作用域保护,只能使用updateClickCount()函数更改!

一个关于闭包的更生动的例子

<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>


参考:JavaScript闭包

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

没有闭包(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();
我们在前端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;

小提琴

这个帖子极大地帮助我更好地理解闭包是如何工作的。

从那以后,我自己做了一些实验,并提出了这个相当简单的代码,它可能会帮助其他人了解如何以实际的方式使用闭包,以及如何在不同的级别上使用闭包来维护类似于静态和/或全局变量的变量,而不会有它们被覆盖或与全局变量混淆的风险。

这将跟踪按钮点击,无论是在本地级别上的每个按钮,还是在全局级别上的每个按钮,计算每个按钮的点击,为单个数字做出贡献。请注意,我没有使用任何全局变量来完成此操作,这是练习的要点—拥有一个可以应用于任何按钮的处理程序,该按钮也对全局具有贡献。

专家们,如果我在这里犯了什么错误,请告诉我!我自己也在学习这些东西。

<!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 and therefore acts
to count each button individually, whereas because
of the first closure totalButtonCount exists at
the scope just outside, it 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
its 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>

在给定的示例中,所包含的变量'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

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

我正在添加示例的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();
}
})
}
.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 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>

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)
}
}
}

消除抖动

deboundation限制了函数在经过一段时间后才会被再次调用。如“仅在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体验功能。

解释JavaScript闭包的实际用法

当我们在另一个函数中创建一个函数时,我们是在创建一个闭包。闭包功能强大,因为它们能够读取和操作其外部函数的数据。无论何时调用函数,都会为该调用创建一个新的作用域。在函数内部声明的局部变量属于该作用域,并且只能从该函数访问它们。当函数完成执行时,作用域通常被销毁。

这类函数的一个简单例子是:

function buildName(name) {
const greeting = "Hello, " + name;
return greeting;
}

在上面的例子中,函数buildName()声明了一个局部变量greeting并返回它。每次函数调用都会创建一个新的作用域和一个新的局部变量。在函数执行完成后,我们无法再次引用该作用域,因此它被垃圾收集。

但如果我们有一个指向范围的链接呢?

让我们看看下一个函数:

function buildName(name) {
const greeting = "Hello, " + name + " Welcome ";
const sayName = function() {
console.log(greeting);
};
return sayName;
}


const sayMyName = buildName("Mandeep");
sayMyName();  // Hello, Mandeep Welcome

本例中的sayName()函数是一个闭包。sayName()函数有自己的局部作用域(变量welcome),也可以访问外部(封闭)函数的作用域。在本例中,是来自buildName()的greeting变量。

在执行buildName之后,在本例中不会销毁作用域。sayMyName()函数仍然可以访问它,因此它不会被垃圾收集。但是,除了闭包之外,没有其他从外部作用域访问数据的方法。闭包充当全局上下文和外部作用域之间的网关。

我正在尝试学习闭包,我认为我创建的示例是一个实际的用例。您可以运行一个代码片段并在控制台中查看结果。

我们有两个不同的用户,他们拥有不同的数据。它们中的每一个都可以看到实际的状态并进行更新。

function createUserWarningData(user) {
const data = {
name: user,
numberOfWarnings: 0,
};


function addWarning() {
data.numberOfWarnings = data.numberOfWarnings + 1;
}


function getUserData() {
console.log(data);
return data;
}


return {
getUserData: getUserData,
addWarning: addWarning,
};
}


const user1 = createUserWarningData("Thomas");
const user2 = createUserWarningData("Alex");


//USER 1
user1.getUserData(); // Returning data user object
user1.addWarning(); // Add one warning to specific user
user1.getUserData(); // Returning data user object


//USER2
user2.getUserData(); // Returning data user object
user2.addWarning(); // Add one warning to specific user
user2.addWarning(); // Add one warning to specific user
user2.getUserData(); // Returning data user object

闭包有各种各样的用例。在这里,我将解释闭包概念的最重要的用法。

  • 闭包可以用来创建私有方法和变量,就像面向对象的语言,如java、c++等。一旦你实现了私有方法和变量,你在函数中定义的变量将不能被窗口对象访问。这有助于数据隐藏和数据安全。
const privateClass = () => {
let name = "sundar";
function setName(changeName) {
name = changeName;
}
function getName() {
return name;
}
return {
setName: setName,
getName: getName,
};
};


let javaLikeObject = privateClass(); \\ similar to new Class() in OOPS.


console.log(javaLikeObject.getName()); \\this will give sundar
javaLikeObject.setName("suresh");
console.log(javaLikeObject.getName()); \\this will give suresh


  • 另一个关于闭包的现实例子:

创建index . html:

<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Program with Javascript</title>
</head>
<body>
<p id="first"></p>
<p id="second"></p>
<button onclick="applyingConcepts()">Click</button>
<script src="./index.js"></script>
</body>
</html>


2)在index.js:

  let count = 0;
return () => {
document.getElementById("first").innerHTML = count++;
};
})();


    在这个例子中,当你点击一个按钮时,你的计数将在p#id上更新。 注意:您可能想知道这段代码有什么特别之处。检查时,您将注意到不能使用window对象更改count的值。这意味着你已经声明了私有变量count,这样可以防止你的状态被客户端破坏

每个人都解释了闭包的实际用例:定义和几个例子。

我想贡献一个闭包用例列表:

  • 假设你想计算按钮被点击的次数;封闭是最好的选择。
  • 节流和反弹
  • 局部套用
  • 记住
  • 在异步世界中维护状态
  • 函数就像一次
  • settimeout
  • 迭代器