Data storage in the circuit modules

Lecture



  1. Data for the counter
    1. Counter object
    2. Counter object + function
  2. Design Reception "Module"
  3. Understanding Closure Tasks

Closures can be used in hundreds of ways. Sometimes people themselves do not notice that they used closures - it is so simple and natural.

In this chapter, we will look at examples of using closures for storing data and tasks on this topic.

Data for the counter

In the example below, makeCounter creates a function that counts its calls:

01 function makeCounter() {
02 var currentCount = 0;
03
04 return function () {
05 currentCount++;
06 return currentCount;
07 };
08 }
09
10 var counter = makeCounter();
11
12 // каждый вызов увеличивает счётчик
13 counter();
14 counter();
15 alert( counter() ); // 3

The current number of calls is currentCount in the currentCount variable of the external function.

At the same time, since every call to makeCounter creates a new variable object, all the created function counters are mutually independent.

1 var c1 = makeCounter();
2
3 var c2 = makeCounter();
4
5 alert( c1() ); // 1
6 alert( c2() ); // 1, счётчики независимы

Add an argument to the counter, which, if passed, sets the value to:

01 function makeCounter() {
02 var currentCount = 0;
03
04 return function (newCount) {
05 if (newCount !== undefined ) { // есть аргумент?
06 currentCount = +newCount; // сделаем его новым значением счётчика
07 // вернём текущее значение, счётчик всегда возвращает его (это удобно)
08 return currentCount;
09 }
10
11 currentCount++;
12 return currentCount;
13 };
14 }
15
16 var counter = makeCounter();
17
18 alert( counter() ); // 1
19 alert( counter(3) ); // 3
20 alert( counter() ); // 4

Here, to test the first argument, a comparison with undefined , so that the calls counter(undefined) and counter() work identically, as if there are no arguments.

Counter object

You can go ahead and return a full-fledged object with counter management functions:

  • getNext() - get the next value, what counter() call did before.
  • set(value) - set the value.
  • reset() - reset the counter.

01 function makeCounter() {
02 var currentCount = 0;
03
04 return {
05 getNext: function () {
06 return ++currentCount;
07 },
08
09 set: function (value) {
10 currentCount = value;
11 },
12
13 reset: function () {
14 currentCount = 0;
15 }
16 };
17 }
18
19 var counter = makeCounter();
20
21 alert( counter.getNext() ); // 1
22 alert( counter.getNext() ); // 2
23
24 counter.reset();
25 alert( counter.getNext() ); // 1

... Now counter is an object with methods that use currentCount when working. From the outside, nothing else but through these methods, currentCount cannot be accessed, since it is a local variable.

Counter object + function

Unfortunately, the short beautiful call counter() disappeared, instead of it now counter.getNext() . But he was so short and comfortable ... So let's get him back:

01 function makeCounter() {
02 var currentCount = 0;
03
04 // возвращаемся к функции
05 function counter() {
06 return ++currentCount;
07 }
08
09 // ...и добавляем ей методы!
10 counter.set = function (value) {
11 currentCount = value;
12 };
13
14 counter.reset = function () {
15 currentCount = 0;
16 };
17
18 return counter;
19 }
20
21 var counter = makeCounter();
22
23 alert( counter() ); // 1
24 alert( counter() ); // 2
25
26 counter.reset();
27 alert( counter() ); // 1

Beautiful, isn't it? The fact that an object is a function does not at all interfere with adding to it any number of methods.

Design Reception "Module"

Receiving programming "module" has a huge number of variations.

Its goal is to declare functions so that unnecessary details of their implementations are hidden. Including: temporary variables, constants, auxiliary mini-functions, etc.

Making the code in the module includes the following steps:

  1. A wrapper function is created that is executed “in place”:
    ( function () {
    ...
    })();
  2. Inside this function, local variables and functions are written that are not needed by the user of the module, but are needed by the module itself:

    1 ( function () {
    2 var count = 0;
    3
    4 function helper() { ... }
    5 })();

    They will only be accessible from within.

  3. Those functions that are needed by the user are “exported” to the external scope.

    If the function is one, this can be done by explicitly returning:

    1 var func = ( function () {
    2 var count = 0;
    3 function helper() { ... }
    4
    5 return function () { ... }
    6 })();
    ... If there are many functions, you can assign them directly to the window :
    1 ( function () {
    2
    3 function helper() { ... }
    4
    5 window.createMenu = function () { ... };
    6 window.createDialog = function () { ... };
    7
    8 })();

    Or, better yet, return the object with them:

    01 var MyLibrary = ( function () {
    02
    03 function helper() { ... }
    04
    05 return {
    06 createMenu: function () { ... },
    07 createDialog: function () { ... }
    08 };
    09
    10 })();
    11
    12 // использование
    13 MyLibrary.createMenu();

All functions of the module will have access to other variables and internal functions through the closure. But from the outside, a programmer using a module can directly access only those that are exported.

This will hide the internal aspects of the implementation that only the developer of the module needs.

You can think of many other variations of this approach. In the end, the “module” is just a wrapper function for hiding variables.

Understanding Closures

Importance: 4

Write the sum function, which works like this: sum(a)(b) = a+b .

Yes, exactly, through double brackets (this is not a typo). For example:

sum(1)(2) = 3
sum(5)(-1) = 4

Decision

For the second brackets in the call to work, the first must return a function.

This function should know about a and be able to add a to b . Like this:

01 function sum(a) {
02
03 return function (b) {
04 return a + b; // возьмет a из внешнего LexicalEnvironment
05 };
06
07 }
08
09 alert( sum(1)(2) );
10 alert( sum(5)(-1) );

[Open task in new window]

Importance: 5

  1. Create a filter(arr, func) function that takes an array of arr and returns a new one that includes only those arr elements for which func returns true .
  2. Create a set of “ready-made filters”: inBetween(a,b) - “between a, b”, inArray([...]) - “in an array [...] ”.
    Usage should be:
    • filter(arr, inBetween(3,6)) - selects only numbers from 3 to 6,
    • filter(arr, inArray([1,2,3])) - selects only elements that match one of the array values.

An example of how this should work:

1 /* .. ваш код для filter, inBetween, inArray */
2 var arr = [1, 2, 3, 4, 5, 6, 7];
3
4 alert( filter(arr, function (a) { return a % 2 == 0 }) ); // 2,4,6
5
6 alert( filter(arr, inBetween(3,6)) ); // 3,4,5,6
7
8 alert( filter(arr, inArray([1,2,10])) ); // 1,2

Decision
Filter function

01 function filter(arr, func) {
02 var result = [];
03
04 for ( var i=0; i
05 var val = arr[i];
06 if (func(val)) {
07 result.push(val);
08 }
09 }
10
11 return result;
12 }
13
14 var arr = [1, 2, 3, 4, 5, 6, 7];
15
16 alert( filter(arr, function (a) { return a % 2 == 0; }) ); // 2, 4, 6

InBetween filter

01 function filter(arr, func) {
02 var result = [];
03
04 for ( var i=0; i
05 var val = arr[i];
06 if (func(val)) {
07 result.push(val);
08 }
09 }
10
11 return result;
12 }
13
14 function inBetween(a, b) {
15 return function (x) {
16 return x >=a && x <= b;
17 };
18 }
19
20 var arr = [1, 2, 3, 4, 5, 6, 7];
21 alert( filter(arr, inBetween(3,6)) ); // 3,4,5,6

InArray filter

01 function filter(arr, func) {
02 var result = [];
03
04 for ( var i=0; i
05 var val = arr[i];
06 if (func(val)) {
07 result.push(val);
08 }
09 }
10
11 return result;
12 }
13
14 function inArray(arr) {
15 return function (x) {
16 return arr.indexOf(x) != -1;
17 };
18 }
19
20 var arr = [1, 2, 3, 4, 5, 6, 7];
21 alert( filter(arr, inArray([1,2,10])) ); // 1,2

[Open task in new window]

Importance: 5

In some programming languages, there is an object "string buffer" that accumulates values ​​within itself. Its functionality consists of two possibilities:

  1. Add value to buffer.
  2. Get current content.

The task is to implement a string buffer on functions in JavaScript, with the following syntax:

  • Creating an object: var buffer = makeBuffer(); .
  • The makeBuffer call should return a buffer function that, when calling buffer(value) adds a value to some internal storage, and when called without buffer() arguments, returns it.

Here is an example of work:

01 function makeBuffer() { /* ваш код */ }
02
03 var buffer = makeBuffer();
04
05 // добавить значения к буферу
06 buffer( 'Замыкания' );
07 buffer( ' Использовать' );
08 buffer( ' Нужно!' );
09
10 // получить текущее значение
11 alert( buffer() ); // Замыкания Использовать Нужно!

The buffer must convert all data to string type:

1 var buffer = makeBuffer();
2 buffer(0); buffer(1); buffer(0);
3
4 alert( buffer() ); // '010'

The solution should not use global variables.

Decision

The current value of the text is conveniently stored in the closure, in the local variable makeBuffer :

01 function makeBuffer() {
02 var text = '' ;
03
04 return function (piece) {
05 if (piece === undefined ) { // вызов без аргументов
06 return text;
07 }
08 text += piece;
09 };
10 };
11
12 var buffer = makeBuffer();
13
14 // добавить значения к буферу
15 buffer( 'Замыкания' );
16 buffer( ' Использовать' );
17 buffer( ' Нужно!' );
18 alert( buffer() ); // 'Замыкания Использовать Нужно!'
19
20 var buffer2 = makeBuffer();
21 buffer2(0); buffer2(1); buffer2(0);
22
23 alert( buffer2() ); // '010'

The initial value of text = '' is an empty string. Therefore, the operation text += piece adds piece to the string, automatically converting it to string type, as required in the condition.

[Open task in new window]

Importance: 5

Add a buffer from the Problem function solution — a string buffer buffer.clear() method that will clear the current buffer contents:

01 function makeBuffer() {
02 ...ваш код...
03 }
04
05 var buffer = makeBuffer();
06
07 buffer( "Тест" );
08 buffer( " тебя не съест " );
09 alert( buffer() ); // Тест тебя не съест
10
11 buffer.clear();
12
13 alert( buffer() ); // ""

Decision

01 function makeBuffer() {
02 var text = '' ;
03
04 function buffer(piece) {
05 if (piece === undefined ) { // вызов без аргументов
06 return text;
07 }
08 text += piece;
09 };
10
11 buffer.clear = function () {
12 text = "" ;
13 }
14
15 return buffer;
16 };
17
18 var buffer = makeBuffer();
19
20 buffer( "Тест" );
21 buffer( " тебя не съест " );
22 alert( buffer() ); // Тест тебя не съест
23
24 buffer.clear();
25
26 alert( buffer() ); // ""

[Open task in new window]

Importance: 5

  • Will access to the name variable via the closure in the example below work?
  • Will the name variable be deleted from memory when you delete donkey.sayHi ? If not, can I somehow contact the name after removing donkey.sayHi ?
  • And if you assign donkey.sayHi = donkey.yell = null - will the name remain in memory?

01 var makeDonkey = function () {
02 var name = "Ослик Иа" ;
03
04 return {
05 sayHi: function () {
06 alert(name);
07 },
08 yell: function () {
09 alert( 'И-а, и-а!' );
10 }
11 };
12 }
13
14 var donkey = makeDonkey();
15 donkey.sayHi();

Decision
  1. Yes, it will work, thanks to the [[Scope]] link to an external variable object, which will be assigned to the sayHi and yell when creating an object.
  2. No, the name will not be removed from memory, because despite the fact that sayHi is no longer present, there is another function yell , which also refers to an external variable object. This object is stored entirely, along with all properties.

    At the same time, since the sayHi function has sayHi removed from the object and there are no references to it, there is no one else to access the name variable. It turned out that she was “stuck” in the memory, although, in fact, nobody needs it.

  3. If both sayHi and yell deleted, then, since there are no more internal functions left, the variable object will be deleted along with the name .
[Open task in new window]

Importance: 5

The following code creates an array of shooter-function shooters . According to the plan, each shooter must display his number:

01 function makeArmy() {
02
03 var shooters = [];
04
05 for ( var i=0; i<10; i++) {
06 var shooter = function () { // функция-стрелок
07 alert(i); // выводит свой номер
08 };
09 shooters.push(shooter);
10 }
11
12 return shooters;
13 }
14
15 var army = makeArmy();
16
17 army[0](); // стрелок выводит 10, а должен 0
18 army[5](); // стрелок выводит 10...
19 // .. все стрелки выводят 10 вместо 0,1,2...9

Why do all shooters display the same thing? Correct the code so that the arrows work as intended. Offer several fixes.

Decision
What happens in this code

The makeArmy function does the following:

  1. Creates an empty shooter array:
    var shooters = [];
  2. In the loop, it fills the array with elements via shooter.push .
    In addition, each element of the array is a function, so that after the loop the array will be like this:
    01 shooters = [
    02 function () { alert(i); }, () { alert(i); },
    03 function () { alert(i); }, () { alert(i); },
    04 function () { alert(i); }, () { alert(i); },
    05 function () { alert(i); }, () { alert(i); },
    06 function () { alert(i); }, () { alert(i); },
    07 function () { alert(i); }, () { alert(i); },
    08 function () { alert(i); }, () { alert(i); },
    09 function () { alert(i); }, () { alert(i); },
    10 function () { alert(i); }, () { alert(i); },
    11 function () { alert(i); } () { alert(i); }
    12 ];
    This array is returned from the function.
  3. A call to the army[5]() is the receipt of an array element (it will be a function) at position 5 , and right there it is launched.
Why a mistake

First, let's see why all the arrows display the same value.

In the shooter functions, the variable i missing. When such a function is called, then i takes it from an external LexicalEnvironment ...

What will be the value of i ?

By the time the army[0]() call, the makeArmy function makeArmy already completed its work. The cycle is completed, the last value was i=10 .

As a result, all functions of the shooter receive the same thing, the last, i=10 value.

Try to fix the problem yourself.

Correction (3 options)

There are several ways to remedy the situation.

  1. The first way to fix the code is to bind the value directly to the arrow function:

    01 function makeArmy() {
    02
    03 var shooters = [];
    04
    05 for ( var i=0; i<10; i++) {
    06
    07 var shooter = function me() {
    08 alert( me.i );
    09 };
    10 shooter.i = i;
    11
    12 shooters.push(shooter);
    13 }
    14
    15 return shooters;
    16 }
    17
    18 var army = makeArmy();
    19
    20 army[0](); // 0
    21 army[1](); // 1

    In this case, each function stores its own number.

    By the way, pay attention to the use of Named Function Expression, here in this section:

    1 ...
    2 var shooter = function me() {
    3 alert( me.i );
    4 };
    5 ...

    If you remove the name of me and leave the appeal through the shooter , then it will not work:

    1 for ( var i=0; i<10; i++) {
    2 var shooter = function () {
    3 alert(shooter.i); // вывести свой номер (не работает!)
    4 // потому что откуда функция возьмёт переменную shooter?
    5 // ..правильно, из внешнего объекта, а там она одна на всех
    6 };
    7 shooter.i = i;
    8 shooters.push(shooter);
    9 }

    Calling alert(shooter.i) when calling will look for the variable shooter , and this variable changes the value during the cycle, and by the time of the call it is equal to the last function created in the cycle.

    If you use the Named Function Expression, then the name is strictly bound to a specific function, and therefore in the code above me.i returns the correct i .

  2. Another more advanced solution is to use an additional function to “catch” the current value of i :

    01 function makeArmy() {
    02
    03 var shooters = [];
    04
    05 for ( var i=0; i<10; i++) {
    06
    07 var shooter = ( function (x) {
    08
    09 return function () {
    10 alert( x );
    11 };
    12
    13 })(i);
    14
    15 shooters.push(shooter);
    16 }
    17
    18 return shooters;
    19 }
    20
    21 var army = makeArmy();
    22
    23 army[0](); // 0
    24 army[1](); // 1

    Let's look at the selected fragment more closely in order to understand what is happening:

    1 var shooter = ( function (x) {
    2 return function () {
    3 alert( x );
    4 };
    5 })(i);

    The shooter function is created as the result of a call to the intermediate functional expression function(x) , which is declared - and immediately executed, getting x = i .

    Since function(x) ends right there, the value of x no longer changes. It will be used in the return function-arrow.

    For beauty, you can change the name of the variable x to i , the essence of what is happening will not change:

    1 var shooter = ( function (i) {
    2 return function () {
    3 alert( i );
    4 };
    5 })(i);

    By the way, pay attention - brackets around function(i) not needed , it is possible and so:

    1 var shooter = function (i) { // без скобок вокруг function(i)
    2 return function () {
    3 alert( i );
    4 };
    5 }(i);

    The brackets are added to the code for better readability so that the person who views it does not think that var shooter = function , but realized that this is a call "in place" and its result is assigned.

  3. Another fun way is to wrap the entire cycle in a temporary function :

    01 function makeArmy() {
    02
    03 var shooters = [];
    04
    05 for ( var i=0; i<10; i++) ( function (i) {
    06
    07 var shooter = function () {
    08 alert( i );
    09 };
    10
    11 shooters.push(shooter);
    12
    13 })(i);
    14
    15 return shooters;
    16 }
    17
    18 var army = makeArmy();
    19
    20 army[0](); // 0
    21 army[1](); // 1

    The call (function(i) { ... }) wrapped in parentheses, so that the interpreter understands that it is a Function Expression .

    The advantage of this method is more readability. In fact, we do not change the creation of the shooter , but simply wrap the iteration into a function.

[Open task
created: 2014-10-07
updated: 2022-02-17
132546



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