How to make your JS cleaner: Top 10 Mistakes in JavaScript
Top

10 Mistakes and Difficulties at the Beginning of My Javascript Career

Only after PHP did I start working with Javascript, so mistakes like writing assignment (=) instead of the equality comparison (==) was a thing of the past. Although, there are mistakes and misunderstandings that still remain fresh in my memory. Here are my top 10:

1. How to lose “this” context.

Probably one of the most common mistakes is to lose “this” context somewhere on the road to creating confident code.

Take a look at this example:

	function Counter() {
  this.count = 0;
  this.increment = function() {
    this.count++
  };
  this.timer = setTimeout(function() {
    this.increment(); // what is “this”?
  }, 0);
};
var score = new Counter();

Executing the above code results in the following error:

Uncaught TypeError: this.increment is not a function

The reason for this error is when you invoke setTimeout(), you are actually invoking window.setTimeout(). This means that this context inside the anonymous function passed to setTimeout() is being referred to the context of the window object, which has no increment() method. It gets even more complicated if you have a function increment() in the global scope that does something of its own. Then your code would behave unexpectedly and fire no errors.

A traditional solution is to simply save your reference to this in a variable that can then be inherited by the closure:

	function Counter() {
  var self = this;   // save reference to 'this'
  this.count = 0;
  this.increment = function() {
    this.count++
  };
  this.timer = setTimeout(function() {
      self.increment();  // now I know who 'self' is
    }, 0);
};
var score = new Counter();
	

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

	function Counter() {
  this.count = 0;
  this.increment = function() {
    this.count++
  };
  this.timer = setTimeout(this.increment.bind(this), 0);
};
var score = new Counter();

2. Prevent default behavior conditionally, that works in all browsers.

There can be problems when you use event.preventDefault() in Safari and IE8-. You can use event.returnValue = false for IE8-. The code for new browsers and IE8- would be:

	event.preventDefault ? event.preventDefault() : event.returnValue = false;

But this won’t do the trick in Safari. The universal way is to do:

	event.preventDefault();
return false;

Return false works with the old IE browsers and Safari, but you need to take into consideration, that return false is the equivalent of calling preventDefault() and stopPropagation() on the event.

Here’s the example, where you need to prevent default conditionally on the click event:

	document.onclick = function(event) {
  If (something) {
    event.preventDefault();
    return false;
  } else {
    // do something
  }
}

3. Difference Between preventDefault, stopPropagation, stopImmediatePropagation with Examples. How to Use Event Delegation.

If using jQuery – event.stopPropagation() is normalized and works consistently on all browsers. If you use plain vanilla Javascript – you may want to take IE8- into consideration.

event.cancelBubble = true; will stop event bubbling in IE.

Differences:

  • preventDefault: Cancels the event if it is possible, without stopping further propagation of the event.
  • stopPropagation: Prevents further propagation of the current event.
  • stopImmediatePropagation: Prevents other listeners of the same event from being called.

For the PreventDefault example we have an HTML form, and submit button that we want to prevent from default behavior:

	 <form id="someForm" action="/dummy-page" method="post">
    <label for="message">Message:</label>
    <textarea id="message"></textarea>
    <button type="submit">Send your message</button>
  </form>

    someForm.onsubmit = function(event) {
      event.preventDefault();
    }

For the stopPropagation example we have a link inside a container, which both have an onclick event listener attached. We will prevent event bubbling from link to container:

	<div class="container">
    <a href="#" class="link">Click Me!</a>
</div>


$('.container').on('click', function(event) {
    alert('container was clicked');
});

$('.link').on('click', function(event) {
    event.preventDefault(); // Now link won't go anywhere
    event.stopPropagation(); // Now the event won't bubble up
    alert('link was clicked');
});

After clicking the link we see a message:

“element was clicked”

For stopImmediatePropagation example we will attach 2 event listeners to a link item:

	<div class="container">
    <a href="#" class="item element">Click Me!</a>
</div>

We’ll add Event.stopPropagation to prevent the event from bubbling up.

	$('.item').on('click', function(event) {
    alert('an item was clicked');
});

$('.element').on('click', function(event) {
    event.preventDefault(); // Now link won't go anywhere
    event.stopPropagation(); // Now the event won't bubble up
    alert('an element was clicked');
});

But, when we click on the element we see:

	"an item was clicked"
"element was clicked"

The reason that two event listeners are triggered is because ‘item’ and ‘element’ are evenly ranked in the DOM. The click event fires on both ‘element’ and ‘item’ at the same time, but it’s not that it bubbles up to ‘container’ element. If you want to prevent all other events from firing on current element you cannot stopPropagation like you’d expect.

This is where stopImmediatePropagation comes in handy!

	$('.element').on('click', function(event) {
    event.preventDefault(); // Now link won't go anywhere
    event.stopImmediatePropagation(); // Now item on click won't fire
    console.log('element was clicked');
});

$('.item').on('click', function(event) {
    console.log('an item was clicked');
});

One important thing you should know about stopImmediatePropagation is that you should declare it on top of other event listeners to prevent them from firing. So, in order to get stopImmediatePropagation working we had to switch the listeners so that the element’s listener comes before item’s.

If we run the code above we will see:

	"element was clicked"

Event delegation allows you to avoid adding event listeners to specific nodes. Instead, the event listener is added to one parent. That event listener analyzes bubbled events to find a match on child elements.

Consider the case where we have a table with >TH< and >TD< elements and we want to highlight only >TD<s on the click event. We will add an event listener to the whole table and then check if the target of the click is a >TD< element to highlight it. Here is an example:

	table.onclick = function(event) {
  var target = event.target;
  if (target.tagName != 'TD') return;
  highlight(target);
};

4. Bind difficulties.

bind allows us to:

  • set the value of “this” to a specific object;
  • reuse methods;
  • curry a function.

At the beginning of my coding experience, I’d avoided using bind for as long as I could. As my coding needs grew larger, I discovered that bind can be an elegant solution to some techniques I’ve already been using.

We can bind ‘this’ context to another object and reuse methods like this:

	var Button = function(content) { 
  this.content = content; 
}; 
Button.prototype.click = function() { 
  console.log(this.content + ' clicked'); 
} 
var simpleButton = new Button('Simple Button'); 
simpleButton.click(); 
var looseClick = simpleButton.click; 
looseClick(); // not bound
var boundClick = simpleButton.click.bind(simpleButton); 
boundClick(); // bound

Which prints out:

	Simple Button clicked 
undefined clicked 
Simple Button clicked

Example showing binding some parameters

	var sum = function(a, b) { 
  return a + b; 
}; 
var add5 = sum.bind(null, 5); 
alert(add5(10));

One more

	x = 9; 
var module = { 
  x: 81, 
  getX: function () { 
    return this.x; 
  } 
}; 
module.getX(); // 81 
var getX = module.getX; 
getX(); // 9, because in this case, "this" refers to the global object 
// create a new function with 'this' bound to module 
var boundGetX = getX.bind(module); 
boundGetX(); // 81

3. Event Listeners Piling Up

When we add event listeners on some conditions – there is a chance of piling them up so that they will be triggered multiple times. Consider the following example:

	<input type="checkbox" id="checkbox" />
<button id="button">Click me!</button>
<p>Click the checkbox a few times.</p>

var checkbox = $('#checkbox'), 
var button = $('#button');

checkbox.on('change', function(){
    if(this.checked){
        button.on('click', function(){
            alert('Hello!');
        });
    }
});

In this code we add an onclick event listener to the button every time the checkbox is set to the “checked” state. Then we click on the button and the alert is shown multiple times.

In plain vanilla javascript we can removeEventListener before adding a new one, to ensure they won’t pile up like this:

	button.removeEventListener('click', eventHandler);
button.addEventListener('click', eventHandler);

Be sure to use named functions, for when you use anonymous functions – removeEventListener will not work.

In jQuery you can add and remove event listeners with on() and off() methods.
But be careful if you attach multiple functions with $.on() method and then remove the event with $.off() method. For example:

	button.on(‘click’, eventHandler1);
button.on(‘click’, eventHandler2);
button.off(‘click’);

Will remove all event handlers from the click event on the button element. So if you need to specify the event listener you want to remove – you can simply do it like so:

	button.on(‘click.customName1’, eventHandler1);
button.on(‘click.customName2’, eventHandler2);
button.off(‘click.customName2’);

The $.off() method will remove eventHandler2, but the button will keep eventHandler1 as a response for clicking on it.

6. Supplying event handlers with additional parameters

This seems obvious to me now, but took me some time to figure out the solution. If you want to provide an event handler with parameters, additional to event object you can do it like this:

	var fn = function(event, value) {
    event.target.value = value;
}
var newValue = 10;
document.addEventListener('click', function(event) {
  fn(event, newValue);
});

7. Understanding the callback principles

I had an issue where I supplied some function with a callback function result instead of a callback function, the callback function returned undefined and the function actually did nothing. The function must have been implemented like this:

	function someFunction(options, callback) {
    // some code here
    if (typeof callback === "function") {
        callback(options);
    }
}

And then I used the callback the wrong way like this:

	someFunction(options, callback());

But should have passed the callback function itself, instead of its result:

	someFunction(options, callback);

8. mutable/immutable in javascript

A mutable object can be changed after it’s created, and an immutable object can’t.
In Javascript, everything (except for strings) is mutable by default:

	var array  = [3, 8];
array[0] = 1;
// array is now [1, 8]

var variable = ++array[0];
// array is now [2, 8]
// variable equals 2

Freezing an object makes it immutable, though:

	 var array  = [3, 8];
// Make it immutable
Object.freeze(array);
array[0] = 1;
// array is still [4, 9]

Strings are the only thing that is immutable in Javascript:

	var testString = 'mutable?';
testString[7] = '!';
// string is still 'mutable?', the error is shown only in "use strict" mode, though.

Mutable objects are nice because you can make changes in-place, without allocating a new object. But be careful—whenever you make an in-place change to an object, all references to that object will now reflect the change.

9. Type coercion issue I’ve bumped into.

I had an html element with data attribute, that I used in my javascript code.

	<button id=”sampleButton” data-test="5">button</button> 

I don’t remember the exact case, where this issue occurred, but the idea was that I expected the data to be a number, but instead got a string.

	sampleButton.addEventListener('click', function(event) {
        alert(event.target.dataset.test === 5);
      })

Will return false, because event.target.dataset.test is a string

10. Debugging tricks

At the end of this article I want to share with you some handy tools to settle the issues you will encounter on your way to become a great coder:

Console.time and console.timeEnd
The console.time and console.timeEnd are the easiest way to check the time spent on code execution. console.time starts the time and console.timeEnd stops the timer and consoles the duration in milliseconds:

	console.time('testForEach');

// some code you need to measure in execution time

console.timeEnd('testForEach');

// 4522.303ms (or whatever time elapsed)

Passing a timer name as an argument allows you to manage multiple concurrent timers.

Console.table

Console.table is used to work with objects. It shows object in a form of a table, where you can sort data, which makes it very convenient to use.

	var languages = [
    { name: "JavaScript", fileExtension: ".js" },
    { name: "TypeScript", fileExtension: ".ts" },
    { name: "CoffeeScript", fileExtension: ".coffee" }
];

console.log(languages);

debugger

console.table(languages);

javascript debugger

Debugger

Debugger is a ‘quick’ debugging tool. Place it in your code, and your browser will automatically stop there when it’s executing. You can even wrap it in conditionals so it’s only run when you need it. All browsers support debugger.

You can use it like this:

	If (something) {
  debugger;
}

Or just put it in a code where you need to see the current state of environment.
For example let’s call debugger inside of a function to see the values of local and global scopes:

	var global1 = "global variable";
var global2 = 15;
var localFunction = function() {
  var local1 = "local variable";
  var local2 = global2;
  debugger;
}
localFunction();

This code execution will be paused where debugger was called and the browser will show us the scopes.

debugger

up

Please turn your device