
Sometimes JavaScript can mislead a developer, and sometimes lead to white heat because of its incomplete consistency. There are some things in JavaScript that just confuse and confuse. The most famous of these are
the with statement ,
implicit global variables, and
strange behavior during a comparison operation .
Perhaps, the most controversy in the history of programming has flared up around JavaScript. In addition to its shortcomings (partly discussed in the new ECMAScript specifications), most programmers are unhappy with the following points:
')
- DOM, which many mistakenly consider the equivalent of the JavaScript language itself, has a very unsuccessful API.
- When you switch to JavaScript from C and Java languages, you fall into the trap of syntax, which is not like in imperative languages. This very often leads to bugs and is very annoying.
As a result, JavaScript has gained a rather bad reputation, which, in general, it does not deserve. And most often this is due to the fact that many developers transfer their experience in Java or C / C ++ to JavaScript. Here are three of the most difficult cases, demonstrating the difference in approaches between Java and JavaScript.
Area of visibility
Most developers switch to javascript due to the need. And almost everyone repeats one mistake - they start writing code without first learning the features of the language. Many people at least once have difficulty with areas of visibility.
The syntax of JavaScript is very similar to that used in the C family, with its curly braces separating the constructs of functions,
if
and
for
. Therefore, many developers assume that the scope at the block level is arranged along similar principles. Unfortunately, this is not the case.
First, the scope of variables is determined by functions, not parentheses. That is,
if
and
for
do not create a new scope, and the variable declared in their constructions, in fact, "rises". That is, it is created at the beginning of the very first function in which it is declared, in other words, in the global scope.
Secondly, the presence of a
with
statement makes the JavaScript scope dynamic, it cannot be determined before the program starts. It is better to avoid using
with
at all; without it, JavaScript becomes a language that uses lexical scopes. That is, it will be enough to read the code in order to understand all the scopes.
Formally, in JavaScript there are four ways to include an identifier in scope:
- According to the language standard : by default, all fields contain the identifiers this and arguments.
- Based on formal parameters : the scope of any formal parameter of a function is limited to the function body.
- Using the function declaration .
- Using variable declarations.
But one thing to keep in mind: declaring (implicit) variables without using
var
leads to an implicit definition of the global scope. The same applies to the
this
pointer when the function is called without explicit binding.
Before going into details, it is recommended to use strict mode (
'use strict';
) and place all declarations of variables and functions at the beginning of each function. Avoid declaring variables and functions inside
for
and
if
blocks.
Uplifting
This term is used to simplify the description of how an ad is actually performed. Raised variables are declared at the very beginning of the functions containing them, and then initialized as
undefined
. Assignment is carried out directly in the line where the declaration occurs.
Consider an example:
function myFunction() { console.log(i); var i = 0; console.log(i); if (true) { var i = 5; console.log(i); } console.log(i); }
What do you think will be displayed on the screen?
undefined 0 5 5
The
var
statement does not declare a local copy of the variable
i
inside an
if
block. Instead, it overwrites the previously declared. Notice that the first
console.log
statement displays the actual value of the variable
i
, initialized as undefined. And if you go into strict mode? In
strict mode, variables
must be declared before they are used, but the JavaScript engine does not require it. By the way, keep in mind that
var
. Will not be required from you. If you need to catch such bugs, then use tools like
JSHint or
JSLint .
Let's look at an example that demonstrates another way of declaring variables that can lead to errors:
var notNull = 1; function test() { if (!notNull) { console.log("Null-ish, so far", notNull); for(var notNull = 10; notNull <= 0; notNull++){
In this example, the
if
block is executed because the local copy of the
notNull
variable
notNull
declared inside the
test()
function and
raised . The casting operation plays its role here.
Functional expressions and function declarations
Raising can be applied not only to variables, but also to functional expressions, which are actually variables, and to function declarations. This feature is only briefly mentioned here. In short, function declarations generally behave as functional expressions, except that their declarations are placed at the beginning of their scope.
Here is an example function declaration:
function foo() {
Now compare with the example of a functional expression:
function foo() {
For a deeper understanding of the issue is to refer to the publications listed at the end of the post.
With
This example reflects the situation when the scope can only be determined at run time:
function foo(y) { var x = 123; with(y) { return x; } }
If
y
has a field
x
, then the function
foo()
returns
yx
, otherwise -
123
. This practice can lead to errors at the execution stage, so it is recommended to avoid using the
with
statement.
Looking to the future: ECMAScript 6
ECMAScript 6 specifications will allow the implementation of a fifth way to define a block-level scope: the
let
statement.
function myFunction() { console.log(i); var i = 0; console.log(i); if (false) { let i = 5; console.log(i); } console.log(i); }
In ECMAScript 6, an
i
declaration inside an
if
using let will create a new local variable in an
if
block. As a non-standard alternative, you can declare
let
blocks:
var i = 6; let (i = 0, j = 2) { }
In this example, the variables
i
and
j
will exist only inside the block. At the time of writing this post, only Chrome is
supported using
let
.
In other languages
Below is a comparative table of the features of the implementation of scopes in different languages:
Property | Java | Python | Javascript | Note |
---|
Area of visibility | Lexical (blocks) | Lexical (functions, classes or modules) | Yes | It does not work at all like in Java or C. |
---|
Block scope | Yes | Not | In conjunction with let (ES6) | It does not work at all like in Java. |
---|
Raising | Not | Not | Yes | For the declaration of variables, functions and functional expressions. |
---|
Functions
Functions are often another stumbling block in JavaScript. The reason is that imperative languages like Java use a completely different concept. JavaScript refers to functional programming languages. True, it is not purely functional, yet it clearly shows the imperative style and encourages mutability. But be that as it may, JavaScript can only be used as a functional language, without any external influence on function calls.
In JavaScript, functions can be accessed like any other data type, for example,
String
or
Number
. They can be stored in variables and arrays, passed as arguments to other functions, and returned by other functions. They can have properties, they can be dynamically changed, and all this thanks to objects.
For many newbies to JavaScript, the fact that the functions here are objects is amazing. The
Function
constructor creates a
Function
object:
var func = new Function(['a', 'b', 'c'], '');
This is almost the same:
function func(a, b, c) { }
Almost - because using a constructor is less efficient. It generates an anonymous function and does not create a closure for its context.
Function
objects are always created in the global scope.
Function
, as a type of function, is based on
Object
. This can be clearly seen if you disassemble any function we declare:
function test() {}
This means that the function has properties. Some of them are assigned at creation. For example,
name
or
length
, returning, respectively, the name and number of arguments in the function definition.
function func(a, b, c) { }
Any function can be set and other properties, at its discretion:
function test() { console.log(test.custom); } test.custom = 123;
In other languages
Comparative table of implementations of functions in different languages:
Property | Java | Python | Javascript | Note |
---|
Functions as built-in types | Lambda java 8 | Yes | Yes | |
---|
Callback / Team Template | Objects (or lambdas for Java 8) | Yes | Yes | Functions (callbacks) |
---|
Dynamic creation | Not | Not | eval ( Function object) | eval raises questions from a security point of view; Function objects can work unpredictably. |
---|
Properties | Not | Not | May have properties | Access to function properties can be restricted. |
---|
Closures
JavaScript was the first major programming language in which closures appeared. As you probably know, in Java and Python for a long time there were simplified versions of closures, when you could only read some values from the enclosing scopes. For example, in Java, an anonymous nested class provides functionality similar to closures (with some limitations). For example, in their scopes only final local variables can be used. More precisely, their values can be read.
JavaScript has full access to external variables and functions of the external scope. They can be read, written down and, if necessary, even hidden with the help of local definitions. Examples of this were repeatedly presented in the first chapter.
Even more interesting is that the function created in the closure "remembers" the environment in which it was created. By combining closures and nesting of functions, you can make it so that external functions return internal ones without their execution. Moreover, the local variables of external functions can be stored in the closure of the internal function for a long time after the execution of the one where they were declared last time. This is quite a powerful tool, but it has one drawback: a common problem with memory leaks in JavaScript applications.
For a better understanding of the foregoing, let's look at a few examples.
function makeCounter () { var i = 0; return function displayCounter () { console.log(++i); }; } var counter = makeCounter();
The function
makeCounter()
creates and returns another function that maintains its connection with its parent environment. Although the execution of
makeCounter()
ended with the assignment of the counter variable, the local variable i is stored in the closure of the
displayCounter
, inside of which you can access it.
If you run
makeCounter()
again, it will create a new closure with a different initial value
i
:
var counterBis = makeCounter();
You can also make so that
makeCounter()
takes an argument:
function makeCounter(i) { return function displayCounter () { console.log(++i); }; } var counter = makeCounter(10);
Arguments of external functions are also stored in the closure, so we do not need to declare a local variable. With each call to
makeCounter()
initial value we set will be remembered, from which the count will be taken.
Closures are crucial for many fundamental things in JavaScript: namespaces, modules, private variables, memoizations, and so on. For example, this is how you can simulate a private variable for an object:
function Person(name) { return { setName: function(newName) { if (typeof newName === 'string' && newName.length > 0) { name = newName; } else { throw new TypeError("Not a valid name"); } }, getName: function () { return name; } }; } var p = Person("Marcello");
This way, you can create a wrapper for the property name with our own setter and getter. In ES 5, this has become much easier to do, since you can create objects with setters / getters for their properties and fine-tune access to these properties.
In other languages
Comparative table of closure implementations in different languages:
Property | Java | Python | Javascript | Note |
---|
Short circuit | Disabled, read only, in anonymous nested classes | Handicapped, read only, in nested definitions | Yes | Memory leaks |
---|
Memoization template | Shared objects must be used. | Perhaps using lists or dictionaries | Yes | It is better to use deferred calculations. |
---|
Namespace / module pattern | Not necessary | Not necessary | Yes | |
---|
Private Attributes Template | Not necessary | Is impossible | Yes | May be misleading |
---|
Conclusion
So, this article describes three features of JavaScript, which most often confuse developers who have previously worked in other programming languages, especially Java and C. If you want to further study the topics touched, you can read these resources:
•
Scoping in JavaScript•
Function Declarations vs Function Expressions•
Let statement and let blocks