This is a very quick post about trying to fix a JavaScript bug that plagued me for an hour this morning.
tl;dr I was tripped up by a rogue unary operator and slap-dash copy-pasting.
The setup
On an existing web page, there was some JavaScript that builds up a string to insert into the DOM:
function GetTemplate(url, html)
{
// other details removed
var template = '<div class="something"><a href="'
+ url
+ '" target="_blank"><strong>Details: </strong><span>'
+ html
+ '</span></a></div>';
return template;
}
Ignore for now whether this code is an abomination and the vulnerabilities etc - it is what it is.
The requirement was simple: insert an optional additional <span>
tag before the <strong>
, only if the value of a variable provided is 'truthy'. Seems pretty easy right? It should have been.
The first attempt
I set about quickly rolling out the fix and came up with code something like this:
function GetTemplate(url, html, summary) {
// other details removed
var template = '<div class="something"><a href="'
+ url
+ '" target="_blank">';
if(summary) {
template += '<span class="summary">'
+ summary
+ '</span>';
}
template +=
+'<strong>Details: </strong><span>'
+ html
+ '</span></a></div>';
return template;
}
All looked ok to me, F5 to reload the page, and… oh dear, that doesn't look right…
Can you spot what I did wrong?
The HTML that was generated looked like this:
<div class="something"><a href="https://thewebsite.com" target="blank">
<span class="summary">The summary</span>NaNThis is the inner message</span></a>
</div>
Spotted it yet?
Concatenation vs addition
Looking at the generated HTML, there appears to be a Rogue "NaN"
string that has found it's way into the generated HTML and there's also no sign of the <strong>
tag in the output. The presence of the NaN
was a pretty clear indicator that there was some conversion to numbers going on here, but I couldn't for the life of me see where!
As I'm sure you all know, in JavaScript the +
symbol can be used both for numeric addition and string concatenation, depending on the variables either side. For example,
console.log('value:' + 3); // 'value:3'
console.log(3 + 1); // 4
console.log('value:' + 3 + '+' + 1); // 'value:3+1'
console.log('value:' + 3 + 1); // 'value:31'
console.log('value:' + (3 + 1)); // 'value:4'
console.log(3 + ' is the value'); // '3 is the value'
In these examples, when either the left or right operands of the +
symbol are a string, the other operand is coerced to a string and a concatenation is performed. Otherwise the operator is treated as an addition.
The presence of the NaN
in the output string indicated there must be some something going on where a string was trying to be used as a number. But given the concatenation rules above, and the fact we weren't using parseInt()
or similar anywhere, it just didn't make any sense!
The culprit
Narrowing the problem down, the issue appeared to be in the third block of string concatenation, in which the strong tag is added:
template +=
+'<strong>Details: </strong><span>'
+ html
+ '</span></a></div>';
If you still haven't spotted it, writing it all one line may do the trick for you:
template += +'<strong>Details: </strong><span>' + html + '</span></a></div>';
Right at the beginning of that statement I am calling 'string' += +'string'
. See the extra +
that's crept in through copying and pasting errors? That's the source of all my woes - a unary operation. To quote the You Don't Know JS book by Kyle Simpson:
+c
here is showing the unary operator form (operator with only one operand) of the+
operator. Instead of performing mathematic addition (or string concatenation -- see below), the unary+
explicitly coerces its operand (c
) to anumber
value.This has an interesting effect on the subsequent string in that it tries to convert it to a number.
This was the exact problem I had. The rogue +
was attempting to convert the string <strong>Details: </strong><span>
to a number, was failing and returning NaN
. This was then coerced to a string as a result of the subsequent concatenations, and broke my HTML! Removing that +
fixed everything.
Bonus
As an interesting side point to this, I was using gulp-uglify
to minify the resulting javascript as part of the build. As part of that minification, the 'unary operator plus value' combination (+'<strong>Details: </strong><span>'
) was actually being stored in the minified javascript as an explicit NaN
. Gulp had seen my error and set it in stone for me!
I'm sure there's a lesson to be learnt here about not rushing and copying and pasting, but my immediate thought was for a small gulp plugin that warns you about unexpected NaN
s in your minified code! I wouldn't be surprised if that already exists…