JavaScript Not Working? Here are 10 Common JavaScript Problems

Editor’s note: This article was updated by our editorial team on January 18, 2023. It has been modified to include recent sources and to align with our current editorial standards.

Today, JavaScript is at the core of virtually all modern web applications. That’s why JavaScript issues, and finding the mistakes that cause them, are at the forefront for web developers.

Powerful JavaScript-based libraries and frameworks for single page application (SPA) development, graphics and animation, and server-side JavaScript platforms are nothing new. JavaScript has become ubiquitous in the world of web app development and is therefore an increasingly important skill to master.

At first, JavaScript may seem quite simple. Indeed, building basic JavaScript functionality into a web page is a fairly straightforward task for any experienced software developer, even if they’re new to JavaScript. Yet the language is significantly more nuanced, powerful, and complex than one would initially believe. In fact, many of JavaScript’s subtleties can lead to a number of common problems that keep it from working—10 of which we discuss here. It is important to be aware of and avoid these issues on your journey to become a master JavaScript developer.

JavaScript Issue No. 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 confusion” causing JavaScript issues.

Consider this example code snippet:

const Game = function() { this.clearLocalStorage = function() { console.log("Clearing local storage..."); }; this.clearBoard = function() { console.log("Clearing board..."); };
}; Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); }, 0);
}; const myGame = new Game();
myGame.restart();

Executing the above code results in the following error:

Uncaught TypeError: this.clearBoard is not a function

Why? It’s all about context. The reason you get that 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 clearBoard() 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.:

Game.prototype.restart = function () { this.clearLocalStorage(); const self = this; this.timer = setTimeout(function(){ self.clearBoard(); }, 0);
};

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

Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); }; Game.prototype.reset = function(){ this.clearBoard();    
};

JavaScript Issue No. 2: Thinking There Is Block-level Scope

As discussed in our JavaScript Hiring Guide, a common source of confusion among JavaScript developers (and therefore a common source of bugs) is assuming that JavaScript creates a new scope for each code block. Although this is true in many other languages, it is not true in JavaScript. Consider, for example, the following code:

for (var i = 0; i < 10; i++) { }
console.log(i);  

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 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.

JavaScript Issue No. 3: Creating Memory Leaks

Memory leaks are almost inevitable issues in JavaScript if you’re not consciously coding to avoid them. There are numerous ways for them to occur, so we’ll just highlight two of their more common occurrences.

Memory Leak Example 1: Dangling References to Defunct Objects

Note: This example applies to legacy JavaScript engines only—modern ones have garbage collectors (GCs) that are smart enough to handle this case.

Consider the following code:

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

If you run the above code and monitor memory usage, you’ll find that you’ve got a significant memory leak—a full megabyte per second! And even a manual garbage collector doesn’t help. So it looks like we are leaking longStr every time replaceThing is called. But why?

Memory leaks are almost inevitable issues in JavaScript if you’re not consciously coding to avoid them.

Let’s examine things in more detail:

Each theThing object contains its own 1MB longStr object. Every second, when we call replaceThing, it holds on to a reference to the prior theThing object in priorThing. But we still wouldn’t think this would be a problem, since, each time through, the previously referenced priorThing would be dereferenced (when priorThing is reset via priorThing = theThing;). Moreover, it is only referenced in the main body of replaceThing and in the function unused, which is, in fact, never used.

So again we’re left wondering why there is a memory leak here.

To understand what’s going on, we need to better understand the inner workings of JavaScript. Closures are typically implemented by every function object linking to a dictionary-style object representing its lexical scope. If both functions defined inside replaceThing actually used priorThing, it would be important that they both get the same object, even if priorThing gets assigned over and over so that both functions share the same lexical environment. But as soon as a variable is used by any closure, it ends up in the lexical environment shared by all closures in that scope. And that little nuance is what leads to this gnarly memory leak.

Memory Leak Example 2: Circular References

Consider this code fragment:

function addClickHandler(element) { element.click = function onClick(e) { alert("Clicked the " + element.nodeName)
    }
}

Here, onClick has a closure that keeps a reference to element (via element.nodeName). By also assigning onClick to element.click, the circular reference is created, i.e., elementonClickelementonClickelement

Interestingly, even if element is removed from the DOM, the circular self-reference above would prevent element and onClick from being collected and thus would become a memory leak.

Avoiding Memory Leaks: The Essentials

JavaScript’s memory management (and, in particular, its garbage collection) is largely based on the notion of object reachability.

The following objects are assumed to be reachable and are known as “roots”:

  • Objects referenced from anywhere in the current call stack (that is, all local variables and parameters in the functions currently being invoked, and all the variables in the closure scope)
  • All global variables

Objects are kept in memory at least as long as they are accessible from any of the roots through a reference or a chain of references.

There is a garbage collector in the browser that cleans memory occupied by unreachable objects; in other words, objects will be removed from memory if and only if the GC believes that they are unreachable. Unfortunately, it’s fairly easy to end up with defunct “zombie” objects that are no longer in use but that the GC still thinks are reachable.

JavaScript Issue No. 4: Confusion About Equality

One JavaScript convenience is that it will automatically coerce any value being referenced in a boolean context to a boolean value. But there are cases in which this can be as confusing as it is convenient. The following expressions, for example, are known to be troublesome for many a JavaScript developer:


console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0); if ({}) if ([]) 

With regard to the last two, despite being empty (which might lead you 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 as clear as mud. Accordingly, unless type coercion is explicitly desired, it’s typically best to use === and !== (rather than == and !=) 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.)

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); console.log(NaN === NaN); console.log(isNaN(NaN));    

JavaScript Issue No. 5: Inefficient DOM Manipulation

JavaScript makes it relatively easy to manipulate the DOM (i.e., add, modify, and remove elements), but does nothing to promote doing so efficiently.

A common example is code that adds a series of DOM elements one at a time. Adding a DOM element is an expensive operation, and code that adds multiple DOM elements consecutively is inefficient and likely not to work well.

One effective alternative when multiple DOM elements need to be added is to use document fragments instead, which will improve efficiency and performance.

For example:

const div = document.getElementById("my_div");
const fragment = document.createDocumentFragment();
const elems = document.querySelectorAll('a'); for (let e = 0; e < elems.length; e++) { fragment.appendChild(elems[e]);
}
div.appendChild(fragment.cloneNode(true));

In addition to the inherently improved efficiency of this approach, creating attached DOM elements is expensive, whereas creating and modifying them while detached and then attaching them yields much better performance.

Consider this code:

var elements = document.getElementsByTagName('input');
var n = elements.length; for (var i = 0; i < n; i++) { elements[i].onclick = function() { console.log("This is element #" + i);
    };
}

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 this JavaScript problem to achieve the desired behavior:

var elements = document.getElementsByTagName('input');
var n = elements.length; 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);
}

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).

JavaScript Issue No. 7: Failure to Properly Leverage Prototypal Inheritance

A surprisingly high number of JavaScript developers fail to fully understand, and therefore fully leverage, the features of prototypal inheritance.

Here’s a simple example:

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

This 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); console.log(secondObj.name); 

But what if we were to do this:

delete secondObj.name;

We’d then get:

console.log(secondObj.name); 

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';

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. 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); delete thirdObj.name;
console.log(thirdObj.name);  

JavaScript Issue No. 8: Creating Incorrect References to Instance Methods

Let’s define a simple object, and create an instance of it, as follows:

var MyObjectFactory = function() {} MyObjectFactory.prototype.whoAmI = function() { console.log(this);
}; var obj = new MyObjectFactory();

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;

And just to be sure we’ve stored a reference to a function, let’s print out the value of our new whoAmI variable:

console.log(whoAmI);

Outputs:

function () { console.log(this);
}

It looks fine so far.

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

obj.whoAmI(); whoAmI();      

What went wrong? Our whoAmI() call is in the global namespace, so this gets set to window (or, in strict mode, to undefined), not to the obj instance of MyObjectFactory! In other words, the value of this normally depends on the calling context.

Arrow functions ((params) => {} instead of function(params) {} provide a static this that is not based on the calling context like this is for regular functions. This gives us a workaround:

var MyFactoryWithStaticThis = function() { this.whoAmI = () => { console.log(this); };
} var objWithStaticThis = new MyFactoryWithStaticThis();
var whoAmIWithStaticThis = objWithStaticThis.whoAmI; objWithStaticThis.whoAmI(); whoAmIWithStaticThis();      

You may have noticed that, even though we got the output to match, this is a reference to the factory, rather than to the instance. Rather than trying to fix this issue further, it’s worth considering approaches to JavaScript that don’t rely on this (or even new) at all, as explained in As a JS Developer, This Is What Keeps Me Up at Night.

JavaScript Issue No. 9: Providing a String As the First Argument to setTimeout or setInterval

For starters, let’s be clear on something here: Providing a string as the first argument to setTimeout or setInterval is not itself a mistake per se. It is perfectly legitimate JavaScript code. The issue here is more one of performance and efficiency. What is often overlooked is that if you pass in a string as the first argument to setTimeout or setInterval, it will be passed to the function constructor to be converted into a new function. This process can be slow and inefficient, and is rarely necessary.

The alternative to passing a string as the first argument to these methods is to instead pass in a function. Let’s look at an example.

Here, then, would be a fairly typical use of setInterval and setTimeout, passing a string as the first parameter:

setInterval("logTime()", 1000);
setTimeout("logMessage('" + msgValue + "')", 1000);

The better choice would be to pass in a function as the initial argument, e.g.:

setInterval(logTime, 1000); setTimeout(function() { logMessage(msgValue); }, 1000);

JavaScript Issue No. 10: Failure to Use “Strict Mode”

As explained in our JavaScript Hiring Guide, “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 a way to make your code more secure.

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

Here are some key benefits of strict mode:

  • 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 code base and directing you more quickly to their source.
  • 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.
  • Eliminates this coercion. Without strict mode, a reference to a this value of null or undefined is automatically coerced to the globalThis variable. This can cause many frustrating bugs. In strict mode, referencing a this value of null or undefined throws an error.
  • 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 significant time tracking down.
  • Makes eval() safer. 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.)
  • Throws an 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.

Mitigating JavaScript Issues With a Smarter Approach

As is true with any technology, the better you understand why and how JavaScript works and doesn’t work, the more solid your code will be and the more you’ll be able to effectively harness the true power of the language. Conversely, a lack of proper understanding of JavaScript paradigms and concepts is where many JavaScript problems lie.Thoroughly familiarizing yourself with the language’s nuances and subtleties is the most effective strategy for improving your proficiency and increasing your productivity.

Home - Wiki
Copyright © 2011-2024 iteam. Current version is 2.134.0. UTC+08:00, 2024-09-29 00:19
浙ICP备14020137号-1 $Map of visitor$