DEV Community

What is the oddest JavaScript behavior?

Ben Halpern on August 29, 2019

JavaScript is notorious for its inconsistencies in a lot of areas. It's also powerful and popular and has a lot going for it. But can we poke fun ...
Collapse
 
peiche profile image
Paul
'11' + 1 // 111
'11' - 1 // 10
Enter fullscreen mode Exit fullscreen mode
Collapse
 
xanozoid profile image
XANOZOID • Edited

I love this. It just made me think about something pretty interesting... If you think of this in terms of what it actually could mean for a mathematical function, then the string appendage (regarding integers) could be thought of as its own math operation too - in a very round about way.

Collapse
 
alephnaught2tog profile image
Max Cerrina

Absolutely, and not even in a very roundabout way, you're pretty spot on :)

Collapse
 
leob profile image
leob • Edited

Question: should we (as JS coders) assume that people know this, and not hesitate to write this sort of code? or should we refrain from this and use explicit type conversion ...

(well the answer is probably that if it is 100% clear that the data type of the first operand is always string and the second operand is always number then this would be okay - if in obscure corner cases it can sometimes suddenly be different then good luck debugging/troubleshooting ...)

Collapse
 
ahkohd profile image
Victor Aremu

😂😂

Collapse
 
jacobmgevans profile image
Jacob Evans

JS coercion at its best and worst in two lines lmao!

Collapse
 
bradtaniguchi profile image
Brad • Edited

This took an hour of my life:

typeof null === 'object' //true
Enter fullscreen mode Exit fullscreen mode
Collapse
 
eliasmqz profile image
Anthony Marquez

omg thank you for your service as I really didn’t know about this.

Collapse
 
willsmart profile image
willsmart • Edited

yep, a good check for an actual object is

thing && typeof(thing)==='object'

Kind of insane they did that that.

Collapse
 
sleeplessbyte profile image
Derk-Jan Karrenbeld • Edited

It's not insane. null, like nil in ruby, a singleton instance of NilClass is technically an object.

Technically an array is also an object, which is why there is Array.isArray (which has its own caveats).

Thread Thread
 
willsmart profile image
willsmart • Edited

But at least ruby is rigorously consistent OO like that.

irb(main):004:0> nil.methods
=> [:&, :^, :|, :===, :inspect, :to_a, :to_s, :to_i, :to_f, :nil?, :to_h, :to_r, :rationalize, :to_c, :tap, :public_send, :instance_variables, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :is_a?, :instance_variable_get, :method, :public_method, :singleton_method, :instance_of?, :extend, :define_singleton_method, :to_enum, :enum_for, :<=>, :=~, :!~, :eql?, :respond_to?, :freeze, :object_id, :display, :send, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

In JS there are very few object-type calls that work in any way with nulls, so it's neat that they aimed to make this aspect of the language more OO, but they should have followed through better.
typeof(null) being 'object' is seldom anything but annoying for JS devs.

eg.

> Object.getOwnPropertyNames(null)
< Uncaught TypeError: Cannot convert undefined or null to object
    at Function.getOwnPropertyNames (<anonymous>)
    at <anonymous>:1:8
> null.foo
< Uncaught TypeError: Cannot read property 'foo' of null
    at <anonymous>:1:6
(anonymous) @ VM176:1
> for (const k of null);
< Uncaught TypeError: null is not iterable
    at <anonymous>:1:17

Basically there are very few times you might write typeof(thing)==='object' where you shouldn't do the null check too

Thread Thread
 
sleeplessbyte profile image
Derk-Jan Karrenbeld

Absolutely true. I was only referring to the use of the word "insane" which just sounds very unknowing/ignorant to me.

FWIW, now that we use TS for most things, this is not problem for us often, except for when something is nullable ánd multiple types (object/number/string/undefined).

My recommendation is to never use null and only use undefined. Replace your "null"s with an "EMPTY/UN_SET" data type.

Thread Thread
 
willsmart profile image
willsmart

Fair enough. I didn't mean insane in the sense of completely without reason, just in the sense that it was badly thought out.

The thing is that nulls are now baked into JS at a few levels, so in our own code we can avoid them, but they pop up throughout the various JS apis, eg

> /a/.exec('b')
< null
> document.body.getAttribute('a')
< null

That's what I mean by the loose "insane" comment. It was a mistake that was bad enough that as JS devs we're now best off to generally avoid the fundamental null value in JS.

I totally agree with you about TS, which corrects many of these issues, and how in JS we're best to use undefined whereever possible.

Collapse
 
brianemilius profile image
Brian Emilius

Or
Object.prototype.toString.call(thing)

Collapse
 
xowap profile image
Rémy 🤖

Without a doubt I'll say this: jsfuck.com/

Collapse
 
ohryan profile image
Ryan

My work firewall thinks this is porn.

Collapse
 
yorodm profile image
Yoandy Rodriguez Martinez

Ryan, HR needs to now why you're searching for programmers porn during work hours

Thread Thread
 
ohryan profile image
Ryan

Clearly :D

Collapse
 
deciduously profile image
Ben Lovy

check out the source for a full list of the nonsense they leverage. I feel like "explain each line of this file to yourself" is probably a pretty good JS litmus test.

Collapse
 
kspeakman profile image
Kasey Speakman

Wat is a funny talk about odd behaviors. JS starts at 1:22.

Collapse
 
alexanderholman profile image
Alexander Holman • Edited

A colleague the other day noticed that the setTimeout/setInterval takes a 32 bit signed int as an argument, which is fine except that (as far as I know) JS doesn't natively support integers... but but instead Numbers are specifically 8 byte signed doubles. If you happen to have a interval or timeout every 2^31 miliseconds (ish) or more, then the value is interpreted as a negative number and therefore executes instantly and in terms of intervals repeatedly. So timeouts and intervals are limited to just under 25 days.

Collapse
 
shayd16 profile image
Shayne Darren

I'm guessing a web app won't need a 25-day timeout or interval.

I tried a very basic test on node and it seems to be waiting on it to execute... any idea why?

Collapse
 
alexanderholman profile image
Alexander Holman • Edited

not sure... i just ran it in node and experienced the same issue as the browser. The following line should make node print without pausing:

setInterval(() => {console.log('log')}, 2**31)

or

setInterval(() => {console.log('log')}, -1)

where the following will take 596.5 hours (roughly):

setInterval(() => {console.log('log')}, 2**31-1)

I am currently on a Windows machine running v8.9.1, perhaps its solved in later versions?

Collapse
 
sebastian_scholl profile image
Seb Scholl

My favorites...

alert(typeof NaN); //alerts 'Number'
alert(NaN === NaN); //evaluates false
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ahferroin7 profile image
Austin S. Hemmelgarn

Most languages do this, but it could be a whole lot worse (fun fact, IEEE 754 actually allows for (2 ^ N) - 1 distinct NaN values not counting signed NaN values, where N is the bit width of the significand field in the binary representation (so for the 8-byte floats JS uses, there are 18014398509481982 possible NaN values including both signed values)).

The reason is simple, NaN is a numeric representation of a non-numeric value, so by definition it has to be the same type as numbers for the language (or, at minimum, it has to be at least one of the numeric types in the language), but you can't be certain what value it is, so you can't treat two NaN values as being identical.

Collapse
 
clarity89 profile image
Alex K. • Edited

I think most of the people get confused by the language's type coercion rules and operations related to those, which are quite often not intuitive:

0 == []            // true
[] == false        // true
[] + 1             // "1"
[] + "1"           // "1"
[] - 1             // -1
[] - "1"           // -1
{} + 1             // 1 
{} + "1"           // 1
{} - 1             // -1
{} - "1"           // -1
{} + []            // 0
{} == []           // Uncaught SyntaxError: Unexpected token ==
[] == {}           // false
Collapse
 
blindfish3 profile image
Ben Calder • Edited

The only thing that surprises me here are the loose equality comparisons between object and array; but it's not as though any sane programmer would be doing this. Otherwise almost all the others look fairly intuitive: coercion is applied to satisfy the operator being applied.

It's perhaps more useful to understand all the values that can be coerced to false; and why sometimes it's a really bad idea to use a shortcut if(isSomeValueTruthy) style condition...

edit - actually [] + 1 is weird :D

Collapse
 
ahferroin7 profile image
Austin S. Hemmelgarn

Yeah, this is a good one. Most languages try to coerce only one side of an expression at a time, and won't ever coerce collection types to non-collection types.

Collapse
 
jwkicklighter profile image
Jordan Kicklighter

Wow, this is a new one for me. The array coercion actually caught me off guard.

Collapse
 
dwd profile image
Dave Cridland • Edited

Scoping rules with (or without) var are the weirdest:


// Prints the value of a.
function printA() {
  console.log(a);
}

// Prints '1'
function print1() {
  var a = 1;
  printA();
}

// Prints '2'
function print2() {
  var a = 2;
  printA();
}

// Prints whatever the value of b in the caller's scope is.
function printB() {
  a = b;
  printA();
}

// Prints 3.
function print3() {
  var b = 3;
  printB();
}

// Print 4, then 5.
function print45() {
  var b = 4;
  var a = 5;
  printB();
  printA();
  // Ha, no it doesn't, it prints 4 twice, and now:
  assert(a === 4);
}

Collapse
 
johnylab profile image
João Ferreira

loved

Collapse
 
mellen profile image
Matt Ellen

you have just blown my tiny mind

Collapse
 
dwd profile image
Dave Cridland

Of course, I made a mistake on it (now corrected), but still. If you want to see something batshit insane with scoping and hoisting, you can try and get your head around this:

function fib(n) {
  if (n) {
    a = 1;
    b = 0;
    c = n + 1;
  }
  if (c === 1) {
    return a + b;
  }
  c = c - 1;
  let d = a + b;
  b = a;
  a = d;
  return fib();
}
Thread Thread
 
mellen profile image
Matt Ellen

😭

Collapse
 
antjanus profile image
Antonin J. (they/them)

Encountered this one yesterday and spent a day on it.

A File object cannot be serialized into JSON. There's a StackOverflow question about it.

But essentially if you do

console.log(someFile);

The browser will happily return something like:

File {
  name: 'filename',
  size: 1898921,
  // other properties
}

But doing

console.log(JSON.stringify(someFile))

will result in:

{}

With no properties. I've noticed that it does this for object spread as well so if you wanted to copy File info, you can't just do: { ...someFile }, you'll need to call and pick out the individual properties manually.

Collapse
 
ahkohd profile image
Victor Aremu

This made my day 😂

Collapse
 
lukewestby profile image
Luke Westby • Edited
typeof document.all === 'undefined' // true
document.all instanceof HTMLAllCollection // true
Collapse
 
kenbellows profile image
Ken Bellows

Imo the implicit type conversion is a little weird, but even ignoring that, I think the point was the difference in behavior between '11' + 1, which converts the number to a string, and '11' - 1, which converts the string to a number. This discrepancy makes implicit type casting feel unpredictable and weird, at noon least to me

Collapse
 
deleteman123 profile image
Fernando Doglio

Wouldn't you say that most of the "strange" behavior is actually associated with people not caring enough to read the actual specs? I mean, the example you listed is perfectly explained in the answers. It's not strange, it behaves as expected if you read the specs.
Same goes for a lot of the comments in this thread, especially those related to data types. If I write a spec for a new language saying that:

typeof 1 == number
typeof 2 == string

No matter how crazy that looks from the outside, if my language's inner logic makes sense, then it's completely valid and not crazy.

I guess what I'm trying to say here is that people need to start seeing JavaScript for the very special boy it is and stop trying to compare it with the rest of the class. It's different, but we love it anyway (at least I know I do :P)

Collapse
 
camdhall profile image
CamDHall

I don't really agree with this at all. Most of my experience is in JavaScript and I've read most of the specs if not all, at one time or another. It doesn't mean these things make "sense". Just because I know WHY it does dumb stuff, doesn't mean it's not dumb. Like, I know why people would try to rob a bank. Doesn't mean it's a perfectly rationale thing to do.

Collapse
 
deleteman123 profile image
Fernando Doglio

Well, the concept of "dumb" requires a context and to be compared against similar actions with different results. Thus you are taking into account expected behavior from others, even if you don't do it consciously. It's the same with your example: robbing a bank is bad, even if you know why they do it, because, in your context, you follow the law but then again, your context isn't the same as that of the robbers.
That is all I'm trying to say, the context here is everything. If we say JS's behavior is "dumb" because in other languages typeof NaN (or its equivalent) should never be "number", then we're taking things out of context. The ECMAScript standard states that Numbers should be IEEE-754 floating point data. This includes Infinity, -Infinity, and also NaN.

I don't mean to start a flame war here, so I hope I'm not offending anyone, all I'm just saying is that before claiming JS is stupid, dumb or behaving strangly, we should consider what we're comparing it against. That's all.

Thread Thread
 
camdhall profile image
CamDHall • Edited

I started programming in JS. I spent probably 5 or 6 years working only with JS. But even before I started working with C# and C++, I thought the way JS handles things was clearly not the best way of handling them.

I can go further on the banking example. Even if I didn't follow the law, robbing a bank is not a smart move. There's easier targets. Less risky targets. Probably even targets that pay all most as well, with far less chance of being caught. So it's still pretty stupid, even in the context of being a criminal. I don't know a lot of thiefs, but I suspect if you ask a bunch of thiefs if robbing a bank is a good idea, they'd tell you no. Rob some poor guy leaving the atm or something.

JavaScript could definitely handle types better. Among other things. Even in the context of what it's used for. Typescript is maybe a good example of this.

I'm also not being malicious or anything. I Like JS, it's still what most of my side projects are in, even though I could choose to use something else. But it has problems. Some of which are self imposed.

Collapse
 
lepinekong profile image
lepinekong • Edited

From Deming / System Thinking: youtube.com/watch?v=OqEeIG8aPPk

A system must have an aim. What's the aim ;) Making the right things (good spec relative to that aim) is far more important than doing the things right (respect a spec) and that's all the problem in software industry in general: the problem is more about how to do the right product (so spec) than to do a product that respects a spec but people won't buy ;)

Doing a spec for a programming language often targets the machine first instead of human first, that should be reverse ;)

Collapse
 
jwkicklighter profile image
Jordan Kicklighter

I mostly agree, however I would add that there were some less-than-ideal design decisions made earlier in the language's life that most people agree should be modified, but must remain so as to not break comparability. Which is a remarkable challenge.

Collapse
 
antontsvil profile image
Anton T

The "everything is an object" approach is takes is such a double edged sword. On one hand it's nice to use index access for objects but typeof [] being object is just silly if you're coming from another language.

I had a stubborn bug where an array was refusing to be identified by Array.isArray in my code yet if I tried it in the console it would work. Even recreating it by inserting it into a blank array using spread syntax like [...myArray] still failed the isArray test :/

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

There is one that I can't remember precisely, but JavaScript cannot add 2.637 or something like that the result is wrong. I will have to look this up.

Collapse
 
keptoman profile image
mlaj

Adding floats. It won't give the right answer. It's a common problem in programming languages.

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Yeah I had a suspension that was the case. Still it's odd and it doesn't not affect JavaScript so I'm technically correct. 🙃

Collapse
 
kspeakman profile image
Kasey Speakman • Edited
console.log(.3);
// 0.3

console.log(.1 + .2);
// 0.30000000000000004
Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

That's the one! Thanks!

Collapse
 
titi profile image
Thibault ROHMER
Math.min() < Math.max() // false

eheh

This is why: ecma-international.org/ecma-262/5....

max: If no arguments are given, the result is −∞.
min: If no arguments are given, the result is +∞.

Math.min() === Infinity // true
Math.max() === -Infinity // true

Thus

Math.min() < Math.max()
// is the same as
Infinity < -Infinity
// which is false obviously

Now, could you say why

Math.min < Math.max // false
Collapse
 
jamesmcguigan profile image
James McGuigan

I think this was designed for the more common usecase of search algorithms looking for new minimum or maximum values.

You will always be greater than the max of an empty list, and always less than the min of an empty list, without needing to check for the edgecase of an empty list.

seen_values = [];
[1,-2,3,-5,4].foreach((value) => {
  if( value > Math.max(seen_values) ) { 
    onNewMaxValue(value); 
  }
  if( value < Math.min(seen_values) ) { 
    onNewMinValue(value); 
  }
  seen_values.push( value )
})
Collapse
 
antogarand profile image
Antony Garand
Collapse
 
willsmart profile image
willsmart • Edited

It makes sense why, but I think it's a pity that

0 === -0

AFAIK it's the only instance where you can have two quite different values that are to all intensive purposes considered identical. They even have effectively identical signs via Math.sign.
You have to do a hack like

1/-0 !== 1/0

just to tell them apart.

Collapse
 
metacritical profile image
Pankaj Doharey • Edited

const a = Array(3).fill([null,null,null] )

// Is not the same as 

const b =  [[null,null, null],[null,null, null],[null,null, null]]

/* because if you try to fill an element in one array in the first example 
it fills all the array with the same element */

a[1][2] = "X";

//This results in the following.

/* 
=> 

[[null, null, "X"]
 [null, null, "X"]
 [null, null, "X"]] /*


/* Aah freaking javascript. */

Collapse
 
camdhall profile image
CamDHall • Edited

I ran into this issue last night. I understood my brain fart of a problem pretty quickly, but sometimes surprising things aren't necessarily new lol.

this.data = await RequestHandler('url-i-was-hitting');

console.log(this.data); // returns undefined

var temp = await RequestHandler('url-i-was-hitting');

console.log(temp) // json I was expecting
 
sleeplessbyte profile image
Derk-Jan Karrenbeld

It does not

Collapse
 
j_mplourde profile image
Jean-Michel Plourde • Edited

I find these very funny:

typeof(1 + '1') // returns a string because + is overloaded to concatenate

typeof(1 * '1') // but not *

"1" -- "1" // returns 2

"1" - "1" // returns 0

Collapse
 
clarity89 profile image
Alex K.

NaN is actually a number and it's not JavaScript specific:

Collapse
 
bergamin profile image
Guilherme Taffarel Bergamin

The weirdest thing for me in JS is the fact that you will only know if something works at runtime because there isn't any compilation.

This brings weird stuff like the undefined state. It is basically telling you that you are using something that doesn't exist. You know it should exist at that point, but because of some mistake somewhere you cannot identify because there is no compilation, you will have to check if that shit is undefined before checking if it is null before checking if it is empty.

Yes, I'm new to JS and I know you can test any variable as if they were Boolean to check for undefined and null, but I think that's not readable enough, so I would rather create a function with another name that checks for that.

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

This is batshit don't you think?

Collapse
 
masoneg profile image
MasonEG • Edited

Array.prototype.push("lol");
let broken = [];
console.log(broken[0]);

Collapse
 
nateous profile image
Nate

Ben thanks for this! Over the years I've loved the quirks of JavaScript. It's a totally different way of coding, and I like it as an option. I'll stick to C# if I want true OO.

My favorite two have always been the guard clause (objNullRef && objNullRef.prop) and the default clause (objNullRef || fallBackObj).

Long live JavaScript!

Love these other comments, you guys are great!

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

That it works.

...

Yes, I'm entirely kidding. I'm known as a critic of the language, but I wouldn't consider my expertise to be sufficient to add anything of value here, besides a laugh! Rock on, JS devs.

Collapse
 
netmask profile image
Jonathan Garay

Does JS support operator overloading?

Collapse
 
brandonmack111 profile image
brandonmack111

I'd say definitely this:

(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
Collapse
 
sebastian_scholl profile image
Seb Scholl

I totally get the idea lol. I just find it funny, almost as if it were the following conversation...

"What are you?" => "Sebastian!"
"Are you Sebastian!" => "Nope!"

Collapse
 
daniel12fsp profile image
daniel12fsp • Edited

this is semantic wrong but it's valid....

isNaN(undefined)  // true

// Correct way

Number.isNaN(undefined) //false
Collapse
 
antogarand profile image
Antony Garand

This makes sense if you assume isNaN means is Not a number, and not is the nan value!

Collapse
 
vimmer9 profile image
Damir Franusic

The ability to persevere 😄

Collapse
 
kelvinblaze profile image
Ifeanyi Kelvin Ossai

A string and a number to be precise.

Collapse
 
uriell profile image
Uriell • Edited
0.0.toString() !== "0.0"

this one left me wondering quite a while why my API was returning null:

JSON.stringify(NaN) // null
Collapse
 
nijeesh4all profile image
Nijeesh Joshy

I observed a weird thing with js and started an discussion on it

dev.to/nijeesh4all/why-two-small-f...

Thanks to @da-ti for the explanation

Collapse
 
elmuerte profile image
Michiel Hendriks

this is

Collapse
 
ben profile image
Ben Halpern

Obligatory

Collapse
 
damuz91 profile image
David Muñoz • Edited

This took hours of my life, i want them back!

"somestring".includes('') // true
Enter fullscreen mode Exit fullscreen mode
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
visualmov profile image
Ryan

I've never programmed in JavaScript in my life, and these oddities make me scared to ever try.

Collapse
 
peiche profile image
Paul

You miss 100% of the shots you never take.

Collapse
 
daniel12fsp profile image
daniel12fsp
isNaN(undefined)  // true

// Correct way

Number.isNaN(undefined) //false
Collapse
 
rafi993 profile image
Rafi

Will JS engines ever manage to get rid of these weird behaviors? and make it more consistent slowly.

Collapse
 
eliasmqz profile image
Anthony Marquez

I understand somewhat your take, however these showcases of “unexpected” behaviors can’t be explained away with just being tribalism.

Collapse
 
fluffynuts profile image
Davyd McColl

I <3 Javascript, but: destroyallsoftware.com/talks/wat :D

Collapse
 
cdanielsen profile image
Christian Danielsen
Collapse
 
flexdinesh profile image
Dinesh Pandiyan

"11" + 1 = 111
"11" - 1 = 10

JavaScript is definitely drunk