DEV Community

Cover image for 5 interesting and not-necessarily-useful Javascript tricks
Arek Nawo
Arek Nawo

Posted on • Edited on • Originally published at areknawo.com

5 interesting and not-necessarily-useful Javascript tricks

This post was taken from my blog, so be sure to check it out for more up-to-date content.

A while ago, I've created a post titled "7 adorable web development tricks". There, I described some interesting tricks that you could pull off using one of 3 major web technologies - HTML, CSS, and JavaScript. And, I must admit - you guys seemed to like it! And so, here comes the sequel!

This time, to be a bit more consistent, I decided to focus solely on JavaScript. It's probably the most interesting and versatile out of the 3, so there's a lot to talk about. We'll go over 5 JavaScript tricks, that weren't mentioned in the previous article. I hope you'll find them interesting!

A quick note before we get into the list. I saw some replies to the previous post and would like to clear something up. I know that not all entries on this or the previous list might be truly useful or a recommended practice, but that's not my goal. By the word "trick" I mean just that - a "trick" that's interesting or worth knowing just for the sake of it. Usefulness is just a bonus. If it was meant to be 100% useful, then I'd call it a "tip". I hope you understand. Now, let's go to the list!

5. Switch with ranges

Starting with the less "extreme" trick, we've got the switch statement. Most of its use-cases come down to string or numeric value matching. But, did you know that you can use it with some more complex boolean values too? Take a look.

const useSwitch = value => {
  let message = "";

  switch (true) {
    case value < 0:
      message = "lesser than 0";
      break;

    case value === 0:
      message = "0";
      break;

    case value % 1 !== 0:
      message = "a float";
      break;

    case value > 0 && value <= 9:
      message = "higher than 0 and is 1 digit long";
      break;

    case value >= 10 && value <= 99:
      message = "2 digits long";
      break;

    case value >= 100:
      message = "big";
      break;
  }

  console.log(`The value is ${message}`);
};

useSwitch(24); // The value is 2 digits long.
Enter fullscreen mode Exit fullscreen mode

Instead of providing the switch statement with an actual variable, we're simply passing true. This way, we essentially make it one big if-else alternative. If you should use it depends solely on your personal preference or the code guidelines you're following. If you find it more readable than an if-else chain, go for it. The performance of both solutions is about the same.

4. Stringify a function

Next up we have something that’s not really a trick by itself. Stringifying a function is a feature that you've most likely known about for a long time now. Instead, I wanted to let you know of some interesting use-cases for this kind of functionality.

const func = () => {
  console.log("This is a function");
}
const stringifiedFunc = `${func}`; /* `() => {
  console.log("This is a function");
}` */
Enter fullscreen mode Exit fullscreen mode

Starting with a quick look at the syntax side. You should know that when you convert a value of any JS type to a string, a special .toString() method is invoked. You can use this fact to implement your own version of this method and handle converting your JS value to a string differently. This can be considered a trick on its own. ;) Anyway, the point I wanted to make is that you can convert your values (including functions) to a string with ES6 template literals (like in the example), by concatenating them with an empty string literal (""), or just by calling the .toString() method directly.

Now, let's get back to functions. I wanted to note that you cannot depend on the result string to contain all the code of your function as it was written. For example, it's only from ES2019 (currently the latest revision of ECMAScript standard), that .toString() is meant to include all the comments and whitespaces inside the function's body in the resulting string. You can read more about ES2019 features in one of my previous articles. Anyway, with all this in mind, how stringifying a function can be even useful?

Not to search too far, I'd like to reference a neat trick that I've used in one of my recent side-projects. Imagine, that there is a kind of nodes that can be created by calling a function. This function takes another function as a parameter, which is then run to configure the new node. The resulting nodes are the same for functions that consist of the same statements.

Sadly, creating new nodes is a slow process (especially when considering large quantities of them), and you'd like to at least minimize the number of nodes being created. To do this, you can e.g. create a "cache" object, where you'd store all the already created nodes by their stringified config function, to prevent any repetitive calls - interesting, huh?

Of course, the stringified function-based IDs would be considered different even with a tiny whitespace or a comment. You could fix it with some additional string processing, but that would neglect all the performance improvements that we're trying to achieve.

However, you shouldn't tolerate object keys being as long as the config functions are. You can easily solve this issue by simply hashing the stringified function - it shouldn't cost you a lot performance-wise.

// ...
const hash = value => {
  let hashed = 0;

  for (let i = 0; i < value.length; i += 1) {
    hashed = (hashed << 5) - hashed + value.charCodeAt(i);
    hashed |= 0;
  }

  return `${hashed}`;
};
const hashedFunc = hash(stringifiedFunc); // "-1627423388"
Enter fullscreen mode Exit fullscreen mode

I know that what I've just described might seem a bit too specific to be applied to more general use-cases. Surely that's somewhat true, but I just wanted to give you a real-world example of possibilities that tricks like this one give you.

3. Callable objects

A callable object, a function with properties, or whatever you want to call it is a fairly simple idea that demonstrates the versatility of JavaScript pretty well.

const func = () => {
  // ...
};
func.prop = "value";
console.log(func.prop); // "value"
Enter fullscreen mode Exit fullscreen mode

The snippet above shouldn't seem any special to you. You can save own properties on pretty much any JS objects, unless it's indicated otherwise with the use of .freeze(), .seal(), or the .preventExtensions() method. The function above can now be used both as a usual function, but also as an object containing some sort of data.

The code snippet above doesn't look very polished though. Assigning properties to the given function can start to feel repetitive and messy with time. Let's try to change that!

const func = Object.assign(() => {
    // ...
}, {
  prop: "value"
});
console.log(func.prop); // "value"
Enter fullscreen mode Exit fullscreen mode

Now we're using the Object.assign() method to make our code look better. Of course, this method is available only in ES6-compatible environments (or with a transpiler), but, as we're also utilizing arrow functions here, I just take it for granted.

2. Lexically-bound class methods

Let's say that we've got a class with a lot of fields and methods. You can imagine yourself in such a situation, don't you? What if, at the given moment, you only need a small subset of all the class properties and methods? Maybe you could use the ES6 destructuring assignment to make your code look better? Sadly, it's not that easy - take a look.

class Example {
  method() {
    console.log(this);
  }
}

const instance = new Example();
const { method } = instance;

method(); // undefined
Enter fullscreen mode Exit fullscreen mode

As you can see, after we extracted our method, the value of this changed to undefined. That's expected behavior - the value of this is runtime-bound and determined by the way and place that your function was called in. I discussed this in my previous post.

There's a way around, however - .bind().

class Example {
    constructor() {
        this.method = this.method.bind(this);
    }
  method() {
    console.log(this);
  }
}

const instance = new Example();
const { method } = instance;

method(); // Example {}
Enter fullscreen mode Exit fullscreen mode

Now our code works as intended, though it required the addition of the class constructor, and thus a few more lines of code. Can we make it shorter?

class Example {
  method = () => {
    console.log(this);
  }
}
// ...
Enter fullscreen mode Exit fullscreen mode

It seems like we've done it! A short and easy way to have lexically-bound methods inside your classes. The syntax above works in the latest ever-green browsers and can be transpiled if necessary, so enjoy!

1. Return from constructor

The last entry on this list is also connected with classes. You might have heard of the possibility of returning custom values from the constructor. It's not a very popular or recommended practice, but it allows you to achieve some interesting results. Remember the previous example of cached nodes that I brought up before? Let's build on that!

// ...
const cache = {};

class Node {
  constructor(config) {
    const id = hash(`${config}`);

    if (cache[id]) {
      return cache[id];
    } else {
      cache[id] = this;
      config();
    }
  }
}

const node = new Node(() => {});
const nodeReference = new Node(() => {});
const secondNode = new Node(() => {
  console.log("Hello");
});

console.log(node === nodeReference, node === secondNode); // true, false
Enter fullscreen mode Exit fullscreen mode

Our node now has a form of a class, and like before, it can be cached with the use of stringified & hashed config function. How nice to see all the pieces coming together!

Something new?

So, that's it for this list. I know that it's not the longest one that you've seen, but hey, at least I managed to get you interested, right? Anyway, let me know in the comments section about which of the above tricks you didn't know? Also down there you can share your opinions on such a type of article and if you'd like to see more of them. ;)

So, if you like this post, consider sharing this post and following me on Twitter, Facebook or Reddit to stay up-to-date with the latest content. As always, thank you for reading this, and I wish you a happy day!

Top comments (7)

Collapse
 
adam_cyclones profile image
Adam Crockett πŸŒ€

I respect this but respectfully it's all horrible.

The one I like because it makes me think, is the switch, what if we didn't have if statements, or better yet, what if statements where expressions too.

The bind destructure, that's strange because you create a copy when you destructure, the rebind make me think is it even worth it? And the lambda class that's not even valid yet?

Collapse
 
areknawo profile image
Arek Nawo

That’s why I call them tricks.

Collapse
 
adam_cyclones profile image
Adam Crockett πŸŒ€

I meant no offense if any was taken.

Thread Thread
 
areknawo profile image
Arek Nawo

No, no - you get me wrong. I understand your opinion and mostly agree with it. No offense. Maybe it’s the dot at the end of the sentence. πŸ˜…

Thread Thread
 
adam_cyclones profile image
Adam Crockett πŸŒ€

How dare you correctly format a sentence haha!

Collapse
 
stevematdavies profile image
Stephen Matthew Davies

They all seem a bit involved and overly bloated. I'm not sure I can see the usefulness of the stringifying a function, the explanation to me at least, is unclear. How would one unhash the function and use it if needed, using apply? for example, which in iteslf is not a great practice.

I do enjoy reading such Vanilla JS practices, since we all do get caught up in libraries and frameworks which do a lot of this heavy lifting for us, it can be easy to ignore what is going on under the hood. However, isn't this the point, so we can avoid overly verbose implementations?

Nice read all the same.

Collapse
 
areknawo profile image
Arek Nawo

Yeah, the use for stringifying function is a bit hard to explain. However, I’ve used it when creating my own framework, so there’s that.