Understand the Hazards of Using Imperative Code

Understand the Hazards of Using Imperative Code

Lesser-Known JavaScript Hazards. Arrow functions and object literals, bindings, shallow sets, and more

JavaScript has been getting a lot of new, sugary features ever since we got over Harmony. While more features can allow us to write readable, high-quality code, it’s also easy to go overboard with what’s new and shiny and run into some of the potential pitfalls.

Let’s go over some of the gotchas I see come up quite frequently as sources of confusion, both new and old.

Arrow Functions and Object Literals

Arrow functions provide a terser and shorter syntax, one of the features available being that you can write your function as a lambda expression with an implicit return value. This comes in handy for functional-style code, like when you have to use mapping arrays using a function. That would be quite a few empty lines with regular functions.

For example:

const numbers = [1, 2, 3, 4];
numbers.map(function(n) {
  return n * n;
});

This becomes a sleek, easy-to-read one-liner with the lambda style arrow functions:

const numbers = [1, 2, 3, 4];
numbers.map(n => n * n);

This use case of an arrow function will work as one would expect: It multiplies the values by itself and returns to a new array containing [1, 4, 9, 16].

However, if you try mapping into objects, the syntax isn’t what one might intuitively expect it to be. For example, let’s say we’re trying to map our numbers into an array of objects containing the value like this:

const numbers = [1, 2, 3, 4];
numbers.map(n => { value: n });

The result here will actually be an array containing undefined values. While it might look like we are returning an object here, the interpreter sees something completely different. The curly braces are being interpreted as the block scope of the arrow function, and the value statement actually ends up being a label. If we were to extrapolate the above arrow function into what the interpreter actually ends up executing, it would look something like this:

const numbers = [1, 2, 3, 4];
numbers.map(function(n) {
  value:
  n
  return;
});

The workaround is quite subtle. We just need to wrap the object in parentheses, which turns it into an expression instead of a block statement, like this:

const numbers = [1, 2, 3, 4];
numbers.map(n => ({ value: n }));

This will evaluate to an array containing an array of objects with the values that one would expect.

Arrow Functions and Bindings

Another caveat with arrow functions is that they don’t have their own this binding, meaning their this value will be the same as the this value of the enclosing lexical scope.

So despite the syntax being arguably sleeker, arrow functions are not a replacement for good old functions. You can quickly run into situations where your this binding is not what you thought it was.

For example:

let calculator = {
  value: 0,
  add: (values) => {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
calculator.add([1, 2, 3]);
console.log(calculator.value);

While one might expect the this binding here to be the calculator object there, it will actually result in this being either undefined or the global object depending on whether the code is running in strict mode or not. This is because the closest lexical scope here is the global scope. In strict mode, that is undefined; otherwise, it’s the window object in browsers (or the process object in a Node.js compatible environment).

Regular functions do have a this binding. When called on an object, this will point at the object, so using a regular function is still the way to go for member functions.

let calculator = {
  value: 0,
  add(values) {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
calculator.add([10, 10]);
console.log(calculator.value);

Also, since an arrow function has no this binding, [Function.prototype.call](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind), and [Function.prototype.apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) won’t work with them either. The this binding is set in stone when the arrow function was declared, and can’t change.

So in the following example, we’ll run into the same issue as we had earlier: The this binding is the global object when the adder’s add function is called, despite our attempt to override it with Function.prototype.call:

const adder = {
  add: (values) => {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
let calculator = {
  value: 0
};
adder.add.call(calculator, [1, 2, 3]);

Arrow functions are neat, but they can’t replace regular member functions where a this binding is needed.

Automatic Semicolon Insertion

While it’s not a new feature, automatic semicolon insertion (ASI) is one of the weirder features in JavaScript, so it’s worth a mention. In theory, you can omit semicolons most of the time (which many projects do). If the project has a precedent, you should follow that, but you do, however, need to be aware that ASI is a feature, or you’ll end up having code that can be deceiving.

Take the following example:

return
{
  value: 42
}

One might think it would return the object literal, but it will actually return undefined because semicolon insertion takes place, making it an empty return statement followed by a block statement and a label statement.

In other words, the final code that is actually being interpreted looks more like the following:

return;
{
  value: 42
};

As a rule of thumb, never start a line with an opening brace, bracket, or template string literal, even when using semicolons because ASI always takes place.

Shallow Sets

Sets are shallow, meaning duplicate arrays and objects with the same values, which will lead to multiple entries in the set.

For example:

let set = new Set();
set.add([1, 2, 3]);
set.add([1, 2, 3]);
console.log(set.length);

The size of that set will be two, which makes sense if you think of it in terms of references as they are different objects.

Strings are immutable, however. Take the example of multiple strings in a set like this:

let set = new Set();
set.add([1, 2, 3].join(','));
set.add([1, 2, 3].join(','));
console.log(set.size);

This will end up with the set having a size of one because strings are immutable and interned in JavaScript, which can be used as a workaround if you find yourself needing to store a set of objects. One could serialize and de-serialize them instead.

Classes and the Temporal Dead Zone

In JavaScript, regular functions get hoisted to the top of the lexical scope, meaning the example below will work as one might expect:

let segment = new Segment();
function Segment() {
  this.x = 0;
  this.y = 0;
}

But the same is not true for classes. Classes are actually not hoisted and need to be fully defined in the lexical scope before you attempt to use them.

For example:

let segment = new Segment();
class Segment {
  constructor() {
    this.x = 0;
    this.y = 0;
  }
}

This will result in a ReferenceError when trying to construct a new instance of the class because they aren’t hoisted like functions are.

Finally

Finally is a bit of a special case. Take a look at the following snippet:

try {
  return true;
} finally {
  return false;
}

What value would you think it returns? The answer is both intuitive and at the same time can become unintuitive. One could think the first return statement makes the function actually return and pop the call stack. But this is the exception to that rule because Finally statements are always run so the return statement inside the Finally block returns instead.

In Conclusion

JavaScript is easy to learn but hard to master. In other words, it’s error-prone unless a developer is careful about what and why they’re doing something.

This is especially true with ECMAScript 6 and its sugary features. Arrow functions, in particular, come up all of the time. If I was to make a guess, I’d say it’s because developers see them as being prettier than regular functions. But they’re not regular functions and they can’t replace them.

Skimming the specification from time to time doesn’t hurt. It’s not the most exciting document in the world, but as far as specifications go, it’s not that bad.

Tools like the AST Explorer also help in shedding some light on what is going on in some of these corner cases. Humans and computers tend to parse things differently.

With that said, I’ll leave you with this final example as an exercise.

javascript code programming

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Who Else Wants to Write Clean JavaScript Code?

Who Else Wants to Write Clean JavaScript Code? 7 Tips to Make Your Coworkers Fall in Love With Your Code.

Top 15 Simple Coding Techniques to Get Your Tasks Done with Shorter Code in JavaScript

Don’t waste time writing long code while you can make it short, yet clearer and easier to read. In this Javascript tutorial, we'll discuss 15 Simple Coding Techniques to Get Your Tasks Done with Shorter Code in JavaScript

The Code that Runs Your JavaScript Code

How often do you think about the environment that your code runs in, and the resources that are available to it when it runs? Understanding the ‘runtime’ environment can help us make better choices as JavaScript developers when writing code.

How to start writing less error-prone code in JavaScript

How to start writing less error-prone code in JavaScript - Everything in JavaScript is an object!’. We said that this assertion is false. Many things in JavaScript can behave like an object, but that doesn’t mean it’s the object. We can say we have three types of objects (objects, functions and arrays) in JavaScript.

13 Free/Low-Cost Sites to Supercharge Your Programming Self-Education

Although we still talk about programming as a standalone career, the dominance of technology in our lives makes it clear that coding is much more than a career path. In my opinion, computer science is more than a college major or a high-paid job; it’s a skill, essential for thriving in a modern-day economy. Whether you work in healthcare, marketing, business, or other fields, you will see more coding and have to deal with a growing number of technologies throughout your entire life.