Closures inside functions

Lecture




  1. Lexical environment
    1. Example
  2. Access to external variables
  3. Nested functions
  4. Memory management
  5. [[Scope]] for new Function
  6. Total

In this chapter, we will continue to look at how variables work, and, as a result, we will become familiar with closures. From the global object, we turn to working inside functions.

Lexical environment

All variables inside a function are properties of a special LexicalEnvironment internal object.

We will call this object “lexical environment” or simply “variable object”.

At startup, the function creates a LexicalEnvironment object, writes there arguments, functions, and variables. The initialization process is performed in the same order as for the global object, which, generally speaking, is a special case of the lexical environment.

Unlike window , the LexicalEnvironment object is internal, it is hidden from direct access.

Example

Let's take an example to better understand how this works:

1 function sayHi(name) {
2    var phrase = "Привет, " + name;
3    alert(phrase);
4 }
5
6 sayHi( 'Вася' );

When calling a function:

  1. Before the first line of its code is executed, at the initialization stage, the interpreter creates an empty LexicalEnvironment object and fills it.

    In this case, the name argument and a single variable phrase get there:

    1 function sayHi(name) {
    2    // LexicalEnvironment = { name: 'Вася', phrase: undefined }
    3    var phrase = "Привет, " + name;
    4    alert(phrase);
    5 }
    6
    7 sayHi( 'Вася' );

  2. The function is executed.

    At runtime, the local variable phrase is assigned, that is, in other words, the new value is assigned to the LexicalEnvironment.phrase property:

    1 function sayHi(name) {
    2    // LexicalEnvironment = { name: 'Вася', phrase: undefined }
    3    var phrase = "Привет, " + name;
    4
    5    // LexicalEnvironment = { name: 'Вася', phrase: 'Привет, Вася'}
    6    alert(phrase);
    7 }
    8
    9 sayHi( 'Вася' );

  3. At the end of the function, the object with variables is usually thrown away and the memory is cleared.

Subtlety specifications

If you read the ECMA-262 specification, then we will see that we are talking about two objects: VariableEnvironment and LexicalEnvironment .

But it is also noted there that in implementations these two objects can be combined. So we avoid unnecessary details and use the term LexicalEnvironment everywhere, it quite accurately allows us to describe what is happening.

A more formal description is found in the ECMA-262 specification, sections 10.2-10.5 and 13.

Access to external variables

From the function, we can access not only a local variable, but also an external one:

1 var a = 5;
2
3 function f() {
4    alert(a); // 5
5 }

The interpreter, when accessing a variable, first tries to find a variable in the current LexicalEnvironment , and then, if it does not exist, searches for an external variable object. In this case, it is a window .

This search order is possible due to the fact that the reference to the external object of variables is stored in a special internal property of the function, which is called [[Scope]] .

Consider how it is created and used in the code above:

  1. It all starts with the creation of the function. The function is not created in a vacuum, but in a certain lexical environment.

    In the case of the above function is created in the global lexical environment window :

      Closures inside functions

    In order for the function to access external variables in the future, at the time of its creation, it receives the hidden [[Scope]] property, which refers to the lexical environment in which it was created:

      Closures inside functions

    This link appears simultaneously with the function and dies with it. The programmer cannot get or change it in any way.

  2. Later, the time comes and the function starts.

    The interpreter recalls that it has the f.[[Scope]] property:

      Closures inside functions

    ... And uses it when creating an object of variables for a function.

    A new LexicalEnvironment object receives a reference to an “external lexical environment” with a value from [[Scope]] . This link is used to search for variables that are not in the current function.

      Closures inside functions
    For example, alert(a) first searches for variables in the current object: it is empty. And then, as shown by the green arrow in the figure below - by reference, in the external environment.

      Closures inside functions

    At the code level, it looks like a search in the external scope, outside the function:

      Closures inside functions

To summarize:

  • When creating, each function receives a [[Scope]] link to an object with variables in the context of which it was created.
  • When the function starts, a new object is created with variables. It copies the link to an external object from [[Scope]] .
  • When searching for variables, it is performed first in the current variable object, and then via this link. Due to this, external variables are available in the function.

Importance: 5

What will be the result of this code?

01 var value = 0;
02
03 function f() {
04    if (1) {
05      value = true ;
06    } else {
07      var value = false ;
08    }
09
10    alert(value);
11 }
12
13 f();

Will the external variable value change?

PS What are the answers if you remove var from the line var value = false ?

Decision

The result is true , because var will be processed and the variable will be created before the code is executed.

Accordingly, the assignment of value=true will work on a local variable, and alert will output true .

The external variable does not change.

PS If var not present, then the variable will not be found in the function. The interpreter will call for it in a window and change it there.

So without var result will also be true , but the external variable will change.

[Open task in new window]

Importance: 5

What will be the result of this code? Why?

01 function test() {
02   
03    alert(window);
04
05    var window = 5;
06
07    alert(window);
08 }
09
10 test();

Decision

The result will be undefined , then 5 .

01 function test() {
02   
03    alert(window);
04
05    var window = 5;
06
07    alert(window);
08 }
09
10 test();

The var directive will be processed prior to the execution of the function code. A local variable will be created, i.e. LexicalEnvironment property:

LexicalEnvironment = {
   window: undefined
}

When the execution of the code begins and the alert triggers, it will output a local variable.

Then assignment will work, and the second alert will print 5 already.

[Open task in new window]

Importance: 4

What will be the result of code execution? Why?

1 var a = 5
2
3 ( function () {
4    alert(a)
5 })()

PS Think well! Here everyone is wrong!
PPS Attention, there is a pitfall! OK, you are warned.

Decision

The result is a mistake . Try:

1 var a = 5
2
3 ( function () {
4    alert(a)
5 })()

The fact is that after var a = 5 there is no semicolon.

JavaScript sees this code as if there were no line breaks:

1 var a = 5( function () {
2    alert(a)
3 })()

That is, it tries to call function 5 , which leads to an error.

If you put a semicolon, everything will be fine:

1 var a = 5;
2
3 ( function () {
4    alert(a)
5 })()

This is one of the most frequent and dangerous pitfalls, leading to the mistakes of those who do not put a semi-colon.

[Open task in new window]

Nested functions

Inside the function, you can declare not only local variables, but also other functions.

As a rule, this is done for auxiliary operations, for example, in the code below, makeMessage and getHello are used to generate a message:

01 function sayHi(person) {
02   
03    var message = makeMessage(person);
04    alert( message );
05
06    // ----- вспомогательные функции -----
07
08    function getHello(age) {
09      return age >= 18 ? 'Здравствуйте' : 'Привет' ;
10    }
11
12    function makeMessage(person) {
13      return getHello(person.age) + ', ' + person.name;
14    }
15 }
16
17 sayHi({
18    name: 'Петька' ,
19    age: 17
20 }); // привет, Петька

Nested functions can be declared both as Function Declaration and as Function Expression .

Nested functions are processed in exactly the same way as global ones. The only difference is that they are created in the variable object of the external function, and not in the window .

That is, when you start the external sayHi function, local variables and nested Function Declaration fall into its LexicalEnvironment . At the same time, the Function Declaration immediately ready for execution.

In the example above, when launching sayHi(person) , the following LexicalEnvironment will be created:

1 LexicalEnvironment = {
2    person: переданный аргумент,
3    message: undefined ,
4    getHello: function ...,
5    makeMessage: function ...
6 }

Then, during the execution of sayHi , nested functions can be accessed; they will be taken from the local variable object.

The nested function has access to external variables through [[Scope]] .

  1. When creating any function, including a nested function, it gets the [[Scope]] property, indicating the variable object in which it was created.
  2. When launched, it will search for variables first in itself, then in an external variable object, then in a more external one, and so on.

Therefore, in the example above, the makeMessage(person) argument can be removed from the declaration of the function makeMessage(person) .

It was:

1 function sayHi(person) {
2    ...
3    function makeMessage(person) {
4      return getHello(person.age) + ', ' + person.name;
5    }
6 }
Will become:
1 function sayHi(person) {
2    ...
3    function makeMessage() { // убрали аргумент
4      // переменная person будет взята из внешнего объекта переменных
5      return getHello(person.age) + ', ' + person.name;
6    }
7 }

Nested function can be returned.

For example, suppose sayHi does not issue an alert right there, but returns a function that does this:

01 function sayHi(person) {
02   
03    return function () { // (*)
04      var message = makeMessage(person); // (**)
05      alert( message );
06    };
07
08    // ----- вспомогательные функции -----
09
10    function getHello(age) {
11      return age >= 18 ? 'Здравствуйте' : 'Привет' ;
12    }
13
14    function makeMessage() {
15      return getHello(person.age) + ', ' + person.name;
16    }
17 }
18
19 var sayHiPete = sayHi({ name: 'Петька' , age: 17 });
20 var sayHiOther = sayHi({ name: 'Василий Иванович' , age: 35 });
21
22 sayHiPete(); // эта функция может быть вызвана позже

In real life, this is needed to call the resulting function later when it is needed. For example, when you press a button.

The returned function (*) will have full access to the arguments of the external function as well as to the other nested functions makeMessage and getHello , because it receives the [[Scope]] link, which points to the current LexicalEnvironment . Variables that are not in it, for example, person , will be taken from it.

In particular, the function makeMessage when called in the (**) string will be taken from an external variable object.

External LexicalEnvironment , in turn, may refer to even more external LexicalEnvironment , and so on.

The closure of the function is called this function itself, plus the entire chain of LexicalEnvironment , which is formed.

Sometimes they say "the variable is taken from the circuit." This means from an external variable object.

It can be said in another way: “the closure is a function and all external variables that are accessible to it”.

Memory management

JavaScript is designed so that any object and, in particular, a function, exists as long as there is a link to it, while it is somehow available to be called, accessed. For more information about memory management, see the article Memory Management in JS and DOM.

This implies an important consequence when working with closures.

The variable object of an external function exists in memory as long as there is at least one internal function that references it through the [[Scope]] property.

Let's look at examples.

  • Normally, the variable object is deleted when the function ends. Even if it has an internal function declaration:
    1 function f() {
    2    var value = 123;
    3    function g() { } // g видна только изнутри
    4 }
    5
    6 f();
    In the above code, the internal function is declared, but it remains inside. After the end of the f() operation, it will become unavailable for calls, so it will be removed from memory along with the other local variables.
  • ... But in this case, the lexical environment, including the variable a , will be saved:
    1 function f() {
    2    var a = Math.random();
    3
    4    function g() { }
    5
    6    return g;
    7 }
    8
    9 var g = f(); // функция g будет жить и сохранит ссылку на объект переменных
  • If f() is called many times, and the resulting functions are saved, for example, added to an array, then LexicalEnvironment objects with the corresponding values ​​of a will be saved:
    1 function f() {
    2    var a = Math.random();
    3
    4    return function () { };
    5 }
    6
    7 // 3 функции,
    8 // каждая ссылается на соответствующий объект LexicalEnvironment = {a: ...}
    9 var arr = [f(), f(), f()];
    Note that the variable a not used in the return function. This means that the browser optimizer can “de facto” delete it from memory. Anyway, because no one will notice.
  • In this code, the closure is first stored in memory, and after removing the reference to g dies:
    01 function f() {
    02    var a = Math.random();
    03
    04    function g() { }
    05
    06    return g;
    07 }
    08
    09 var g = f(); // функция g жива
    10 // а значит в памяти остается соответствующий объект переменных
    11
    12 g = null ; // ..а вот теперь память будет очищена

[[Scope]] for new Function

There is one exception to the general assignment rule [[Scope]] .

There is another way to create a function, which we have not talked about before, because it is used very rarely. It looks like this:

1 var sum = new Function( 'a,b' , ' return a+b; ' );
2
3 var result = sum(1,2);
4 alert(result); // 3

That is, the function is created by calling new Function(params, code) :

params
Function parameters separated by commas as strings.
code
Function code as a string.

This method is used very rarely, but in some cases it is very useful, as it allows you to construct a function during program execution, for example, from data received from a server or from a user.

When creating a function using new Function , its [[Scope]] property refers not to the current LexicalEnvironment , but to the window .

The following example demonstrates how the function created by new Function ignores external variable a and outputs global instead.

First, the usual behavior:

01 var a = 1;
02 function getFunc() {
03    var a = 2;
04  
05    var func = function () { alert(a); }; () { alert(a); };
06
07    return func;
08 }
09
10 getFunc()(); // 2 , из LexicalEnvironment функции getFunc

And now for the function created through the new Function :

01 var a = 1;
02 function getFunc() {
03    var a = 2;
04  
05    var func = new Function( '' , 'alert(a)' );
06
07    return func;
08 }
09
10 getFunc()(); // 1 , из window

Total

  1. All variables and parameters of functions are properties of the variable object LexicalEnvironment . Each function launch creates a new such object.
    At the top level, the “global object” plays the role of LexicalEnvironment , in the browser it is a window .
  2. When created, the function gets the [[Scope]] system property, which refers to the LexicalEnvironment in which it was created (except for new Function ).
  3. When the function starts, its LexicalEnvironment refers to the external one stored in [[Scope]] . Variables are first searched in their object, then in the object by reference, and so on, down to the window .

Developing without closures in JavaScript is almost impossible. You will meet them more than once in the next chapters of the textbook.

created: 2014-10-07
updated: 2021-03-13
132605



Rating 9 of 10. count vote: 2
Are you satisfied?:



Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone