How to effectively attach eventListeners to DOM elements

Anonymous functions are great way to quickly get the job done, but can be a huge problem when used carelessly. It is especially when attaching eventListeners to DOM elements. Take the code below for example. Here we are attaching an eventListner to the element for the “click” event. Now, later lets say for some reason or requirement we need to remove this “click” listener.


var element = document.getElementById("my-id");
element.addEventListener("click", function() {
// handle event
});

After this point we don’t have any reference to this handler since we forgot to keep a reference to it hence we cannot call the removeEventListener on the element, and the function stays in the browse memory.

Now assume the same scenario with multiple elements. An appropriate example of it might be when a NxN grid of elements is created. Lets assume that the cells in the grid is being created dynamically and appended to the DOM. If these cells need to have some event handler we might end-up doing the following:


var newCell = getNewCell(); // function to create a new cell

newCell.addEventListenet("click", function() {
// handle code
});

This way we ended up creating NxN anonymous functions to which we don’t have references to, in case event listeners need to be removed. Even after the element does not exist in the DOM, it is the browser to decide when to remove these abandoned listeners from its memory. But as a programmer its our job to clean up the mess we created.

How to fix this:

A better approach to add eventListeners to DOM elements would be to have some kind of reference to them as below:


function myListener(event, target) {
// handle the event
}

var newCell = getNewCell();

newCell.addEventListener("click", myListener);

Now whenever needed, this listener can to removed from the element:


newCell.removeEventListener("click", myListener);

Sometimes, there is a requirement of firing en event listener only once (for example an init button to be clicked for initializing the application). In that case, the addEventListener method can accept an optional object parameter with a Boolean property “once” to tell the browser that this listener needs to be fired only once and after that the listener needs to be removed. So in such cases adding anonymous function as eventListeners can be considered fine (but not with multiple elements as we are still creating several anonymous functions which will sit there untill the event occurs for the first time).


myElement.removeListener("click", function() {}, {capture: false, once: true, passive: true});

So it is quite essential to take precautions when attaching anonymous functions as eventListeners especially with multiple elements.

Advertisements

Constants in ECMA Script 6 and their implementations

Web browsers have already started providing ECMA Script 6 functionalities to the users,

BUT 

before utilizing these features there are few anomalies in the implementations in different web browsers, and one of them is constant. Constants were among the most awaited features in JavaScript. Below is a brief look at constants and their behavior in different browsers.

Lets say we need a variable(that does not change over time) in our application, for example the time zone of the user(since for a normal user the time zone does not change very frequently).

So ECMA Script6 provides the following construct to declare a constant:

const TIME_ZONE = "IST+0530";

Now later, somewhere in the code even if the TIME_ZONE is tried to be assigned new value,  it will still hold the initial value:

TIME_ZONE = "ABC+0500"; // does not work
console.log(TIME_ZONE);

prints:

"IST+0530"

In case of constants holding objects as values the properties of the object can be altered but the constant itself cannot be assigned any new object or value. Take a look at the snippet below of instance:

const CENTER = {x: 24, y: 50};

now the rule for assignment of new object or value stays same i.e.:

CENTER = {a: 10, b: 20};
console.log(CENTER);

will print

{x: 24, y: 50}

but the following can be done:

CENTER.x = 10;
CENTER.y = 20;

So the code below will output the object with updated values of its properties:

console.log(CENTER);
{x: 10, y: 20}

But if the same constant is tried to be re-written again in the same scope as below a TypeError is thrown saying that the specified constant has already been defined.

function abc () {
    const PI = 3.14; 
    const PI = 3.14159; // TypeError/re-declaration
    console.log(PI);
}

Anomalies in browser implementations

In case of re creating constant in different scopes different browsers may behave differently. For example, the following code will work in IE edge and firefox 44.0.2 but not in chrome 48.0.2564.109 m.

//Works in IE Edge and Firefox but not in chrome
function abc () {
 const PI = 3.14; 
 console.log(PI);
 {
     const PI = 3.14159;
     console.log(PI);
 }
 console.log(PI);
}

Output in firefox and IE Edge

3.14
3.14159
3.14

but in chrome

Uncaught SyntaxError: Identifier 'PI' has already been declared

So there are still few in consistencies among web browsers in the implementations on ECMA Script6 features and recommendations.

JavaScript Programmer ? Do you also ignore semicolon?

A little story of two JS Programmers ==> Alice and Bob

Bob: Hey Alice. You know what, I have learned JavaScript very well. It’s very easy and fun.

Alice: Good, but you still need to be careful while writing you JS program.

Bob: Are you kidding me ? Careful with JavaScript? Several times I escape putting the semicolons at the end of the statement and you know it still works like charm.

Alice: You must be kidding. You shouldn’t forget to put the semicolon after the statement.

Bob: I am serious, who wants to put that reoccurring and irritating semicolon.

Alice: Of course JS ignores such things at several places, BUT  it doesn’t mean you should abuse this behavior.

Bob: Why not? I can do the following and it works perfectly fine:

    console.log("Hello! I missed the semicolon.")
    console.log("Hello! I missed the semicolon too.")
Output:
    Hello! I missed the semicolon.
    Hello! I missed the semicolon too.

What harm does it do?

Alice: Well, I think nothing till now. Bob can you do me a favor? Can you just put both the statements in the same line as it is?

Bob: Sure!

    console.log("Hello! I missed the semicolon.") console.log("Hello! I missed the semicolon too.")

Output:

    SyntaxError: Unexpected identifier

Bob: What!!!!!!!!!??????????
Bob: But wait, putting two statements in the same line is itself not good.
Alice: Fine(probably he doesn’t know about minification). Ok tell me the purpose of this code:

    +("233434");

Bob: Its just a way to convert the string to number.
Alice: Exactly. Now what do you expect from of the following code written in your style:

    var a = 0
    var b = 1
    +("23456")

Bob: Its too straight forward: 0 is assigned to variable “a” and the value 1 is assigned to the variable “b”.
Alice: Are you sure?
Bob: Wait ! Wait ! Wait ! Wait !. Oh I see the problem.
Bob: Hmmmm! You are right I shouldn’t miss my best friend semicolon.

Better use of Memoization JavaScript

Referring to the post Generating Unique Strings (Ids) in JavaScript where we looked at how to generate unique strings in JavaScript. Also I mentioned the performance and memory issues with the approach.

So, here we are going to look a much better use or application of Memoization, but we won’t be using it for generating unique strings. We will be looking at another issue and will try to find an efficient way to handle it.

We all know that accessing DOM elements is pretty much costly in terms of performance. Lets look at an example of such an issue.

    function someFunction() {
        var text = document.getElementById("some-id").innerHTML;
        return text;
    }

Now suppose the above function is executed several times(reason could be anything like, user clicks). Every time this function is executed, it traverses the DOM and then returns it, which makes it really inefficient.
Now, I hope we have the idea of how to solve problem. Exactly, we will simply access the element one time and store it in a cache rather than looking into the DOM again and again.
So, here is how to do it:

    function someFunction() {
        if(someFunction.cachedElement) {
            return someFunction.cachedElement.innerHTML;
        } else {
            var ele = document.getElementById("some-id");
            someFunction.cachedElement = ele;
            return ele.innerHTML;
        }
    }

This technique can be more efficient if we are looking for multiple elements for example by className of the elements. In that case, we just need to get all the elements only once and store them in an array. So next time when the function is called all the elements are returned from the cache rather than going through whole DOM.
Pretty fast haan, isn’t it!

Generating Unique Strings (Ids) in JavaScript

Well, there are tools in JavaScript that allow you to create unique ids or strings.

I will be using to two ideas to create these unique strings.

Method 1: Memoization

The idea is to generate some random string and checking if it has already been generated the recursively generate another one and test again. If the generated string has not been already generated, then simply cache it somewhere and return it to the calling function.

Here is an example of it:

function getUniqueId() {
    getUniqueId.cache = getUniqueId.cache ? getUniqueId.cache : [];
    var randomString = "ID" + Math.random().toString().substr(2);
    if(getUniqueId.cache.indexOf(randomString) === -1) {
        getUniqueId.cache.push(randomString);
        return randomString;
    } else {
        return getUniqueId();
    }
}

Method 2: Closure
Closures are on of the coolest things available in JavaScript. Closures allow us to access local variables of a function when you are out of he scope of the function.
Doesn’t it sound a little crazy: “How can a local variable of a function can be accessed while we are not in the scope of that function i.e., the variable is out of the current scope!”.

Well its true that we cannot access the out of the scope variable of a function, But the variables and functions local to that variable can access it right. This is the concept we will be using to generate our unique Id or string.

Here it is.

    var getUniqueId = (function () {
        var local = 0;
        return function () {
            return local++;
        }
    })();

In the function above we are returning a reference to a local function, for which the variable local is in the scope and can still increment it. So every time we call the getUniqueId function actually the inner function also gets called and every time we get the incremented value.

Issues with these approaches:

Both of the above concepts are pretty cool to use, but you know nothing comes for free. Both have some performance and memory(space) issues with them.

If we carefully analyze the situation when we use closures, we are accessing an out of scope function and we all know that accessing out of scope variables is much costlier as compared to accessing local variables and functions in terms of performance as JavaScript engine has to search for the variable which is sitting far-far away from the current scope.
The situations worsens if the function has been defined in the global scope, because we have to go all the way to the global scope to get it.

Now if we take a look at the Memoization approach, we will see as the list of cached items increases, the function will tend to slow down. This slowness is because of the fact that before returning the the generated string, it has to traverse the whole cache. This slowness increases as the size of the cache grows.

There are some memory issues too with theses approaches because we in case of Memoization are maintaining a cache and in case of Closure the JS engine has to preserve a whole scope and its members.

So using these tool with care is necessary or our program would run into other issues.

PS: Lets get lazy a bit
Now here is the laziest(and easiest) way to generate unique strings:
 
Date/Time

function getUniqueId(padding) {
   return padding + (new Date()).getTime();
}

 
I hope this last function is very self explainatory.