DEV Community

Umashankar S
Umashankar S

Posted on

Common Beginners JavaScript Mistakes - 2.0

1. Incorrect References to this

There’s no shortage of confusion among JavaScript developers regarding JavaScript’s this keyword.

As JavaScript coding techniques and design patterns have become increasingly sophisticated over the years, there’s been a corresponding increase in the proliferation of self-referencing scopes within callbacks and closures, which are a fairly common source of “this/that confusion” causing JavaScript issues.

Consider this example code snippet:

function getInformation() {
  setTimeout(function() {
    console.log(this.names); //undefined
    this.reset();            //type error reset is not a function
  }, 1000);
}

const obj1 = {
  names: 'Uma',
  reset: function() {
    console.log("reset")
  }
}

const data = getInformation.bind(obj1);
data();
Enter fullscreen mode Exit fullscreen mode

Why? It’s all about context. The reason you get the above error is because, when you invoke setTimeout(), you are actually invoking window.setTimeout(). As a result, the anonymous function being passed to setTimeout() is being defined in the context of the window object, which has no reset() method.

A traditional, old-browser-compliant solution is to simply save your reference to this in a variable that can then be inherited by the closure; e.g.:

function getInformation() {
  var self = this;
  setTimeout(function() {
    console.log(self.names);
    self.reset();
  }, 1000)
}

const obj1 = {
  names: 'Uma',
  reset: function() {
    console.log("reset")
  }
}

const data = getInformation.bind(obj1);
data();
Enter fullscreen mode Exit fullscreen mode

Alternatively, in newer browsers, you can use the bind() method to pass in the proper reference:

function getInformation() {
  setTimeout(this.reset.bind(this), 1000);
}

const obj1 = {
  names: 'Uma',
  reset: function() {
    console.log("reset")
  }
}

const data = getInformation.bind(obj1);
data();
Enter fullscreen mode Exit fullscreen mode

2. Thinking There Is Block-level Scope

Consider, for example, the following code:

for (var i = 0; i < 10; i++) {
    /* ... */
}
console.log(i);  // What will this output?
Enter fullscreen mode Exit fullscreen mode

If you guess that the console.log() call would either output undefined or throw an error, you guessed incorrectly. Believe it or not, it will output 10. Why?

In most other languages, the code above would lead to an error because the “life” (i.e., scope) of the variable i would be restricted to the for block. In JavaScript, though, this is not the case and the variable i remains in scope even after the for loop has completed, retaining its last value after exiting the loop. (This behavior is known, incidentally, as variable hoisting.)

Support for block-level scopes in JavaScript is available via the let keyword. The let keyword has been widely supported by browsers and back-end JavaScript engines like Node.js for years now..

If that’s news to you, it’s worth taking the time to read up on scopes, prototypes, and more.

3. Creating Memory Leaks

Now, consider this code (No Memory Leak):

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  theThing = {
    longStr: new Array(1000000).join('*')
  };
};
setInterval(replaceThing, 1000);
Enter fullscreen mode Exit fullscreen mode

var originalThing = theThing (Referencing);
But everytime replaceThing() function finish the execution, JS Engine is clever enough to clear the memory. beacuse, we are not using that reference anywhere inside the closure functions.

Now, consider this code (Memory Leak):

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);
Enter fullscreen mode Exit fullscreen mode

Please Refer this article for more detail

4. Confusion About Equality
One of the conveniences in JavaScript is that it will automatically coerce any value being referenced in a boolean context to a boolean value. But there are cases where this can be as confusing as it is convenient. Some of the following, for example, have been known to be troublesome for many a JavaScript developer:

// All of these evaluate to 'true'!
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0);

// And these do too!
if ({}) // ...
if ([]) // ...
Enter fullscreen mode Exit fullscreen mode

With regard to the last two, despite being empty (which might lead one to believe that they would evaluate to false), both {} and [] are in fact objects and any object will be coerced to a boolean value of true in JavaScript, consistent with the ECMA-262 specification.

As these examples demonstrate, the rules of type coercion can sometimes be clear as mud. Accordingly, unless type coercion is explicitly desired, it’s typically best to use === and !== (rather than == and !=), so as to avoid any unintended side effects of type coercion. (== and != automatically perform type conversion when comparing two things, whereas === and !== do the same comparison without type conversion.)

And completely as a sidepoint—but since we’re talking about type coercion and comparisons—it’s worth mentioning that comparing NaN with anything (even NaN!) will always return false. You therefore cannot use the equality operators (==, ===, !=, !==) to determine whether a value is NaN or not. Instead, use the built-in global isNaN() function:

console.log(NaN == NaN);    // False
console.log(NaN === NaN);   // False
console.log(isNaN(NaN));    // True
Enter fullscreen mode Exit fullscreen mode

5. Incorrect Use of Function Definitions Inside for Loops

var elements = document.getElementsByTagName('input');
var n = elements.length;    // Assume we have 10 elements for this example
for (var i = 0; i < n; i++) {
    elements[i].onclick = function() {
        console.log("This is element #" + i);
    };
}
Enter fullscreen mode Exit fullscreen mode

Based on the above code, if there were 10 input elements, clicking any of them would display “This is element #10”! This is because, by the time onclick is invoked for any of the elements, the above for loop will have completed and the value of i will already be 10 (for all of them).

Here’s how we can correct the aforementioned problems with JavaScript to achieve the desired behavior:

var elements = document.getElementsByTagName('input');
var n = elements.length;    // Assume we have 10 elements for this example
var makeHandler = function(num) {  // Outer function
     return function() {   // Inner function
         console.log("This is element #" + num);
     };
};
for (var i = 0; i < n; i++) {
    elements[i].onclick = makeHandler(i+1);
}
Enter fullscreen mode Exit fullscreen mode

In this revised version of the code, makeHandler is immediately executed each time we pass through the loop, each time receiving the then-current value of i+1 and binding it to a scoped num variable. The outer function returns the inner function (which also uses this scoped num variable) and the element’s onclick is set to that inner function. This ensures that each onclick receives and uses the proper i value (via the scoped num variable).

6. Failure to Properly Leverage Prototypal Inheritance
A surprisingly high percentage of JavaScript developers fail to fully understand, and therefore to fully leverage, the features of prototypal inheritance.

Here’s a simple example. Consider this code:

BaseObject = function(name) {
    if (typeof name !== "undefined") {
        this.name = name;
    } else {
        this.name = 'default'
    }
};
Enter fullscreen mode Exit fullscreen mode

Seems fairly straightforward. If you provide a name, use it, otherwise set the name to ‘default’. For instance:

var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');

console.log(firstObj.name);  // -> Results in 'default'
console.log(secondObj.name); // -> Results in 'unique'
Enter fullscreen mode Exit fullscreen mode

But what if we were to do this:

delete secondObj.name;
console.log(secondObj.name); // -> Results in 'undefined'
Enter fullscreen mode Exit fullscreen mode

But wouldn’t it be nicer for this to revert to ‘default’? This can easily be done, if we modify the original code to leverage prototypal inheritance, as follows:

BaseObject = function (name) {
    if(typeof name !== "undefined") {
        this.name = name;
    }
};

BaseObject.prototype.name = 'default';
Enter fullscreen mode Exit fullscreen mode

With this version, BaseObject inherits the name property from its prototype object, where it is set (by default) to 'default'. Thus, if the constructor is called without a name, the name will default to default. And similarly, if the name property is removed from an instance of BaseObject, the prototype chain will then be searched and the name property will be retrieved from the prototype object where its value is still 'default'. So now we get:

var thirdObj = new BaseObject('unique');
console.log(thirdObj.name);  // -> Results in 'unique'

delete thirdObj.name;
console.log(thirdObj.name);  // -> Results in 'default'
Enter fullscreen mode Exit fullscreen mode

7. Creating Incorrect References to Instance Methods
Let’s define a simple object, and create an instance of it, as follows:

var MyObject = function() {}

MyObject.prototype.whoAmI = function() {
    console.log(this === window ? "window" : "MyObj");
};

var obj = new MyObject();
Enter fullscreen mode Exit fullscreen mode

Now, for convenience, let’s create a reference to the whoAmI method, presumably so we can access it merely by whoAmI() rather than the longer obj.whoAmI():

var whoAmI = obj.whoAmI;
Enter fullscreen mode Exit fullscreen mode

And just to be sure everything looks copacetic, let’s print out the value of our new whoAmI variable:

console.log(whoAmI);
Enter fullscreen mode Exit fullscreen mode

Outputs:

function () {
    console.log(this === window ? "window" : "MyObj");
}
Enter fullscreen mode Exit fullscreen mode

Okay, cool. Looks fine.

But now, look at the difference when we invoke obj.whoAmI() vs. our convenience reference whoAmI():

obj.whoAmI();  // Outputs "MyObj" (as expected)
whoAmI();      // Outputs "window" (uh-oh!)
Enter fullscreen mode Exit fullscreen mode

What went wrong? When we did the assignment var whoAmI = obj.whoAmI;, the new variable whoAmI was being defined in the global namespace. As a result, its value of this is window, not the obj instance of MyObject!

Thus, if we really need to create a reference to an existing method of an object, we need to be sure to do it within that object’s namespace, to preserve the value of this. One way of doing this would be as follows:

var MyObject = function() {}

MyObject.prototype.whoAmI = function() {
    console.log(this === window ? "window" : "MyObj");
};

var obj = new MyObject();
obj.w = obj.whoAmI;   // Still in the obj namespace

obj.whoAmI();  // Outputs "MyObj" (as expected)
obj.w();       // Outputs "MyObj" (as expected)
Enter fullscreen mode Exit fullscreen mode

8. Failure to Use “Strict Mode”
“strict mode” (i.e., including 'use strict'; at the beginning of your JavaScript source files) is a way to voluntarily enforce stricter parsing and error handling on your JavaScript code at runtime, as well as making it more secure.

While, admittedly, failing to use strict mode is not a “mistake” per se, its use is increasingly being encouraged and its omission is increasingly becoming considered bad form.

Here are some key benefits of strict mode:

1. Makes debugging easier: Code errors that would otherwise have been ignored or would have failed silently will now generate errors or throw exceptions, alerting you sooner to problems with JavaScript in your codebase and directing you more quickly to their source.

2. Prevents accidental globals: Without strict mode, assigning a value to an undeclared variable automatically creates a global variable with that name. This is one of the most common JavaScript errors. In strict mode, attempting to do so throws an error.

3. Eliminates this coercion: Without strict mode, a reference to a this value of null or undefined is automatically coerced to the global. This can cause many frustrating bugs. In strict mode, referencing a this value of null or undefined throws an error.

4. Disallows duplicate property names or parameter values: Strict mode throws an error when it detects a duplicate named property in an object (e.g., var object = {foo: "bar", foo: "baz"};) or a duplicate named argument for a function (e.g., function foo(val1, val2, val1){}), thereby catching what is almost certainly a bug in your code that you might otherwise have wasted lots of time tracking down.

5. Makes eval() safe: There are some differences in the way eval() behaves in strict mode and in nonstrict mode. Most significantly, in strict mode, variables and functions declared inside an eval() statement are not created in the containing scope. (They are created in the containing scope in nonstrict mode, which can also be a common source of problems with JavaScript.)

6. Throws error on invalid use of delete: The delete operator (used to remove properties from objects) cannot be used on nonconfigurable properties of the object. Nonstrict code will fail silently when an attempt is made to delete a nonconfigurable property, whereas strict mode will throw an error in such a case.

Top comments (0)