📜 ⬆️ ⬇️

Everything you wanted to know about scopes in JavaScript (but were afraid to ask)

JS has several concepts related to scope that are not always clear to novice developers (and sometimes even experienced ones). This article is dedicated to those who seek to plunge into the abyss of JS scope, hearing such words as scope, closure, “this”, namespace, function scope, global variables, lexical scope, private and public domains ... I hope reading the material you can answer the following questions:

- what is the scope?
- what is a global / local OS?
- what is the namespace and how does it differ from the OB?
- What does this keyword mean, and how does it relate to agents?
- What is a functional and lexical agents?
- what is a closure?
- How can I understand and create all this?

What is the scope?



In JS, scope is the current context in the code. OBs can be defined locally or globally. The key to writing bulletproof code is to understand OB. Let's understand where variables and functions are available, how to change the context in the code and write faster and more supported code (which is easier to debug). It is easy to deal with agents - we ask ourselves which of them are we in A or B?
')

What is a global / local OS?



Without writing a single line of code, we are already in the global OB. If we immediately define a variable, it is in the global OB.

//   var name = 'Todd'; 


Global OB is your best friend and worst nightmare. While learning how to work with different agents, you will not encounter problems with a global agent, unless you see name intersections. You can often hear the “global OB — this is bad,” but it is rarely possible to get an explanation of why. GOV is not bad, you need to use it when creating modules and APIs that will be available from different agents, you just need to use it for good and for good.

We all used jQuery. As soon as we write

 jQuery('.myClass'); 


we access jQuery in a global OB, and we can call this access a namespace. Sometimes the term “namespace” is used instead of the term OB, however, they usually denote the OB of the level itself. In our case, jQuery is in a global OB, and is our namespace. The jQuery namespace is defined in a global OB, which works as a PI for the jQuery library, while all its contents are inherited from this PI.

What is a local OB?



Local OB call any OB defined after global. Usually we have one GOV, and each defined function carries a local OB. Each function defined inside another function has its own local OB associated with the OB of the external function.

If I define functions and define variables inside them, they belong to a local OB. Example:

 //  A:  var myFunction = function () { //  B:  }; 


All variables from BOV are not visible in GOV. They cannot be accessed from the outside directly. Example:

 var myFunction = function () { var name = 'Todd'; console.log(name); // Todd }; // ReferenceError: name is not defined console.log(name); 


The variable "name" refers to the local OB, it is not visible from the outside and therefore is not defined.

Functional OB.



All local OBs are created only in functional OBs, they are not created by for or while cycles or by if or switch directives. New feature - new scope. Example:

 //  A var myFunction = function () { //  B var myOtherFunction = function () { //  C }; }; 


So simply you can create a new OB and local variables, functions and objects.

Lexical OV



If one function is defined inside another, the internal one has access to the external OB. This is called “lexical OB”, or “closure”, or else “static OB”.

 var myFunction = function () { var name = 'Todd'; var myOtherFunction = function () { console.log('My name is ' + name); }; console.log(name); myOtherFunction(); //   }; // : // `Todd` // `My name is Todd` 


It is quite simple to work with the lexical OB — everything that is defined in the parent's OB is available in the child's OB. For example:

 var name = 'Todd'; var scope1 = function () { // name   var scope2 = function () { // name   var scope3 = function () { // name   ! }; }; }; 


In the opposite direction it does not work:

 // name = undefined var scope1 = function () { // name = undefined var scope2 = function () { // name = undefined var scope3 = function () { var name = 'Todd'; //   }; }; }; 


You can always return a reference to “name”, but not the variable itself.

OB sequences



OB sequences determine the OB of any selected function. Each defined function has its own OB, and each function defined inside the other has its own OB associated with the external OB — this is the sequence or chain. Position in the code determines the OB. Defining the value of a variable, JS goes from the deepest nested OB to the outside until it finds the desired function, object, or variable.

Closures



They live in close union with lexical agents. A good use case is returning a reference to a function. We can return to the outside various links that make it possible to access what has been defined inside.

 var sayHello = function (name) { var text = 'Hello, ' + name; return function () { console.log(text); }; }; 


To display text, it is not enough just to call the function sayHello:

 sayHello('Todd'); //  


The function returns the function, so it must first be assigned and then called:

 var helloTodd = sayHello('Todd'); helloTodd(); //     'Hello, Todd' 


You can of course cause a closure and directly:

 sayHello('Bob')(); //     


AngularJS uses similar calls in the $ compile methods, where you need to pass a link to the current OB:

 $compile(template)(scope); 


One can guess that, simplified, their code looks like this:

 var $compile = function (template) { //   //    scope return function (scope) { //      `template`   `scope` }; }; 


The function does not have to return anything to be a closure. Any access to variables from outside the current OB creates a closure.

OB and 'this'



Each OB assigns its own value to the variable “this”, depending on how the function is called. We all used the keyword this, but not everyone understands how it works and what the differences are when calling. By default, it refers to the object of the outermost OB, the current window. An example of how different calls change this values:

 var myFunction = function () { console.log(this); // this = , [ Window] }; myFunction(); var myObject = {}; myObject.myMethod = function () { console.log(this); // this =   { myObject } }; var nav = document.querySelector('.nav'); // <nav class="nav"> var toggleNav = function () { console.log(this); // this =  <nav> }; nav.addEventListener('click', toggleNav, false); 


There are also problems with the value of this. In the following example, the value and OB may vary within the same function:
 var nav = document.querySelector('.nav'); // <nav class="nav"> var toggleNav = function () { console.log(this); // <nav> element setTimeout(function () { console.log(this); // [ Window] }, 1000); }; nav.addEventListener('click', toggleNav, false); 


Here we have created a new OB, which is not called from an event handler, which means it refers to the window object. You can, for example, remember the value of this in another variable, so that there is no confusion:

 var nav = document.querySelector('.nav'); // <nav class="nav"> var toggleNav = function () { var that = this; console.log(that); //  <nav> setTimeout(function () { console.log(that); //  <nav> }, 1000); }; nav.addEventListener('click', toggleNav, false); 


Change OBs with .call (), .apply () and .bind ()



Sometimes there is a need to change agents depending on what you need.
In the example:

 var links = document.querySelectorAll('nav li'); for (var i = 0; i < links.length; i++) { console.log(this); // [ Window] } 


The value of this does not apply to enumerated elements, we do not call or change anything. Let's look at how we can change the OB (more precisely, we change the context of the function call).

.call () and .apply ()



The methods .call () and .apply () allow you to transfer the OB to a function:

 var links = document.querySelectorAll('nav li'); for (var i = 0; i < links.length; i++) { (function () { console.log(this); }).call(links[i]); } 


As a result, the values ​​of the enumerated elements are transferred to this. The .call method (scope, arg1, arg2, arg3) takes a comma-separated list of arguments, and the .apply method (scope, [arg1, arg2]) takes an array of arguments.

It is important to remember that the .call () or .apply () methods call functions, so instead of

 myFunction(); //  myFunction 


Let .call () call a function and pass a parameter:

 myFunction.call(scope); 


.bind ()



.bind () does not call a function, but simply binds the values ​​of variables before calling it. As you know, we cannot pass parameters to function references:

 //  nav.addEventListener('click', toggleNav, false); //      nav.addEventListener('click', toggleNav(arg1, arg2), false); 


This can be fixed by creating a new nested function:

 nav.addEventListener('click', function () { toggleNav(arg1, arg2); }, false); 


But here again there is a change in the OV, the creation of an extra function, which will negatively affect the speed. Therefore, we use .bind (), as a result, we can pass arguments in such a way that a function call does not occur:

 nav.addEventListener('click', toggleNav.bind(scope, arg1, arg2), false); 

Private and public agents



In JavaScript, unlike many other languages, there are no concepts of public and private agents, but we can emulate them with closures. To create a private OB, we can wrap our functions in other functions.

 (function () { //    })(); 


Add functionality:

 (function () { var myFunction = function () { //  ,   }; })(); 


But you cannot call this function directly:

 (function () { var myFunction = function () { //  ,   }; })(); myFunction(); // Uncaught ReferenceError: myFunction is not defined 


Here you have a private OB. If you need a public OB, use the following trick. Create a Module namespace that contains everything related to this module:

 //   var Module = (function () { return { myMethod: function () { console.log('myMethod has been called.'); } }; })(); //    Module.myMethod(); 


The return directive returns publicly available methods in a global OB. However, they belong to the desired namespace. A Module can contain as many methods as needed.

 //   var Module = (function () { return { myMethod: function () { }, someOtherMethod: function () { } }; })(); //    Module.myMethod(); Module.someOtherMethod(); 


No need to try to dump all methods into a global OB and pollute it. This is how you can organize a private OB without returning the function:

 var Module = (function () { var privateMethod = function () { }; return { publicMethod: function () { } }; })(); 


We can call publicMethod, but we cannot call privateMethod - it belongs to a private OB. Anything can be thrown into these functions — addClass, removeClass, Ajax / XHR calls, Array, Object, etc.

An interesting twist is that within one OB, all functions have access to any other, so we can call private methods from public methods that are not available in the global OB:

 var Module = (function () { var privateMethod = function () { }; return { publicMethod: function () { //     `privateMethod`: // privateMethod(); } }; })(); 


This increases the interactivity and security of the code. For the sake of safety, it is not necessary to dump all functions into a global OB, so that functions that do not need to be called up would not inadvertently cause.

An example of returning an object using private and public methods:

 var Module = (function () { var myModule = {}; var privateMethod = function () { }; myModule.publicMethod = function () { }; myModule.anotherPublicMethod = function () { }; return myModule; // returns the Object with public methods })(); //  Module.publicMethod(); 


It is convenient to begin the name of private methods with an underline in order to visually distinguish them from public ones:

 var Module = (function () { var _privateMethod = function () { }; var publicMethod = function () { }; })(); 


It is also convenient to return methods in a list, returning references to functions:

 var Module = (function () { var _privateMethod = function () { }; var publicMethod = function () { }; return { publicMethod: publicMethod, anotherPublicMethod: anotherPublicMethod } })(); 

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


All Articles