Home  >  Article  >  Web Front-end  >  Deepen your understanding of the concept of JavaScript closures

Deepen your understanding of the concept of JavaScript closures

高洛峰
高洛峰Original
2016-11-28 15:16:131125browse

I checked a lot of JavaScript closure-related information on the Internet, and most of the information written was very academic and professional. For beginners, let alone understanding closures, even text descriptions are difficult to understand. The purpose of writing this article is to use the most popular words to reveal the true face of JavaScript closures.

1. What is closure?

The "official" explanation is: a closure is an expression (usually a function) that has many variables and an environment bound to these variables, so these variables are also part of the expression. I believe that few people can directly understand this sentence because his description is too academic. In fact, this sentence in layman's terms means: all functions in JavaScript are closures. But generally speaking, the closure generated by nested functions is more powerful, and it is what we call "closure" most of the time. Look at the code below:

function a() {
    var i = 0;
    function b() {
        alert(++i);
    }
    return b;
}
var c = a();
c();

This code has two characteristics:

function b is nested inside function a;

function a returns function b.

In this way, after executing var c=a(), variable c actually points to function b. Variable i is used in b. After executing c(), a window will pop up to display the value of i (the first time for 1). This code actually creates a closure. Why? Because variable c outside function a refers to function b within function a, that is to say: when the internal function b of function a is referenced by a variable outside function a, a so-called "closure" is created.

Let’s be more thorough. The so-called "closure" is to define another function in the constructor body as the method function of the target object, and the method function of this object in turn refers to the temporary variable in the outer function body. This allows the temporary variable values ​​used by the original constructor body to be indirectly maintained as long as the target object can always maintain its methods during its lifetime. Although the initial constructor call has ended and the name of the temporary variable has disappeared, the value of the variable can always be referenced within the method of the target object, and the value can only be accessed through this method. Even if the same constructor is called again, only new objects and methods will be generated, and the new temporary variables only correspond to new values, which are independent from the last call.

In order to have a deeper understanding of closures, let us continue to explore the functions and effects of closures.

2. What are the functions and effects of closure?

In short, the function of the closure is that after a is executed and returned, the closure prevents Javascript's garbage collection mechanism GC from reclaiming the resources occupied by a, because the execution of a's internal function b needs to depend on a Variables. This is a very straightforward description of the role of closures. It is not professional or rigorous, but you can definitely understand it. Understanding closures requires a step-by-step process.

In the above example, due to the existence of closure, i in a will always exist after function a returns. In this way, every time c() is executed, i will be the value of i that is alerted after adding 1.

Then let’s imagine another situation. If a returns something other than function b, the situation is completely different. Because after a is executed, b is not returned to the outside world of a, but is only referenced by a. At this time, a will only be referenced by b. Therefore, functions a and b refer to each other but are not disturbed by the outside world (referenced by the outside world). , functions a and b will be recycled by GC.

3. The micro world of closures

If we want to have a deeper understanding of closures and the relationship between function a and nested function b, we need to introduce several other concepts: function execution context (execution context), active object (call) object), scope, scope chain. Take the process of function a from definition to execution as an example to illustrate these concepts.

When defining function a, the js interpreter will set the scope chain of function a to the "environment" where a is when defining a. If a is a global function, there will only be a window object in the scope chain. .

When executing function a, a will enter the corresponding execution context.

In the process of creating an execution environment, a scope attribute will first be added to a, which is the scope of a, and its value is the scope chain in step 1. That is, the scope chain of a.scope=a.

Then the execution environment will create a call object. An active object is also an object that has properties, but it does not have a prototype and cannot be accessed directly from JavaScript code. After creating the active object, add the active object to the top of a's scope chain. At this time, a's scope chain contains two objects: a's active object and the window object.

The next step is to add an arguments attribute on the active object, which holds the parameters passed when calling function a.

最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象

当在函数b中访问一个变量的时候,搜索顺序是:

先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。

如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

如果整个作用域链上都无法找到,则返回undefined。

小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:

function f(x) {
    var g = function () { return x; }
    return g;
}
var h = f(1);
alert(h());

这段代码中变量h指向了f中的那个匿名函数(由g返回)。

假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。

假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。

如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。

4. 闭包的应用场景

保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。

function Constructor(...) {
    var that = this;
    var membername = value;
    function membername(...) {...}
}

以上3点是闭包最基本的应用场景,很多经典案例都源于此。

5. JavaScript的垃圾回收机制

在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

6. 结语

理解JavaScript的闭包是迈向高级JS程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。


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