This article was originally published on Live Code Stream by Juan Cruz Martinez (twitter: @bajcmartinez), founder and publisher of Live Code Stream, entrepreneur, developer, author, speaker, and doer of things.

If you work long enough in JavaScript, you're guaranteed to come across some very strange behaviors.

Nobody normal ever accomplished anything meaningful in this world. — Jonathan, Stranger Things

Oh course, even though JavaScript may occasionally throw surprises at you, it's a very powerful language that's worthy of some investigation of its mysteries. So in this article, I will explain some of its strange code snippets so that you can add their behaviors to your toolbox. This language can be a weirdo, but we love it!

Scenario #1: [‘1’, ‘7’, ‘11’].map(parseInt)

Let's take a look at the code for our first scenario.


['1', '7', '11'].map(parseInt);

For what you would expect the output to be:


[1, 7, 11]

However, things get a bit off here and the actual result is:


[1,NaN,3]

At first, this may look very weird, but it actually has an elegant explanation. To understand what is going on, we need to understand the two involved functions, map and parseInt.

map()

map() calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values (including undefined).
Now the callback function referenced above will receive some particular parameters. Let’s take a look at an example with its output:

[1, 2, 3].map(console.log)
1 0 > (3) [1, 2, 3]
2 1 > (3) [1, 2, 3]
3 2 > (3) [1, 2, 3]


As we can see, the map function not only did pass the value of the item but also the index and a copy of the full array on each iteration. This is important and is in part what’s affecting our previous result.

parseInt()

The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).
So now, by definition, parseInt(string [, radix]) expects two parameters: the string we want to parse and the radix.

Resolving the mystery

Now that we know enough about the two functions, let’s try to understand what is happening in our case. We will start with our original script and we will explain it step-by-step:


['1', '7', '11'].map(parseInt);
As we know, the callback for the map function will receive three arguments, so let’s do that:

['1', '7', '11'].map((currentValue, index, array) => parseInt(currentValue, index, array));

Starting to get an idea of what happened? When we add the arguments, it comes clear that the parseInt function is receiving additional parameters and not only the actual value of the item in the array. So now we can test what the function would do for each of these value combinations, but we can also ignore the array parameter as it will be discarded by the parseInt function:


parseInt('1', 0
1

parseInt('7', 1
NaN

parseInt('11', 2
3
So that now explains the values we saw initially. The parseInt function result is being altered by the redix parameter which determines the base for the conversion.

Is there a way to get the originally expected result?

Now that we know how it works, we can easily fix our script and get the desired result:


['1', '7', '11'].map((currentValue) => parseInt(currentValue));
> (3) [1, 7, 11]


Scenario #2: (‘b'+'a'+ + ‘a’ + ‘a’).toLowerCase() === ‘banana’

You may be thinking that the expression above is false. After all, there is no letter ‘n’ in the string we are building on the left side of the expression, or isn’t it?

Let’s find out:


('b'+'a'+ + 'a' + 'a').toLowerCase() === 'banana'
true

Ok, you probably realized already what is going on, but if not let me quickly explain it here. Let’s focus on the left side of the expression. There’s nothing weird on the right side, believe me.


('b'+'a'+ + 'a' + 'a').toLowerCase()
"banana"

Somehow, we're forming the word ‘banana’. Let’s remove the lower case conversion and see what happens:


('b'+'a'+ + 'a' + 'a')
"baNaNa"
Bingo! We found some ‘N’ now, and looks like we actually found a NaN inside the string. Perhaps it’s coming from the + + expression. Let’s pretend that and see what we would get:

b + a + NaN + a + a
Hmm, we have an extra a, so let’s try something else:

+ + 'a'
NaN
Ahh there we go… the + + operation by itself is not evaluating, but when we add the character ‘a’ at the end, it all goes into NaN, and now fits into our code. The NaN expression is then concatenated as a string with the rest of the text and we finally get banana. Pretty weird!

Scenario #3: Can’t even name it


(![] + [])[+[]] +  
	(![] + [])[+!+[]] +  
  ([![]] + [][[]])[+!+[] + [+[]]] +  
  (![] + [])[!+[] + !+[]] === 'fail'

What in the world? How does a bunch of brackets form the word fail? (And no, JS is not failing. We are actually getting the string 'fail' as the output.)

Let’s try to explain it. There are a few things in that bunch that form a pattern.


(![] + [])
That pattern evaluates to the string false, which is strange, but it's a property of the language. Turns out that the false + [] === 'false' transformation has to do with how JS internally maps the calls. We won’t get into the details as to why this exactly happens.
Once you form the string false the rest is easy. Just look for the positions of the letters that you need — except for one case, the letter i, which is not part of the word false.
For a variation, let’s look at it ([![]] + [][[]]), which evaluates to the string falseundefined. So basically we force an undefined value and concatenate it to the false string.

Loving it so far? Let’s do some more.

Scenario #4: To be truthy or to be true, that is the question.

To be truthy or to be true, that is the question. To be falsy or to be false, that is the question.

What is truthy and falsy? And why are they different from true or false?

Every value in JavaScript has its own boolean value (truthy/falsy). These values are used in operations where a boolean is expected but not given. It's very likely you've once done something like this:


const array = [];
if (array) {  
	console.log('Truthy!');
}
In the code above, array is not a boolean even though the value is “truthy” and the expression will result in executing the console.log below.

How do I know what is truthy and what is falsy?

Everything that is not falsy is truthy. Terrible explanation? Fair enough, let’s examine it further.

Falsy values care values with an inherit boolean false. For example, values like:
  • 0
  • -0
  • 0n
  • '’ or "”
  • null
  • undefined
  • NaN
  • Everything else would be truthy.

    Scenario #5: Array equality

    Some things in JS are simply weird. It's the way the language is design and we accept it the way it is. Let’s see some weird array equalities:

    
    [] == ''   // -> true
    [] == 0    // -> true
    [''] == '' // -> true
    [0] == 0   // -> true
    [0] == ''  // -> false
    [''] == 0  // -> true
    
    [null] == ''      // true
    [null] == 0       // true
    [undefined] == '' // true
    [undefined] == 0  // true
    
    [[]] == 0  // true
    [[]] == '' // true
    
    [[[[[[]]]]]] == '' // true
    [[[[[[]]]]]] == 0  // true
    
    [[[[[[ null ]]]]]] == 0  // true
    [[[[[[ null ]]]]]] == '' // true
    
    [[[[[[ undefined ]]]]]] == 0  // true
    [[[[[[ undefined ]]]]]] == '' // true
    

    If you are interested in why all of the above ... is, you can read more about that in section 7.2.13 Abstract Equality Comparison of the specification. Though I have to warn you that it's not for the easily startled :p

    Scenario #6: Math is Math, unless….

    In our real world, we know that math is math and we know how it works. We were taught since childhood how to add numbers and that whenever you sum the same two numbers together you will get the same result, right? Well… for JavaScript this is not always true… or kind of… let’s see it:

    
    3  - 1  // -> 2
    3  + 1  // -> 4
    '3' - 1  // -> 2
    '3' + 1  // -> '31'
    
    '' + '' // -> ''
    [] + [] // -> ''
    {} + [] // -> 0
    [] + {} // -> '[object Object]'
    {} + {} // -> '[object Object][object Object]'
    
    '222' - -'111' // -> 333
    
    [4] * [4]       // -> 16
    [] * []         // -> 0
    [4, 4] * [4, 4] // NaN
    

    All looked good until we got to:

    
    '3' - 1  // -> 2
    '3' + 1  // -> '31'
    

    When we subtracted, the string and the number interacted as numbers. But during the addition, both acted as strings. Why? Well… as maddening as it might seem, JS is designed to things this way. Luckily, there’s a simple table that will help you understand how the language handles each case like this:

    
    Number  + Number  -> addition
    Boolean + Number  -> addition
    Boolean + Boolean -> addition
    Number  + String  -> concatenation
    String  + Boolean -> concatenation
    String  + String  -> concatenation
    
    What about the other examples? The ToPrimitive and ToString methods are being implicitly called for [] and {} before addition. Read more about evaluation process in the specification:
  • 12.8.3 The Addition Operator (+)
  • 7.1.1 ToPrimitive(input [,PreferredType])
  • 7.1.12 ToString(argument)
  • 
    {  
    	// a code block here
     }
     +[]; // -> 0
    
    To get the same output as [] + {}, we can wrap it in parenthesis.
    
    ({} + []); // -> [object Object]
    

    Conclusion

    Again, JavaScript is an amazing language — it just happens to be one that's got some tricks and weirdness. I hope this article brings you some clarity into some of these interesting quirks and that next time you encounter something like them you can more easily figure out what exactly is happening.

    And by the way, since what we went over above are only some of the situations under which JS can be very weird, I'm officially leaving the door open for a Strange Things in JavaScript Part 2 blog.

    Juan’s Live Code Stream writing can be sent to you as a free weekly newsletter. Sign up for updates on everything related to programming, AI, and computer science in general.

    Discussion

    Categories
    Share Article

    Continue Reading