There is a lot of fun to be had when working in JavaScript. Even for developers that interact with it daily, some parts of the language remain unexplored. I’m going to highlight seven things you may not know about JavaScript.
Note: I am going to use ES2015 in the examples: pretty much the let keyword (you can replace it with var if you don’t have an interpreter with ES2015 support) and arrow functions. Alternatively, you can babelify the code automagically.
1. There Are Two Zeros
Usually, we use the positive value of zero. But there is a -0 as well! Because of the way how they are stringified, you won’t see them in the console:
+0 -> 0 -0 -> 0
That’s because both (-0).toString() and (+0).toString() return 0.
Going a bit deeper, we find out that they are actually equal:
+0 === -0 -> true +0 > -0 -> false +0 < -0 -> false
Also, if we try to use indexOf over arrays that’s not going to help too much (since indexOf does use strict equal: ===):
// We would expect 1, right? [+0, -0, 42].indexOf(-0) -> 0 [-0, +0, 42].indexOf(+0) -> 0
There is a way, actually, to find out if a value is negative zero: by dividing a positive and finite number to that value:
42 / 0 -> Infinity 42 / -0 -> -Infinity
So, we can simply say:
// Two conditions: // 1. The input parameter should be zero (negative or positive) // 2. Dividing a number by that input, we should get a negative value (-Infinity) let isNegativeZero = input => input === 0 && 1 / input < 0; // Let's test it isNegativeZero(0) -> false isNegativeZero(+0) -> false isNegativeZero(-0) -> true
2. NaN (Not a Number) Is a Special Number
Yes, NaN is a number:
typeof NaN -> "number"
Thre are a few ways we can obtain it:
- 0 / 0 (divide 0 by 0)
- +’foo’ (convert a non-number string into number)
- Infinity – Infinity
- …and many more.
Truth is that it is a special number.
NaN does not equal itself.
If you ever saw a thing like if (x !== x) {…}, now you’ll understand what was going on there.
In short, NaN does not equal NaN!
NaN === NaN -> false // Even by storing the value in a variable let x = NaN x === x -> false
A side effect is that you’ll never use indexOf if you want to find out the index of a NaN value into an array:
let values = [7, NaN, 42]; // Find the index of 42 values.indexOf(42); -> 2 // Find the index of NaN (WRONG!) values.indexOf(NaN) -> -1 // If you really want to find the index of NaN, simply create your indexOf function let myIndexOf = (arr, value, start) => { if (value !== value) { start = start || 0; for (let i = start; i < arr.length; ++i) { if (arr[i] !== arr[i]) { return i; } } return -1; } return arr.indexOf(value, start); }; // Now, it will work! myIndexOf(values, NaN) -> 1
Note you can use isNaN(x) instead of checking if x !== x to find out if x is NaN, but that’s a little bit slower.
But why is that? The easy answer is that this behaviour is dictated by IEEE 754:
Every NaN shall compare unordered with everything, including itself.
Tip: If you’re using ES2015, includes handles the NaN case:
[42, NaN].includes(NaN) -> true
NaN is not (in)finite.
NaN is not a finite number. That does not mean it is infinite. It’s simply not finite or infinite.
// Check if it's finite isFinite(NaN) -> false // Comparing with infinity, it will always give us false Infinity > NaN -> false > Infinity < NaN -> false -Infinity < NaN -> false > -Infinity > NaN -> false
NaN is neither positive or negative.
NaN is NaN. There are no +NaN or -NaN. Both are NaN:
NaN -> NaN -NaN -> NaN +NaN -> NaN
3. Use of Bitwise Operators
This is quite language-agnostic, but since the bitwise operators are supported in JavaScript I’m going to show you some nice stuff using them.
Fast multiplication/division of integers
The use-case could be when you want to render some complex 3D animation on a big canvas. You want to generate and render the frames as quick as possible. Let’s assume you need some math there (surely you do).
When you want to multiply/divide something by 2, 4, 8, 16 or any other power of 2, the trick is to use the bitwise operators which move the bits of the numbers to the right (division) or to the left (multiplication).
// Same with 21 * 2, but faster 21 << 1 -> 42 // Same with 5 * 4 5 << 2 -> 20
The << operator moves all the bits to the left one position. The numbers are represented in a binary form, therefore it adds a new 0 on the right side:
// 5 in base 2: 101 101 << 1 -> 1010 (which is 10 in base 2)
Similar things happen when using the >> operator.
84 >> 1 -> 42
From my testing, using this << is about 1.04 times faster than using the * operator. Not sure if you want to sacrifice the human-readable code for such a small speed difference. Maybe. Anyway, it’s good to know.
Send encrypted messages
The XOR (^) operator is used in cryptography. You can encrypt and decrypt messages using it. Here is how it works:
A B ^ ========= 0 0 0 0 1 1 1 0 1 1 1 0
A simple example would be encrypting and decrypting a number:
// Alice and Bob share the same secret key: let key = 123; // Alice wants to send Bob a number let msg = 42; // But before sending it, Alice encrypts it: msg = msg ^ key // or directly: msg ^= key -> 81 // Bob receives 45, but knowing the key is 123 he knows to decrypt it: 81 ^ key -> 42 // Now Bob can enjoy the message from Alice
Find if an element is part of an array
Using the bitwise NOT (~) operator we can check easily if an element appears in an array or not.
let fruits = ["apple", "pear", "orange"]; // Usually they do it like this if (fruits.indexOf("pear") !== -1) { ... } // That works, but using the `~` makes the code shorter if (~fruits.indexOf("pear")) { ... }
This operator turns -1 into 0 (because of its internal binary representation, turning 0 into 1 and 1 into 0), while for other numbers it returns non-zero results. Since 0 is a false value (!!0 -> false), that’s why it won’t enter in that if condition.
Tip: If you’re using ES2015, includes will do the job:
["pear", "apple"].includes("apple") -> true
4. Represent Strings By Using Hex/Unicode Code
If you want, for some weird reason, to hide a string in the code, without calling any functions, you can do that natively. Use wither hexadecimal or unicode escape sequences:
// Hex: '2a' (in base 16) is 42--representing the ASCII code of '*' 'x2a' -> '*' // Unicode: it expects 4 hex digits 'u002a' -> '*'
Let’s create a simple function that is supposed to convert human-readable messages:
let strToHex = input => input.split('').map( // Convert each character into its hex code c => `x${c.charCodeAt(0).toString(16)}` ).join(''); // Let's convert something. strToHex('hello world!') -> '\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21' // Once we have the above output, we just have to remove the escaping: $ echo '\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21' | awk 'gsub("\\\\", "\")' -> x68x65x6cx6cx6fx20x77x6fx72x6cx64x21 // And finally, we have it! x68x65x6cx6cx6fx20x77x6fx72x6cx64x21 -> 'hello world!'
5. Short-Circuiting Binary Logical Operators
In a binary expression, the second operand is not evaluated if the first one met the result conditions. Let’s take a look at some examples:
true && 'hello' -> 'hello' // We won't get 'hello', because 'false && anything else' will be always false false && 'hello' -> false // Let's try a logical or false || 'hello' -> 'hello' // When we have more than two, they are interpreted in the obvious way (from left to right) 0 && false && 'hello' -> 0 42 && false && 'hello' -> false 42 && true && 'hello' -> 'hello'
Now, the nice thing is that same is applied for functions.
// Get some random number let rand = () => Math.random() + 1; // Let's create an object to collect some data let data = {}; // A function to assign some random numbers to that specific key, only the first time let add = key => !data[key] && (data[key] = rand()) || data[key]; // Assign a random number to 'a' add('a') -> 1.0398168717659242 // Reassigning won't work, but we get back the existing value add('a') -> 1.0398168717659242 // Do the same for 'b' add('b') -> 1.4267915083378722 // And for 'c' add('c') -> 1.495289329665785 // Let's see how the data looks like internally: // Seems we do have a clean key-value map { a: 1.0398168717659242, b: 1.4267915083378722, c: 1.495289329665785 }
Obviously, the magic is in the add function. Let’s expand this:
// Let's see what's going on here let add = key => !data[key] && (data[key] = rand()) || data[key]; // Looks more human-readable, but it's longer :D add = key => { // If the value is not yet set... if (!data[key]) { // set it! data[key] = rand(); } // Always, do return the value return data[key]; };
6. Running Eval in Strict Mode Is Not That Bad
Yes, eval is evil, but maybe it has some features which can help you in your mad-science projects.
When running eval in strict mode, it doesn’t let you create variables in the surrounding scope. Eval simply interprets and executes the code you pass in:
let x = 35; // Sum x + 7 let result = eval('x + 7'); -> 42 // You can declare variables there (we are NOT in the strict mode): eval('var y = x + 7'); console.log(y); -> 42
Now, when we enable the strict mode, the second eval call above will still work, but will not create the y variable outside of the eval environment:
"use strict"; let x = 35; eval('var y = x + 7'); // ^ // ReferenceError: y is not defined console.log(y);
7. Create Functions Dynamically
We can define dynamic functions using the new Function constructor.
let square = new Function('x', 'return x * x'); // Let's see how it looks like: console.log(square.toString()); function anonymous(x /**/) { return x*x } square(4) -> 16
One use-case when this is used is in templating libraries (such as ejs, ajs etc) which parse the template once and return a function that accepts the template data in it.
Note: You should not use eval or new Function for cases like parsing JSON data, getting the value of a dynamic object key.
Hopefully, you learned something new or at least got a better understanding of what is going on with these JavaScript gems. What other unexplored/uncommon JavaScript features do you know? Share them in the comments.
7 Comments