📜 ⬆️ ⬇️

Top 10 Common JavaScript Mistakes



Today, JavaScript is at the core of most modern web applications. At the same time, in recent years a large number of JavaScript libraries and frameworks for developers of Single Page Application (SPA) , graphics, animation, and even server platforms have appeared. JavaScript is widely used for web development, and therefore the quality of the code is becoming increasingly important.

At first glance, this language may seem rather simple. Embedding basic JavaScript functionality into a web page is not a problem for any experienced developer, even if he hasn’t previously come across this language. However, this is a deceptive impression, since JavaScript is much more complicated, more powerful and more sensitive to nuances than it seems at first. Many subtleties in this language lead to a large number of common errors. Today we look at some of them. You need to pay special attention to these errors if you want to program well in JavaScript.

1. Incorrect links to this

Due to the fact that in recent years, JavaScript programming has become very complicated, the number of occurrences of callback functions and closures has increased, which often cause confusion with the this .
')
For example, the execution of this code:
 Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); //   "this"? }, 0); }; 

Causes an error:
 Uncaught TypeError: undefined is not a function 

Why is this happening? It's all about context. When you call setTimeout() , you actually call window.setTimeout() . As a result, the anonymous function passed to setTimeout() is defined in the context of a window object that does not have a clearBoard() method.

The traditional solution, compatible with older browsers, involves simply storing the reference to this in a variable that can be stored in the closure:
 Game.prototype.restart = function () { this.clearLocalStorage(); var self = this; //    'this',     'this'! this.timer = setTimeout(function(){ self.clearBoard(); //    }, 0); }; 

For new browsers, you can use the bind() method, which allows you to associate a function with the execution context:
 Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); //  'this' }; Game.prototype.reset = function(){ this.clearBoard(); //     'this'! }; 

2. Block-level scope

Developers often believe that JavaScript creates a new scope for each block of code. Although this is true for many other languages, this does not happen in JavaScript. Let's look at this code:
 for (var i = 0; i < 10; i++) { /* ... */ } console.log(i); //   ? 

If you think that the console.log() call will result in an undefined output or an error, then you are mistaken: “10” will be output. Why? In most other languages, this code would lead to an error, because the scope of the variable i would be limited to a for block. However, in JavaScript, this variable remains in scope even after the for loop is completed, retaining its last value (this behavior is known as “ var hoisting ”). It should be noted that block-level support for scopes has been introduced in JavaScript since version 1.7 using the let descriptor.

3. Memory leaks

Memory leaks are almost inevitable if, during work, you do not consciously avoid them. There are many reasons for the appearance of leaks, but we will focus only on the most frequent.

References to non-existent objects . Let's analyze this code:
 var theThing = null; var replaceThing = function () { var priorThing = theThing; var unused = function () { // 'unused' -  ,   'priorThing', //  'unused'    if (priorThing) { console.log("hi"); } }; theThing = { longStr: new Array(1000000).join('*'), //  1M  someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000); //  'replaceThing'   

If you run the execution of this code, you can detect a massive memory leak at a speed of about a megabyte per second. It seems that we lose the memory allocated for longStr on every call to replaceThing . What is the reason?

Each theThing object contains its own 1Mb longStr object. Every second when calling replaceThing , the function stores a reference to the previous theThing object in the theThing variable. This is not a problem, because each time the previous priorThing link will be ground ( priorThing = theThing; ). So what is the reason for the leak?

A typical way to implement a closure is to create a connection between each function object and a dictionary object, which is the lexical scope for this function. If both functions ( unused and someMethod ) defined inside replaceThing actually use priorThing , then it is important to understand that they receive the same object, even if priorThing rewritten again and again, since both functions use the same lexical scope . And as soon as the variable is used in any of the closures, it falls into the lexical scope, used by all closures in this scope. And this little nuance leads to a powerful memory leak.

Cyclic links . Consider the sample code:
 function addClickHandler(element) { element.click = function onClick(e) { alert("Clicked the " + element.nodeName) } } 

Here onClick has a closure in which the element reference is stored. By onClick as the click event handler for element , we created a circular reference: element -> onClick -> element -> onClick -> element ...

Even if you delete an element from the DOM, a circular reference will hide element and onClick from the garbage collector and a memory leak will occur. What is the best way to avoid leaks? JavaScript memory management (and, in particular, garbage collection) is largely based on the concept of object reachability. The following objects are considered reachable and are known as root:

Objects are stored in memory only as long as they are accessible from the root by reference or chain of references.

Browsers have a built-in garbage collector that clears memory from unreachable objects. That is, the object will be deleted from memory only if the garbage collector decides that it is unreachable. Unfortunately, unused large objects that are considered “achievable” can easily accumulate.

4. Misunderstanding of equality

One of the advantages of JavaScript is that it automatically converts any value to a boolean value if it is used in a boolean context. However, there are times when this convenience can be misleading:
 //     'true'! console.log(false == '0'); console.log(null == undefined); console.log(" \t\r\n" == 0); console.log('' == 0); //   ! if ({}) // ... if ([]) // ... 

Given the last two lines, even if they are empty, {} and [] are actually objects. And any object in JavaScript corresponds to the boolean value true . However, many developers believe that the value will be false .

As the two examples show, automatic type conversion can sometimes interfere. It is generally better to use === and !== instead of == and != To avoid the side effects of type conversion.

By the way, comparing NaN with something (even with NaN !) NaN always give the result false . Thus, you cannot use the equality operators ( == , === != !== ) to determine if the value of NaN matches. Instead, use the isNaN() built-in global function:
 console.log(NaN == NaN); // false console.log(NaN === NaN); // false console.log(isNaN(NaN)); // true 

5. Irrational use of the DOM

In JavaScript, you can easily work with the DOM (including adding, changing and deleting elements), but often developers do it inefficiently. For example, add a series of items one at a time. However, the operation of adding elements is very costly, and its consistent implementation should be avoided.

If you need to add several elements, then, as an alternative, you can use document fragments:
 var div = document.getElementsByTagName("my_div"); var fragment = document.createDocumentFragment(); for (var e = 0; e < elems.length; e++) { fragment.appendChild(elems[e]); } div.appendChild(fragment.cloneNode(true)); 

We also recommend that you first create and modify elements, and then add to the DOM, it also significantly improves performance.

6. Incorrect use of function definitions inside for loops

Consider the sample code:
 var elements = document.getElementsByTagName('input'); var n = elements.length; // ,    10  for (var i = 0; i < n; i++) { elements[i].onclick = function() { console.log("This is element #" + i); }; } 

When clicking on any of the 10 elements, the message “This is element # 10” would appear. The reason is that by the time onclick is called by any of the elements, the upstream for loop will have completed and the value of i will be 10.

An example of the correct code:
 var elements = document.getElementsByTagName('input'); var n = elements.length; // ,    10  var makeHandler = function(num) { //   return function() { //   console.log("This is element #" + num); }; }; for (var i = 0; i < n; i++) { elements[i].onclick = makeHandler(i+1); } 

makeHandler immediately launched at each iteration of the loop, gets the current value of i+1 and stores it in the variable num . The external function returns an internal function (which also uses the num variable) and sets it as an onclick handler. This ensures that each onclick receives and uses the correct i value.

7. Incorrect prototype inheritance

Surprisingly, many developers do not have a clear understanding of the mechanism of inheritance through prototypes. Consider the sample code:
 BaseObject = function(name) { if(typeof name !== "undefined") { this.name = name; } else { this.name = 'default' } }; var firstObj = new BaseObject(); var secondObj = new BaseObject('unique'); console.log(firstObj.name); // ->  'default' console.log(secondObj.name); // ->  'unique' 

But if we wrote this:
 delete secondObj.name; 

you would get:
 console.log(secondObj.name); // ->  'undefined' 

But isn't it better to return the value to default ? This can be done easily if you apply inheritance through prototypes:
 BaseObject = function (name) { if(typeof name !== "undefined") { this.name = name; } }; BaseObject.prototype.name = 'default'; 

Each BaseObject instance inherits the name property of its prototype, in which it is assigned the value of default . Thus, if the constructor is called without a name , the default name will be default . And in the same way, if the name property is removed from the BaseObject instance, the prototype chain will be searched and the name property will be obtained from the prototype object, in which it is still equal to default :
 var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // ->  'unique' delete thirdObj.name; console.log(thirdObj.name); // ->  'default' 

8. Creating invalid references to instance methods.

We define a simple constructor and use it to create an object:
 var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? "window" : "MyObj"); }; var obj = new MyObject(); 

For convenience, create a link to the whoAmI method:
 var whoAmI = obj.whoAmI; 

whoAmI value of our new variable whoAmI :
 console.log(whoAmI); 

The console will display:
 function () { console.log(this === window ? "window" : "MyObj"); } 

Now notice the difference when calling obj.whoAmI() and whoAmI() :
 obj.whoAmI(); //  "MyObj" (  ) whoAmI(); //  "window" 

Something went wrong? When we assign var whoAmI = obj.whoAmI; The new variable has been defined in the global namespace. As a result, the value of this turned out to be equal to window , and not obj , to the MyObject instance. Thus, if we really need to create a reference to an existing object method, we need to do it within the namespace of this object. For example:
 var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? "window" : "MyObj"); }; var obj = new MyObject(); obj.w = obj.whoAmI; //     obj.whoAmI(); //  "MyObj" (  ) obj.w(); //  "MyObj" (  ) 

9. Using a string as the first argument to setTimeout or setInterval

This in itself is not a mistake. And it's not just about performance. The fact is that when you pass a string variable as the first argument to setTimeout or setInterval , it will be passed to the Function constructor for conversion to a new function. This process can be slow and inefficient. An alternative is to use the function as the first argument:
 setInterval(logTime, 1000); //   logTime  setInterval setTimeout(function() { //     setTimeout logMessage(msgValue); // (msgValue    ) }, 1000); 

10. Failure to use "strict mode"

This is a mode in which a number of restrictions are imposed on the executable code, which increases security and can prevent some errors from occurring. Of course, the rejection of the use of "strict mode" is not an error per se. Just in this case, you deprive yourself of a number of advantages:


In conclusion

The better you understand how and why JavaScript works, the more reliable the code will be, the more effectively you can use the capabilities of this language. Conversely, a misunderstanding of the paradigms embedded in JavaScript causes a large number of bugs in software products.

Therefore, the study of the nuances and subtleties of the language is the most effective strategy to improve their professionalism and productivity, and also help to avoid many common mistakes when writing JavaScript code.

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


All Articles