简体   繁体   English

JavaScript中闭包的确切用法是什么?

[英]What is the exact use of closures in Javascript?

嗨,我仍然不确定在javascript中使用闭包的确切用法。我对闭包有一个想法:“闭包是一个内部函数,可以访问外部(封装)函数的变量-作用域链”。但是我没有知道为什么我们在javascript中使用闭包。

It allows you to succinctly express logic without needing to repeat yourself or supply a large number of parameters and arguments for a callback function. 它使您可以简洁地表达逻辑,而无需重复自己或为回调函数提供大量参数和参数。

There is more information available here: javascript closure advantages? 这里有更多信息: javascript闭包的优势?

In general, the main use of closures is to create a function that captures the state from it's context. 通常,闭包的主要用途是创建一个从上下文捕获状态的函数。 Consider that the function has the captured variables but they are not passed as parameters. 请考虑该函数具有捕获的变量,但它们不会作为参数传递。

So, you can think of it of a way to create families of functions. 因此,您可以想到一种创建功能族的方法。 For example if you need a series of function that only differ in one value, but you cannot pass that value as a parameter, you can create them with closures. 例如,如果您需要一系列仅在一个值上有所不同的函数,但是无法将该值作为参数传递,则可以使用闭包来创建它们。

The Mozilla Developer Network has a good introduction to closures. Mozilla开发人员网络很好地介绍了闭包。 It shows the following example: 它显示以下示例:

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

In this case the function displayName has captured the variable name . 在这种情况下,函数displayName已捕获变量name As it stand this example is not very useful, but you can consider the case where you return the function: 就目前而言,该示例不是很有用,但是您可以考虑返回函数的情况:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Here the function makeFunc return the function displayName that has captured the variable name . 在这里,函数makeFunc返回捕获了变量name的函数displayName Now that function can be called outside by assigning it to the variable myFunc . 现在,可以通过将其分配给变量myFunc来在外部调用该函数。

To continue with this example consider now if the captured variable name were actually a parameter: 要继续此示例,现在考虑捕获的变量name是否实际上是参数:

function makeFunc(name) {
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc("Mozilla");
myFunc();

Here you can see that makeFunc create a function that shows a message with the text passed as parameter. 在这里,您可以看到makeFunc创建了一个函数,该函数显示一条消息,其中文本作为参数传递。 So it can create a whole family of function that vary only on the value of that variable ( "Mozilla" in the example). 因此,它可以创建整个函数系列,这些函数仅在该变量的值上有所不同(示例中为"Mozilla" )。 Using this function we can show the message multiple times. 使用此功能,我们可以多次显示该消息。

What is relevant here is that the value that will be shown in the massage has been encapsulated. 这里重要的是,将在按摩中显示的值已封装。 We are protecting this value in a similar fashion a private field of a class hides a value in other languages. 我们以类似的方式保护此值,一个类的私有字段用其他语言隐藏了一个值。

This allows you to, for example, create a function that counts up: 例如,这使您可以创建一个递增计数的函数:

function makeFunc(value) {
  function displayName() {
    alert(value);
    value++;
  }
  return displayName;
}

var myFunc = makeFunc(0);
myFunc();

In this case, each time you call the function that is stored in myFunc you will get the next number, first 0, next 1, 2... and so on. 在这种情况下,每次调用存储在myFunc中的函数时,您将获得下一个数字,第一个0,下一个1、2 ...等。


A more advanced example is the "Counter" "class" also from the Mozilla Developer Network. 一个更高级的示例是Mozilla开发人员网络中的“ Counter”“ class”。 It demonstrates the module pattern: 它演示了模块模式:

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */

Here you can see that Counter is an object that has a method increment that advances the privateCounter variable, and the method decrement that decrements it. 在这里,您可以看到Counter是一个对象,该对象的方法incrementprivateCounter变量,而方法decrement该变量。 It is possible to query the value of this variable by calling the method value . 可以通过调用方法value来查询此变量的value

The way this is archived is with an auto-invocation of an anonymous function that creates a hidden scope where the varialbe privateCounter is declared. 存档的方式是自动调用匿名函数,该函数创建一个隐藏范围,在该范围中声明varialbe privateCounter Now this variable will only be accessible from the functions that capture its value. 现在,只能从捕获其值的函数中访问此变量。

It is because of information hiding . 这是因为信息隐藏

var myModule = (function (){

    var privateClass = function (){};
    privateClass.prototype = {
        help: function (){}
    };

    var publicClass = function (){
        this._helper = new privateClass();
    };
    publicClass.prototype = {
        doSomething: function (){
            this._helper.help();
        }
    };

    return {
        publicClass: publicClass
    };

})();

var instance = new myModule.publicClass();
instance.doSomething();

In javascript you don't have classes with ppp (public, protected, private) properties so you have to use closures to hide the information. 在javascript中,您没有具有ppp(公共,受保护,私有)属性的类,因此您必须使用闭包来隐藏信息。 On class level that would be very expensive, so the only thing you can do for better code quality to use closure on module level, and use private helper classes in that closure. 在类级别上这将是非常昂贵的,因此,要获得更好的代码质量,您唯一可以做的就是在模块级别使用闭包,并在该闭包中使用私有帮助器类。 So closure is for information hiding. 因此,封闭是为了隐藏信息。 It is not cheap, it cost resources (memory, cpu, etc..) so you have to use closures just in the proper places. 它并不便宜,它消耗资源(内存,cpu等),因此您只能在适当的地方使用闭包。 So never use it on class level for information hiding, because it is too expensive for that. 因此,请勿在类级别使用它来隐藏信息,因为这样做太昂贵了。

Another usage to bind methods to instances by using callbacks. 通过使用回调方法绑定到实例的另一种用法。

Function.prototype.bind = function (context){
    var callback = this;
    return function (){
        return callback.apply(context, arguments);
    };
};

Typical usage of bound function is by asynchronous calls: defer, ajax, event listeners, etc... 绑定函数的典型用法是通过异步调用:defer,ajax,事件监听器等。

var myClass = function (){
    this.x = 10;
};
myClass.prototype.displayX = function (){
    alert(this.x);
};

var instance = new myClass();
setTimeout(instance.displayX.bind(instance), 1000); //alerts "x" after 1 sec

Imagine if instead of 想象一下,如果不是

   alert("Two plus one equals" + (2+1) );

you'd be forced to declare a variable for every constant you ever use. 您将不得不为使用过的每个常量声明一个变量。

   var Two = 2;
   var One = 1;
   var myString = "Two plus one equals";
   alert(myAlert + (Two + One) );

You'd go crazy if you had to define a variable for every single constant before you can ever use it. 如果必须在使用前为每个常量定义一个变量,就会发疯。

The access to local variables in case of closures is an advantage, but the primary role - usefulness - is the use of a function as a primary expression, a constant. 在闭包的情况下访问局部变量是一个优点,但是主要作用(有用性)是将函数用作主要表达式,常量。

Take 采取

  function Div1OnClick()
  {
       Counter.clickCount ++; 
  }

  $('#Div1').click(Div1OnClick);

versus

  $('#Div1').click(function(){ Counter.clickCount++; });

You don't create a new function name in the "above" namespace just to use it once. 您不会在“上方”命名空间中创建新的函数名称,而只能使用一次。 The actual activity is right there where it's used - you don't need to chase it across the code to where it was written. 实际的活动就在使用它的地方-您无需将其遍历整个代码到编写它的地方。 You can use the actual function as a constant instead of first defining and naming it and then calling it by name, and while there are countless caveats, advantages and tricks connected to closures, that's the one property that sells them. 您可以将实际函数用作常量,而不是先定义和命名它,然后再按名称调用它,尽管闭包有无数的警告,优点和技巧,但这是出售它们的一个属性。

Closures are a powerful construct used to implement a lot of additional features in JavaScript. 闭包是一种强大的构造,用于在JavaScript中实现许多其他功能。 For instance a closure can be used to expose private state as follows: 例如,可以使用闭包来公开私有状态,如下所示:

function getCounter() {
    var count = 0;

    return function () {
        return ++count;
    };
}

var counter = getCounter();

alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3

In the above example when we call getCounter we create a private variable count . 在上面的示例中,当我们调用getCounter我们创建了一个私有变量count Then we return a function which return count incremented. 然后我们返回一个返回count递增的函数。 Hence the function we return is a closure in the sense that it closes over the variable count and allows you to access it even after count goes out of scope. 因此,从某种意义上说,我们返回的函数是一个闭包,它关闭了变量count ,即使在count超出范围后也允许您访问它。

That's a lot of information stuffed in a few lines. 几行中塞满了很多信息。 Let's break it down? 让我们分解一下?

Okay, so variables have a lifetime just like people do. 好的,变量就像人一样有生命。 They are born, they live and they die. 他们出生,生活,死亡。 The beginning scope marks the birth of a variable and the end of a scope marks the death of a variable. 范围的开始标志着变量的诞生,范围的结束标志着变量的死亡。

JavaScript only has function scopes. JavaScript仅具有函数作用域。 Hence when you declare a variable inside a function it's hoisted to the beginning of the function (where it's born). 因此,当您在函数内部声明变量时,该变量将被提升到该函数的开头(在其出生的地方)。

When you try to access a variable which is not declared you get a ReferenceError . 当您尝试访问未声明的变量时,会出现ReferenceError However when you try to access a variable which is declared later on you get undefined . 但是,当您尝试访问稍后声明的变量时,将得到undefined This is because declarations in JavaScript are hoisted. 这是因为悬挂了JavaScript中的声明。

function undeclared_variable() {
    alert(x);
}

undeclared_variable();

When you try to access an undeclared variable you get a ReferenceError . 当您尝试访问未声明的变量时,会出现ReferenceError

function undefined_variable() {
    alert(x);

    var x = "Hello World!";
}

undefined_variable();

When you try to access a variable which is declared later in the function you get undefined because only the declaration is hoisted. 当您尝试访问稍后在函数中声明的变量时,您将得到undefined因为仅悬挂了声明。 The definition comes later. 该定义稍后出现。


Coming back to scopes a variable dies when it goes out of scope (usually when the function within which the variable is declared ends). 返回范围时,变量超出范围时将死亡(通常是在声明该变量的函数结束时)。

For example the following program will give a ReferenceError because x is not declared in the global scope. 例如,以下程序将给出ReferenceError因为x不在全局范围内声明。

function helloworld() {
    var x = "Hello World!";
}

helloworld();

alert(x);

Closures are interesting because they allow you to access a variable even when the function within which variable is declared ends. 闭包很有趣,因为闭包允许您访问变量,即使声明了变量的函数结束也是如此。 For example: 例如:

function getCounter() {
    var count = 0;

    return function () {
        return ++count;
    };
}

var counter = getCounter();

alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3

In the above program the variable count is defined in the function getCounter . 在上面的程序中,变量count在函数getCounter定义。 Hence when a call to getCounter ends the variable count should die as well. 因此,当对getCounter的调用结束时,变量count应该消失。

However it doesn't. 但是事实并非如此。 This is because getCounter returns a function which accesses count . 这是因为getCounter返回一个访问count的函数。 Hence as long as that function ( counter ) is alive the variable count will stay alive too. 因此,只要该函数( counter )处于活动状态,变量count也将保持活动状态。

In this case the function which is returned ( counter ) is called a closure because it closes over the variable count which is called the upvalue of counter . 在这种情况下被返回(功能counter )被称为闭包,因为它关闭了该变量count这就是所谓的upvaluecounter

Uses 用途

Enough with the explanation. 足够的解释。 Why do we need closures anyway? 为什么我们仍然需要关闭?

As I already mentioned before the main use of closures is to expose private state as is the case with the getCounter function. 如前所述,闭包的主要用途是公开私有状态,就像getCounter函数一样。

Another common use case of closures is partial application . 闭包的另一个常见用例是部分应用 For instance: 例如:

function applyRight(func) {
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        var rest = Array.prototype.slice.call(arguments);
        return func.apply(this, rest.concat(args));
    };
}

function subtract(a, b) {
    return a - b;
}

var decrement = applyRight(subtract, 1);

alert(decrement(1)); // 0

In the above program we had a function called subtract . 在上面的程序中,我们有一个名为subtract的函数。 We used partial application to create another function called decrement from this subtract function. 我们使用部分应用程序从此subtract函数创建了另一个称为decrement函数。

As you can see the decrement function is actually a closure which closes over the variables func and args . 如您所见, decrement函数实际上是一个闭包,它关闭了变量funcargs

That's pretty much all you need to know about closures. 这就是关于闭包的几乎所有知识。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM