JS has several concepts related to scope, which 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, having heard such words as scope, closure, “this”, namespace, scope of function, global variables, lexical scope, private and public areas ... I hope, by By reading the material you can answer the following questions:
- What is the scope?
- what is global / local OS?
- what is the namespace and how does it differ from OB?
- What does this keyword mean, and how does it relate to OB?
- What is a functional and lexical OB?
- what is a closure?
- How can I understand and create all this?
What is 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 understanding OB. Let's figure out where the variables and functions are available, how to change the context in the code and write faster and more supported code (which is faster to debug). Dealing with the OM is simple - we ask ourselves the question, which of the OMs are we in now, in A or B?
What is global / local OB?
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. Learning how to work with different agents, you will not encounter problems with global agents, unless you see name intersections. Often you can hear "global OB is bad", but it’s not often possible to get an explanation of why. GOV - not bad, you need to use it when creating modules and APIs that will be available from different OBs, you just need to use it to the benefit and carefully.
We all used jQuery. As soon as we write
jQuery('.myClass');
we get access to jQuery in the global OB, and we can call this access a namespace. Sometimes the term “namespace” is used instead of the term OB, but usually it refers to an OB of the same level. In our case, jQuery is located in the global OB, and is our namespace. The jQuery namespace is defined in the global OB, which acts as a PI for the jQuery library, while all its contents are inherited from that PI.
What is local OB?
Local OM is called any OM 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 external function OB.
If I define functions and set variables inside, they belong to the local OB. Example:
// ОВ A: глобальная
var myFunction = function () {
// ОВ B: локальная
};
All variables from the BWT are not visible in the GOW. They cannot be accessed directly from the outside. 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 not defined.
Functional OB.
All local OBs are created only in functional OBs; they are not created by for or while loops or by directives like if or switch. A new feature is a new scope. Example:
// ОВ A
var myFunction = function () {
// ОВ B
var myOtherFunction = function () {
// ОВ C
};
};
So you can simply create a new OB and local variables, functions and objects.
Lexical OB
If one function is defined inside another, the internal has access to the external OM. This is called a “lexical OB”, or “closure,” or else a “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 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, this 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 link to “name”, but not the variable itself.
OB sequences
The OB sequences determine the OB of any selected function. Each defined function has its own OM, and each function defined inside another has its own OM, connected with the external OM - this is a sequence, or a chain. The position in the code defines the OB. Determining the value of a variable, JS goes from the deepest embedded OB outward until it finds the function, object or variable that it is looking for.
Short circuits
They live in close alliance with lexical OBs. A good use case is returning a function reference. We can bring out different links that make it possible to access what was defined inside.
var sayHello = function (name) {
var text = 'Hello, ' + name;
return function () {
console.log(text);
};
};
To display the text, it is not enough just to call the sayHello function:
sayHello('Todd'); // тишина
The function returns a function, so you must first assign it, and then call:
var helloTodd = sayHello('Todd');
helloTodd(); // вызывает замыкание и выводит 'Hello, Todd'
You can of course cause a closure directly:
sayHello('Bob')(); // вызывает замыкание без присваивания
AngularJS uses similar calls in the $ compile method, where you need to pass a link to the current OB:
$compile(template)(scope);
You can guess that in simplified terms their code looks something like this:
var $compile = function (template) {
// всякая магия
// без доступа к scope
return function (scope) {
// здесь есть доступ и к `template` и к `scope`
};
};
A function does not have to return anything to be a closure. Any access to variables from outside the current OB creates a closure.
OV and 'this'
Each OB assigns its value to the “this” variable, depending on how the function is called. We all used the this keyword, but not everyone understands how it works and what are the differences in calls. 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'); //
There are also problems with the value of this. In the following example, inside the same function, the value and OB can change:
var nav = document.querySelector('.nav'); //
var toggleNav = function () {
console.log(this); // element
setTimeout(function () {
console.log(this); // [объект Window]
}, 1000);
};
nav.addEventListener('click', toggleNav, false);
Here we created a new OB, which is not called from the event handler, which means it refers to the window object. You can, for example, store the value of this in another variable so that there is no confusion:
var nav = document.querySelector('.nav'); //
var toggleNav = function () {
var that = this;
console.log(that); // элемент
setTimeout(function () {
console.log(that); // элемент
}, 1000);
};
nav.addEventListener('click', toggleNav, false);
Change the OB using .call (), .apply () and .bind ()
Sometimes there is a need to change the OS 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 the elements being searched, we do not call anything or change the OB. Let's see how we can change the OB (more precisely, we change the context of function calls).
.call () and .apply ()
The .call () and .apply () methods allow passing OBs 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 iterated elements are passed to this. The .call method (scope, arg1, arg2, arg3) takes a list of arguments separated by commas, and the .apply (scope, [arg1, arg2]) method takes an array of arguments.
It’s important to remember that .call () or .apply () methods call functions, so instead of
myFunction(); // вызывает myFunction
let .call () call the function and pass the parameter:
myFunction.call(scope);
.bind ()
.bind () does not call the function, but simply binds the values of the 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 organic matter, the creation of an extra function, which will negatively affect performance. Therefore, we use .bind (), as a result, we can pass arguments so that the function is not called:
In JavaScript, unlike many other languages, there are no concepts of public and private OS, but we can emulate them using closures. To create a private OB, we can wrap our functions in other functions.
(function () {
// здесь приватная ОВ
})();
Add functionality:
(function () {
var myFunction = function () {
// делаем здесь, что нужно
};
})();
But you cannot directly call this function:
(function () {
var myFunction = function () {
// делаем здесь, что нужно
};
})();
myFunction(); // Uncaught ReferenceError: myFunction is not defined
Here you have a private OB. If you need public OV, we will 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 methods available publicly in the global OB. Moreover, they relate to the desired namespace. 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 the methods in the global OM 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 can not privateMethod - it refers to a private OB. You can put anything you like into these functions - addClass, removeClass, Ajax / XHR calls, Array, Object, etc.
An interesting twist is that inside one OB all functions have access to any other, so from public methods we can call private ones that are not available in the global OB:
var Module = (function () {
var privateMethod = function () {
};
return {
publicMethod: function () {
// есть доступ к методу `privateMethod`:
// privateMethod();
}
};
})();
This improves the interactivity and security of the code. For the sake of safety, it is not worth dumping all functions into the global OB, so that functions that you do not need to call would not be called inadvertently.
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 start the name of private methods with underlining in order to visually distinguish them from public:
var Module = (function () {
var _privateMethod = function () {
};
var publicMethod = function () {
};
})();
It is also convenient to return methods in a list, returning links to functions:
var Module = (function () {
var _privateMethod = function () {
};
var publicMethod = function () {
};
return {
publicMethod: publicMethod,
anotherPublicMethod: anotherPublicMethod
}
})();