📜 ⬆️ ⬇️

Features or “Why I love JavaScript”: Closures, Prototyping and Context

Having emerged as a scripting language to help web developers, with the further development of JavaScript has become a powerful client-side development tool that provides the convenience and interactivity of a page right in the user's browser.

Due to the specificity of the environment and the objectives, JavaScript is different from ordinary programming languages, and has many features, without understanding which, it is quite difficult to write a good cross-browser code.

I think that most programmers who have written JavaScript code for more than a couple of days have come across these features. The purpose of this topic is not to discover something new, but to try to describe these features “on fingers” and “disadvantages” to make “advantages” .
')
In this topic will be considered:

  1. Closures
  2. Prototyping
  3. Execution context


Foreword

I, as an author, of course want to describe all the possibilities that JavaScript is rich in. However, if I just try to do this, the article will stretch across a huge number of pages, and many novice developers simply cannot remember all the information. Therefore, the examples cited may seem too simple to someone, and the topics are not fully revealed. But, I hope, the article will be able to interest those who are not very familiar with these features, and those who are already familiar - to help understand that in actual fact everything is elementary.

Closures, or "These Unusual Scopes"


“Arch-useful thing!” - these two words can be expressed
my attitude to closures and their implementation in javascript.


The essence of closures is simple: inside the function, you can use all variables that are available in the place where the function was declared .

Although the idea of ​​closures is simple, in practice there are often many incomprehensible moments of behavior in a particular case. So first, let's remember the basics of declaring a variable, namely, “ variables in JavaScript are declared using the var keyword ”:

var title = "Hello World"; alert(title); 


When you run the code, it displays the text " Hello World ", as expected. The essence of what is happening is simple - a global variable is created with the value " Hello World ", which is shown to the user with the help of alert . In this example, even if we omit the var keyword, the code will still work correctly due to the global context. But more about that later.

Now let's try to declare the same variable, but inside the function:

 function example (){ var title = "Hello World"; } alert(title); 


As a result of the launch of the code, an error was generated " 'title' is undefined " - "the variable 'title' was not declared ". This is due to the mechanism of the local scope of variables: all variables declared inside a function are local and are visible only inside this function . Or simpler: if we declare a variable inside a function, we will not have access to this variable outside this function.

In order to display the inscription " Hello World ", you need to call an alert inside the called function:

 function example(){ var title = "Hello World"; alert(title); } example(); 


Or return the value from the function:

 function example(){ var title = "Hello World"; return title; } alert(example()); 


I think that all these examples are obvious - this behavior is implemented in almost all programming languages. So what is the peculiarity of closures in JavaScript, which distinguishes implementation from other languages ​​so much?

The key difference is that in JavaScript the functions can be declared inside other functions, and the functions themselves in JavaScript are objects! Due to this, you can perform the same actions with them as with ordinary objects - check for existence, assign variables, add properties, call methods and return a function object as a result of performing another function!

Since a function is an object, this means that the mechanism of the scope of variables also extends to functions: a function declared inside another function is visible only where it was declared

 function A(){ function B(){ alert("Hello World"); } } B(); 


As in the example with a variable, when starting the code, an error is generated that the variable B was not declared. If we place the call to function B immediately after the declaration inside function A , and call the function A itself, we get the cherished message " Hello World "

 function A(){ function B(){ alert("Hello World"); } B(); } A(); 


Now let's get down to a description of what most beginners stumble upon learning to stumble upon — the definition of where the variables come from. As mentioned above, variables need to be declared using the var keyword:

 var title = 'external'; function example(){ var title = 'internal'; alert(title); } example(); alert(title); 


In this example, the title variable was declared twice — the first time globally, and the second time — inside the function. Due to the fact that inside the function example , the title variable was declared using the var keyword, it becomes local and is not related to the title variable declared before the function. As a result of code execution, " internal " (internal variable) and then " external " (global variable) are displayed first.

If we remove the var keyword from the var line title = 'internal' , then running the code will result in the message " internal " twice. This is due to the fact that when we called the function we declared not the local variable title , but overwritten the value of the global variable!

Thus, you can see that using the var keyword makes the variable local, ensuring there are no conflicts with external variables ( for example, in PHP, all variables inside the function are local by default; and to access the global variable, you must declare it global global keyword ).

Hidden text
It should be remembered that all parameters of the function are automatically local variables:

 var title = "external title"; function example(title){ title = "changing external title"; alert(title); } example('abc'); alert(title); 

When you run the code, messages " changing external title " are generated, and then " external title ", indicating that the external variable title was not changed inside the function, although we did not declare it with var.

The process of initialization of local variables takes place before the execution of the code - for this, the interpreter runs through the function code and initializes ( without assigning values! ) All the local variables found:

 var title = "external title"; function example(){ title = "changing external title"; alert(title); var title = "internal title"; } example(); alert(title); 


As in the previous example, when you run the code, messages " changing external title " are generated, and then " external title ", indicating that the external variable title has not been changed inside the function.

If you comment out the line title = "changing external title"; , then at the first generated message it will become " undefined " - the local variable title has already been initialized (exists), but the value (at the time the alert was called) was not assigned.

Code:
 function example(){ alert(title); var title = "internal title"; } 

equivalent to the following:
 function example(){ var title; alert(title); title = "internal title"; } 


Thus, it becomes clear that wherever a variable is declared inside a function, at the time of the call, the variable will already be initialized. It also means that the repeated declaration does not make sense - the interpreter will simply ignore the declaration of the variable a second time.



So, how do you determine which variable is used in a function?


If, when declaring a function, the variable was not declared locally using the var keyword, the variable will be looked up in the parent function. If it is not found there, the search will proceed further along the chain of parent functions until the interpreter finds the declaration of a variable or reaches the global scope.

If the variable declaration is not found either up the chain of declarations of the function, or in the global scope, there are two options for development:
  1. When trying to use (get the value) a variable, an error is generated that the variable was not declared
  2. When you try to assign a value to a variable, the variable will be created in the global scope and assigned a value.


 function A(){ title = 'internal'; return function B(){ alert(title); } } var B = A(); B(); alert(title); 


Having executed the code, we will receive both times " internal ". Assigning the value of the variable to the variable inside the function A creates a global variable that can be used outside the function. It should be borne in mind that assigning a value to a variable (and hence creating a global variable) occurs at the stage of calling function A , so an attempt to call alert (title) before calling function A generates an error.

Hidden text
In fact, the mechanism of global variables is a bit more complicated than described here: if you try to assign the value of an uninitialized variable, you will create not a global variable, but a property of the window object ( which acts as a container for all global variables ).

The main difference from global variables ( declared with var ) is that variables cannot be deleted using the delete operator; otherwise, working with the properties of the window object is identical to working with global variables.

Read more here .


Now back to the topic of closures in JavaScript.

Compared to most other programming languages, JavaScript provides a more flexible approach to working with variable scope. And thanks to the possibility of combining local scope and dynamic function declaration within other functions, a locking mechanism arises.

As you know, all local variables are created anew each time a new function is called. For example, we have a function A , inside which the variable title is declared:

 function A(){ var title = 'internal'; alert(title); } A(); 


After the function A is executed, the title variable will cease to exist and it can not be accessed in any way. Attempting to access a variable in any way will cause an error that the variable has not been declared.

Now inside function A we add the declaration of the function, which displays the value of the variable title , and also the function that changes this value to the passed one, and return these functions:

 function getTitle (){ var title = "default title"; var showTitle = function(){ alert(title); }; var setTitle = function(newTitle){ title = newTitle; }; return { "showTitle": showTitle, "setTitle": setTitle }; } var t = getTitle(); t.showTitle(); t.setTitle("Hello World"); t.showTitle(); 


Before starting this example, let us try to consider the logical behavior of the variable title : when the getTitle function is started, the variable is created, and after the end of the call, it is destroyed. However, when you call the getTitle function, an object is returned with two dynamically-declared functions showTitle and setTitle , which use this variable. What happens if you call these functions?

And now, by running the example, you can see that the default title is displayed first, and then the Hello World . Thus, the title variable continues to exist, although the getTitle function has long been completed. At the same time, there is no other access to this variable, except from the above-mentioned showTitle / setTitle functions . This is a simple closure example - the title variable “closed” and became visible only for those functions that had access to it during its declaration.

If you run the getTitle function again, you can see that the title variable, like the showTitle / setTitle functions, is re-created each time, and has nothing to do with previous launches:

 var t1 = getTitle(); t1.setTitle("Hello World 1"); var t2 = getTitle(); t2.setTitle("Hello World 2"); t1.showTitle(); t2.showTitle(); 


Running the code (do not forget to add the above function code getTitle ), two messages will be generated: " Hello World 1 " and " Hello World 2 " ( this behavior is used to emulate private variables ).

Leaving the theory and the simplest examples, and try to understand what benefits can be derived from the closures in practice:


The first is the opportunity not to litter the global scope.

The problem of conflicts in the global scope is obvious. A simple example: if there are several javascript files on the page that declare the showTitle function, then when you call showTitle, the function declared last will be executed. The same applies to declared variables.

To avoid such a situation, you must either call each function / variable with a unique name, or use closures. It is inconvenient to pervert and call each function and variable by a unique name, and still does not guarantee complete uniqueness. On the other hand, the closure mechanism provides complete freedom in the naming of variables and functions. To do this, it is sufficient to wrap the whole code in an anonymous function, which is executed immediately after the declaration:

 (function(){ /*  ,     */ })(); 


As a result, all declared variables and functions will not be available in the global scope, which means there will be no conflicts.

What to do if it is still necessary that one or two functions are available globally? And everything is pretty simple. The most global object that provides the global scope is the window object. Due to the fact that a function is an object, it can be assigned to the window property so that it becomes global. An example of declaring a global function from a closed scope:

 (function(){ var title = "Hello World"; function showTitle(){ alert(title); } window.showSimpleTitle = showTitle; })(); showSimpleTitle(); 


As a result of the code execution, the message “ Hello World ” is generated - the local function showTitle is available globally under the name showSimpleTitle , while using the “closed” title variable, which is inaccessible outside our anonymous function.

Since we have wrapped everything in an anonymous function, which is immediately executed; this function can be passed parameters, which inside this function will be available under local naming. Example with jQuery:

 (function(){ $('.hidden').hide(); })(); 


Will cause an error if the global variable $ is not jQuery. And this happens if, in addition to jQuery, another library is used that uses the $ function, for example, Prototype.JS. Head-on:

 (function(){ var $ = jQuery; $('.hidden').hide(); })(); 


Will work, and will work correctly. But not very beautiful. There is a more beautiful solution - to declare a local variable $ as a function argument, passing the jQuery object there:

 (function($){ /* ,  $ */ })(jQuery); 


If we recall that all the arguments of the function are local variables by default, it becomes clear that now inside our anonymous function, $ is in no way associated with the global $ object, being a reference to the jQuery object. In order to make reception with anonymous functions more understandable, you can make the anonymous function non-anonymous - they declared the function and immediately started it:

 function __run($){ /* code */ } __run(jQuery); 


Well, if you still need to call the global function $, you can use window. $ .

The second is the dynamic declaration of functions using closures.

When using the event model, often there are situations when you need to hang the same event, but on different elements. For example, we have 10 div-elements, on click on which we need to call alert (N) , where N is any unique element number.

The simplest solution using a closure:

 for(var counter=1; counter <=10; counter++){ $('<div>').css({ "border": "solid 1px blue", "height": "50px", "margin": "10px", "text-align": "center", "width": "100px" }).html('<h1>'+ counter +'</h1>') .appendTo('body') .click(function(){ alert(counter); }); } 


However, the execution of this code leads to an "unexpected" result - all the clicks output the same number - 11. Guess why?

The answer is simple: the value of the counter variable is taken at the moment of clicking on the element. And since by that time the value of the variable was 11 (condition for exiting the cycle), then the number 11 is output for all elements, respectively.

The correct solution is to dynamically generate a click processing function separately for each item:

 for(var counter=1; counter <=10; counter ++){ $('<div>').css({ "border": "solid 1px blue", "height": "50px", "margin": "10px", "text-align": "center", "width": "100px" }).html('<h1>'+ counter +'</h1>') .appendTo('body') .click((function(iCounter){ return function(){ alert(iCounter); } })(counter)); } 


In this approach, we use an anonymous function that takes the value of counter as a parameter and returns a dynamic function. Inside an anonymous function, the local variable iCounter contains the current value of counter at the time of the function call. And as with each call of any function all local variables are declared again (a new closure is created), then when we call our anonymous function, each time a new dynamic function will return with an already “closed” number.

To put it simply, by running the function the second (third, fourth ...) times, all local variables will be created in memory again and will have nothing to do with the variables created during the previous function call.

Complicated? I think that the first time - yes. But you do not need to have a bunch of global variables, and do checks in the function "from where did I get called ...". And using jQuery.each, which by default calls the passed function, the code becomes even simpler and more readable:

 $('div.handle-click').each(function(counter){ $(this).click(function(){ alert(counter); }); }); 


Thanks to closures, you can write beautiful, concise and understandable code, naming variables and functions as you would like; and be sure that this code will not conflict with other plugin scripts.

Hidden text
The mechanism of variable closures is implemented using the internal property of the [[Scope]] function - this object is initiated during function declaration and contains references to variables declared in the parent function. Due to this, during the operation (call), all the “parent” variables are accessible. Actually, the object [[Scope]] is the key to the mechanism of closures.

More information about the mechanism of closures can be found in the articles available on the links at the bottom of the topic.


Prototyping or "I want to make a class object"


About prototyping in JavaScript many good articles are written. Therefore, I will try not to repeat what has already been written, but simply describe the basis of the prototype mechanism.

In JavaScript, there is the concept of an object, but there is no concept of a class. Due to weak typing, All JavaScript is based on objects, so almost all the data in JavaScript is objects, or can be used as objects. And as an alternative to classes, there is the possibility of prototyping - assign a prototype to an object with properties and methods “by default”.

Working with objects in JavaScript is very simple - you just need to declare an object and assign properties and methods to it:

 var dog = { "name": "Rocky", "age": "5", "talk": function(){ alert('Name: ' + this.name + ', Age: ' + this.age); } }; 


If we have many objects, it will be more convenient to make a separate function that returns an object:

 function getDog(name, age){ return { "name": name, "age": age, "talk": function(){ alert('Name: ' + this.name + ', Age: ' + this.age); } }; } var rocky = getDog('Rocky', 5); var jerry = getDog('Jerry', 3); 


The same can be done using prototypes:

 function Dog(name, age){ this['name'] = name; this.age = age; } Dog.prototype = { "talk": function(){ alert('Name: ' + this.name + ', Age: ' + this.age); } }; var rocky = new Dog('Rocky', 5); var jerry = new Dog('Jerry', 3); 


As mentioned above, the prototype is a simple object that contains properties and methods “by default”. Those. if any object tries to get a property or call a function that the object does not have, then the JavaScript interpreter, before generating an error, will try to find this property / function in the prototype object and, if found, use the property / function from prototype.

Hidden text
I think it should be emphasized that the properties from the prototype are used only for reading / execution, since they are accessed only when trying to get a value / execute a function, in case the original property is not found near the object itself.

If you try to assign an object to a property (existing in the prototype, but not in the object), then the property will be created for the object, and not changed in the prototype.


JavaScript is very flexible language. Therefore, by and large, everything that can be done with prototypes can be done without prototypes. However, the use of prototypes gives more flexibility with modification and inheritance, as well as syntax closer to OOP. For those who are interested, at the end of the article there are links to articles with a more detailed description of the mechanism and possibilities for implementing your own classes.

And a small illustrative example of expanding the capabilities of existing objects using prototypes

You must get the name of the day of the week from any date, but the built-in Date object contains only the getDay method, which returns a numeric representation of the day of the week from 0 to 6: from Sunday to Saturday.

You can do this:

 function getDayName(date){ var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; return days[date.getDay()]; } var today = new Date(); alert(getDayName(today)); 


Or use prototyping and extend the built-in date object:

 Date.prototype.getDayName = function(){ var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; return days[this.getDay()]; } var today = new Date(); alert(today.getDayName()); 


I think that the second method is more elegant and does not clog the scope of the “extra” function. On the other hand, it is believed that the extension of embedded objects is a bad tone if several people work on the code. So be careful with this.

Execution Context or "This Mysterious This"


Turning to JavaScript from other programming languages ​​where OOP is used, it is rather difficult to understand what this object means in JavaScript. If you try to explain simply, this is a reference to the object for which the function is called. For example:

 var exampleObject = { "title": "Example Title", "showTitle": function(){ alert(this.title); } }; exampleObject.showTitle(); 


From the example, it can be seen that when the exampleObject.showTitle () is called, the function is called as a method of the object, and inside the function this refers to the exampleObject object that called the function. By themselves, the functions are not tied to the object and exist separately. Context binding occurs directly during a function call:

 function showTitle(){ alert(this.title); } var objectA = { "title": "Title A", "showTitle": showTitle }; var objectB = { "title": "Title B", "showTitle": showTitle }; objectA.showTitle(); objectB.showTitle(); 


This example clearly shows that when you call objectA.showTitle () , this refers to objectA , and when you call objectB.showTitle () , it calls objectB. The showTitle function itself exists separately and is simply assigned to objects as a property at the time of creation.

If, when calling a function, it (the function) does not refer to any object, then this inside the function will refer to the global window object . Those.if you simply call showTitle () , an error will be generated that the title variable is not declared; however, if you declare the global variable title , the function will output the value of this variable:

 var title = "Global Title"; function showTitle(){ alert(this.title); } showTitle(); 


Hidden text
Strict Mode : , this window , this .


To demonstrate that the context of a function is determined during the call, I will give an example where the function initially exists only as an object method:

 var title = "Global Title"; var exampleObject = { "title": "Example Title", "showTitle": function(){ alert(this.title); } }; var showTitle = exampleObject.showTitle; //      showTitle(); //         


As a result of the execution, the message " Global Title " will appear , meaning that during the call of the function this points to the global window object , and not to the exampleObject object . This is due to the fact that in the var showTitle = exampleObject.showTitle line we get the reference to the function only, and when calling showTitle () there is no reference to the original exampleObject object , which causes this to refer to the window object .

Simplifying: if the function is called as a property of an object, then this will refer to this object. If there is no caller, thiswill refer to the global window object .

Example of a common error:

 var exampleObject = { "title": "Example Title", "showTitle": function(){ alert(this.title); } }; jQuery('#exampleDiv').click(exampleObject.showTitle); 


When clicking on a DIV with the id " exampleDiv " instead of the expected " Example Title ", an empty string or the value of the div attribute of the "title" is displayed. This is due to the fact that we give a function to a click event, but we don’t give an object; and, as a result, the function is launched without being tied to the original exampleObject object ( for convenience, jQuery runs the handler functions in the context of the element itself, which leads to a similar result ). To run a function bound to an object, call the function with the object reference:

 jQuery('#exampleDiv').click(function(){ exampleObject.showTitle(); }); 


To avoid such a "clumsy" declaration, using JavaScript itself, you can bind the function to the context using bind :

 jQuery('#exampleDiv').click(exampleObject.showTitle.bind(exampleObject)); 


However, everyone's favorite IE up to version 9 does not support this feature. Therefore, most JS libraries independently implement this feature in one way or another. For example, in jQuery this is a proxy:

 jQuery('#exampleDiv').click(jQuery.proxy(exampleObject.showTitle, exampleObject)); //  : jQuery('#exampleDiv').click(jQuery.proxy(exampleObject, "showTitle")); 


The essence of the approach is quite simple - when jQuery.proxy is called, an anonymous function is returned, which, using closures, calls the original function in the context of the passed object.

In fact, JavaScript can run any function in any context. And do not even have to assign a function to the object. There are two ways to do this in JavaScript: apply and call :

 function showTitle(){ alert(this.title); } var objectA = { "title": "Title A", }; var objectB = { "title": "Title B", }; showTitle.apply(objectA); showTitle.call(objectA); 


Without using the parameters of the function, both work in the same way - the apply and call functions differ only in the way parameters are passed when calling:

 function example(A, B, C){ /* code */ } example.apply(context, [A, B, C]); example.call(context, A, B, C); 


More detailed information on the topics covered can be found here:

Source: https://habr.com/ru/post/178133/


All Articles