📜 ⬆️ ⬇️

Three features of JavaScript, which is useful to know every Java / C developer



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:
')

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:


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++){ //.. } console.log("Now it's not null", notNull); } console.log(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() { // A function declaration function bar() { return 3; } return bar(); // This function declaration will be hoisted and overwrite the previous one function bar() { return 8; } } 

Now compare with the example of a functional expression:

 function foo() { // A function expression var bar = function() { return 3; }; return bar(); // The variable bar already exists, and this code will never be reached var bar = function() { return 8; }; } 

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) { /* Other code here */ } // prints 6 console.log(i); 

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:
PropertyJavaPythonJavascriptNote
Area of ​​visibilityLexical (blocks)Lexical (functions, classes or modules)YesIt does not work at all like in Java or C.
Block scopeYesNotIn conjunction with let (ES6)It does not work at all like in Java.
RaisingNotNotYesFor 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() {} // prints "object" console.log(typeof test.prototype); // prints function Function() { [native code] } console.log(test.constructor); 

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) { } // prints "func" console.log(func.name); // prints 3 console.log(func.length); 

Any function can be set and other properties, at its discretion:

 function test() { console.log(test.custom); } test.custom = 123; // prints 123 test(); 

In other languages


Comparative table of implementations of functions in different languages:
PropertyJavaPythonJavascriptNote
Functions as built-in typesLambda java 8YesYes
Callback / Team TemplateObjects (or lambdas for Java 8)YesYesFunctions (callbacks)
Dynamic creationNotNoteval ( Function object)eval raises questions from a security point of view; Function objects can work unpredictably.
PropertiesNotNotMay have propertiesAccess 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(); // prints 1 counter(); // prints 2 counter(); 

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(); // prints 1 counterBis(); // prints 3 counter(); // prints 2 counterBis(); 

You can also make so that makeCounter() takes an argument:

 function makeCounter(i) { return function displayCounter () { console.log(++i); }; } var counter = makeCounter(10); // prints 11 counter(); // prints 12 counter(); 

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"); // prints "Marcello" a.getName(); // Uncaught TypeError: Not a valid name a.setName(); // Uncaught TypeError: Not a valid name a.setName(2); a.setName("2"); // prints "2" a.getName(); 

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:

PropertyJavaPythonJavascriptNote
Short circuitDisabled, read only, in anonymous nested classesHandicapped, read only, in nested definitionsYesMemory leaks
Memoization templateShared objects must be used.Perhaps using lists or dictionariesYesIt is better to use deferred calculations.
Namespace / module patternNot necessaryNot necessaryYes
Private Attributes TemplateNot necessaryIs impossibleYesMay 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

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


All Articles