Home  >  Article  >  Web Front-end  >  Take you to further understand js closures (details)

Take you to further understand js closures (details)

不言
不言Original
2018-10-18 13:39:182337browse

The content of this article is to help you further understand js closures (in detail). It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Translator: Closures have been discussed so much that even if I don’t understand closures, I’m embarrassed to say that I know JS. But when I read this article, my eyes lit up and it also made me understand closures. I have some new understanding of packages, and it involves some knowledge of classes and prototype chains. This is an article from 2012. It is a little early and the content is slightly basic, but it is very clear. I hope it can bring new understanding to readers. .

Closure is a somewhat complex and misunderstood feature of the JavaScript language. Simply put, a closure is an object that contains a method (function) and a reference to the environment when the method was created. In order to fully understand closures, we also need to understand two features in js, one is the first-class function and the other is the inner function.

First-Class Functions

In js, a method is a first-class citizen because it can be easily converted to other data types. For example, a first-level method can be constructed on the fly and assigned to a variable. It can also be passed to other methods, or returned through other methods. In addition to meeting these criteria, methods also have their own properties and methods.
Through the following examples, let's take a look at the capabilities of the first-level method.

var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();
Translator's Note: Omitting the textual explanation of the code in the original text, what is reflected here is that the first-level method can return parameters, the parameters can be another first-level function, and the returned result can also be called.

Internal methods/Inner Functions

Internal methods or nested methods refer to methods defined inside other methods. Whenever the external method is invoked, the instance of the internal method is is created. The following example reflects the use of internal methods. The add method is an external method and doAdd is an internal method.

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

In this example, an important feature is that the internal method obtains the scope of the external method, which means that the internal method can use the variables, parameters, etc. of the external method. In the example, the parameters value1 and value2 of add() are passed to the operand1 and operand2 parameters of doAdd(). However, this is not necessary, because doAdd can directly obtain value1 and value2. So we can also write the above example like this:

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

Creating Closures

The internal method obtains the scope of the external method, forming a closure . A typical scenario is that the external function returns its internal method, which keeps a reference to the external environment and saves all variables under the scope.
The following example shows how to create and use closures.

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

Explanation:

  • add returns the internal method doAdd, doAdd calls the parameters of add, and the closure is created.

  • value1 is a local variable of the add method, which is a non-local variable for doAdd (a non-local variable means that the variable is neither in the function body itself nor in the global world), and value2 is a local variable of doAdd. .

  • When add(1) is called, a closure is created and stored in increment. In the reference environment of the closure, value1 is bound to 1, and the bound 1 is equivalent to "blocking" in this function, which is also the origin of the name "closure".

  • When increment(2) is called, the closure function is entered, which means that doAdd carrying value1 is 1 is called, so the closure can essentially be regarded as the following function:

function increment(value2) {
  return 1 + value2;
}

When to use closures?

Closures can achieve many functions. For example, bind the callback function to specified parameters. Let’s talk about two scenarios that make your life and development easier.

  1. Cooperate with timers

Closure is very useful in combination with setTimeout and setInterval. Closure allows you to pass specified parameters to the callback function, such as the following For example, insert a string into the specified dom every second.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000, "some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>
</head>
<body>
  <span id="message"></span>
</body>
</html>

Unfortunately, IE does not support passing parameters to the setInterval callback. The page in IE will not display "some message" but "undefined" (no value is passed into showMessage()). To solve this problem, The expected value can be bound to the callback function through closure. We can rewrite the above code:

window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br />");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}

2. Simulate private properties
Most object-oriented programming languages ​​support private properties of objects, but js It is not a pure object-oriented language, so there is no concept of private properties. However, we can simulate private properties through closures. Recall that a closure contains a reference to the environment in which it was created. This reference is no longer in the current scope, so this reference can only be accessed within the closure. This is essentially a private property.
Look at the following example (Translator: omit text description of the code):

function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}

There is a serious problem here, because js does not support private attributes, so we cannot prevent others from modifying the name field of the instance , for example, we create a Person instance called Colin, and then change his name to Tom.

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"

没有人愿意不经同意就被别人改名字,为了阻止这种情况的发生,通过闭包让_name字段变成私有。看如下代码,注意这里的_name是Person构造器的本地变量,而不是对象的属性,闭包形成了,因为外层方法Person对外暴露了一个内部方法getName。

function Person(name) {
  var _name = name;// 注:区别在这里

  this.getName = function() {
    return _name;
  };
}

现在,当getName被调用,能够保证返回的是最初传入类构造器的值。我们依然可以为对象添加新的_name属性,但这并不影响闭包getName最初绑定的值,下面的代码证明,_name字段,事实私有。

var person = new Person("Colin");

person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"

什么时候不要用闭包?

正确理解闭包如何工作何时使用非常重要,而理解什么时候不应该用它也同样重要。过度使用闭包会导致脚本执行变慢并消耗额外内存。由于闭包太容易创建了,所以很容易发生你都不知道怎么回事,就已经创建了闭包的情况。本节我们说几种场景要注意避免闭包的产生。
1.循环中
循环中创建出闭包会导致结果异常。下例中,页面上有三个按钮,分别点击弹出不同的话术。然而实际运行,所有的按钮都弹出button4的话术,这是因为,当按钮被点击时,循环已经执行完毕,而循环中的变量i也已经变成了最终值4.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button " + i);
        });
      }
    });
  </script>
</head>
<body>
  <input type="button" id="button1" value="One" />
  <input type="button" id="button2" value="Two" />
  <input type="button" id="button3" value="Three" />
</body>
</html>

去解决这个问题,必须在循环中去掉闭包(译者:这里的闭包指的是click事件回调函数绑定了外层引用i),我们可以通过调用一个引用新环境的函数来解决。下面的代码中,循环中的变量传递给getHandler函数,getHandler返回一个闭包(译者:这个闭包指的是getHandler返回的内部方法绑定传入的i参数),独立于原来的for循环。

function getHandler(i) {
  return function handler() {
    alert("Clicked button " + i);
  };
}

window.addEventListener("load", function() {
  for (var i = 1; i < 4; i++) {
    var button = document.getElementById("button" + i);

    button.addEventListener("click", getHandler(i));
  }
});

2.构造函数里的非必要使用
类的构造函数里,也是经常会产生闭包的错误使用。我们已经知道如何通过闭包设置类的私有属性,而如果当一个方法不需要调用私有属性,则造成的闭包是浪费的。下面的例子中,Person类增加了sayHello方法,但是它没有使用私有属性。

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}

每当Person被实例化,创建sayHello都要消耗时间,想象一下有大量的Person被实例化。更好的实践是将sayHello放入Person的原型链里(prototype),原型链里的方法,会被所有的实例化对象共享,因此节省了为每个实例化对象去创建一个闭包(译者:指sayHello),所以我们有必要做如下修改:

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};

需要记得一些事情

闭包包含了一个方法,以及创建它的代码环境引用

闭包会在外部函数包含内部函数的情况下形成

闭包可以轻松的帮助回调函数传入参数

类的私有属性可以通过闭包模拟

类的构造器中使用闭包不是一个好主意,将它们放到原型链中

The above is the detailed content of Take you to further understand js closures (details). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn