Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Stranger Things, JavaScript Edition (livecodestream.dev)
73 points by bajcmartinez on June 3, 2020 | hide | past | favorite | 53 comments


This code snippets are the JavaScript equivalent of the Obsfucated C contest: amusing for insiders, a source of smug satisfaction for language warriors, and ultimately of no consequence to day-to-day practitioners.

There is a relatively small list of "gotchas" in JavaScript that stem from the early days of the language. They are easily avoided. Use a linter, "use strict," use ===, and be explicit about your type conversions.


> easily avoided

Not always. Don't forget about this, arrow functions and the fact that popular mocking frameworks can automatically mock regular class methods but not arrow methods.


I have a bit of a hard time with you there, I like to use maps in my code and trying to map a parser over a list of strings and getting the wrong answer is far from 'of no consequence' to me.

I'm not saying these issues make the language unusable, but you've got to be a bit more honest that you were if you want to be taken seriously.


"If I want to be taken seriously?" Was that bit of petty oneupmanship really necessary? I assure you, Alex, I'm completely unconcerned with whether you take me seriously.

You were probably just trying to show off. But in case you weren't, here's how JS works.

In JavaScript, if you're passing in a function as a parameter, the normal way is to use an arrow function. Like this: `array.map((element) => yourFunction(element));`. This gives you explicit control over which parameters are passed in to your function and how.

As a shortcut, you can provide a function name rather than an arrow function. If you do so, you're saying that you want all parameters to be passed to that function. You're expected to know what those parameters are and how the function you're passing in will use them. If you don't know that, well, you're programming blind and bad things will sometimes happen. Maybe don't do that.

As with many things in JavaScript, it's better to be explicit than rely on the shortcut.

Edit: Just to be clear, here's the correct way to map an array of strings to an array of numbers in JavaScript: `array.map((s) => parseInt(s, 10));`


It wasn't meant to be oneupmanship, just that you were deliberately ignoring the core of the argument. I could make an article about why NULL in C is a wonderful feature and how it allows such nice optimizations, or I could acknowledge that it has a great cost too. You basically said "There's no downside if you are as smart and experienced as me", but with respect to Javascript, much of the world (myself included) are not. Your argument came down to "people with sufficient skill don't make this mistake", which was definitional instead of actually addressing that the default behavior of an actual usecase was not sane.


I am an amateur dev and used the arrow notation only in the way you described in your first example: array.map((element) => yourFunction(element))

What exactly is the shortcut you mention? (wild your mind converting the example into the incorrect/dangerous version?)


But you're not getting the wrong answer, it's doing exactly what it should do. That one isn't really even a weird language quirk like the others. It's just assuming you're not aware that parseInt can take a 2nd argument


The issue doesn't arise because of parseInt() being able to take more than one argument, the issue is that map() is passing not only the value in the array but also its index as well as a copy of the full array.

I use map() all the time but I didn't know about that until I read this article. I wonder why map() functions this way in Javascript and if any other languages pass additional values like this in their own map() implementations.

I do think GP has a fair point of criticism - at least when I use map() I expect it to take each value in the array and pass it to the callback function. I don't expect it to take each value in the array, the index of that value and the entire array and pass all of that to the callback function instead.


> I wonder why map() functions this way in Javascript and if any other languages pass additional values like this in their own map() implementations.

Sometimes it's very convenient to be able to look ahead or behind the current element being map()'d to make decisions.


Where do your expectations come from though?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


> map() is passing not only the value in the array but also its index as well as a copy of the full array. I use map() all the time but I didn't know about that until I read this article

Then you don't know the basics of the language you're using- how can you complain about it?

> when I use map() I expect it to take each value in the array and pass it to the callback function. I don't expect...

map is well documented. Making the element's index available is indeed pretty useful (the entire array less so, but sometimes it can be).


Good linter will warn you that you are using parseInt without second parameter.


That's a fair criticism of my confusion, but I think I made my point poorly. The way map is implemented may be useful for some usecases and more convenient to implement, but it is a a very poor fit for many other usecases.

As an example of one of the many ways to solve this to make it more sane, the Rust language allows you to call .iter() on something that can be looped over, but also .enum(), which gives you an index and the item. In this way, you can manage expectations about what you are actually getting. Javascript hands you all the tools at once unconditionally which strips a lot of the abstracting power away.


I tend to avoid parseInt unless I explicitly need a specific radix. I find the Number function much more reliable in parsing numbers.

    ['1', '7', '11'].map(Number)
And if I explicitly need integers I can always filter the result

    ['1', '7', '11'].map(Number).filter(Number.isInteger)
It also doesn't silently parse '123thisisnotanumber' or `0x11`



Say what you will about the fact that these exist in the language, I found this post interesting because although realistically and thankfully I never have to deal with these quirks day-to-day, it brought me more insight to the weird nature of this language.

More specifically, the first one was quite interesting and subtle enough that if I didn't have the `parseInt(N, 10)` rule ingrained in my head, if I had to parse a list of numbers I would try that and then scratch my head for like 30 minutes being very confused. It's also subtle enough that I could see it possibly going to production, because it could be missed if the code is only lightly tested and could be missed in a code review too.

The other one that intrigued me was the second one, and I had to look up why it works -- I didn't realize that using the + operator turns any non-parseable value into NaN.

Thanks for the cool article!


I recommend people to study few things before jumping in to something complex which will help them deal with the mess -

Understand the difference between primitive values and object values. There are few historical bugs here with typeof that are now defined in the spec (null, undefined etc are primitive values but will give object etc).

Floating point math:

https://en.m.wikipedia.org/wiki/Floating-point_arithmetic

Type coercion:

https://exploringjs.com/deep-js/ch_type-coercion.html

Prototypical inheritance:

https://javascript.info/prototype-inheritance

How this works:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

And you will be fine for the most part. Other things you will discover on your own.

And use typescript if you can.


This article is written like these flaws in javascript are just a quirky feature.

No, they are an embarrassment.

They don't make the language cool or interesting, they aren't tricks. They are logically incoherent nonsense.


The cooles trick in JS is that they fixed the off-by-one error using dates. Since Month are stored in an array the 5th month is... April! How did all other Languages get this one wrong? I don't know. That's the stuff that makes JS cool and interesting!


The solution to Scenario #1 is still going to break. As far as I'm concerned, if you're using parseInt, you MUST explicitly define the radix. Not doing so Will absolutely bite you in the ass.

In some cases, the parseInt rules go sideways when your number string starts with a 0 (zero). That's going to be a rare occurrence that happens months after you write this code, works just fine on your machine, and is going to take days to find.

Per MDN [1]:

If radix is undefined, 0, or unspecified, JavaScript assumes the following:

1. If the input string begins with "0x" or "0X" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.

2. If the input string begins with "0" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.

3. If the input string begins with any other value, the radix is 10 (decimal).

1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


None of those are mysteries and have been demonstrated years and years ago... So posting clickbait...

Any engineer that allows any of the code like this in their codebase has only themselves to blame.


i've been reading the "professional javascript for web developers" by matt frisbie. matt has done a great job, the book is very thorough. after reading through about a 1/3 of the book it's evident that js is a very flexible language and riddled with soooo many inconsistencies due to all the band-aids manily due to the evolution of the js standard. the section of var blew my mind. it's really sad that so many engineers are exposed to this mess. there is a reason there are so many languages that compile to js. with this pattern, it boggles my mind that the js standards folks continue to add more cruft into the language. they've done enough damage.


It doesn’t help that JS initially had no formal specification. So every implementation had their quirks (because there were no grammar or implementation tests).

Later formal standards created the “use strict” feature which alleviates a lot of problems.


In the beginning, there was just a single vendor (Netscape) and a single implementation (Netscape Navigator). Then there was also a license and a non compliant implentation and things became a bit weird. (E.g., missing implementations of standard functions or missing bound checks.) However there were definitions of the core language available from Netscape for each of the versions, even before the ECMA standard.


Everytime I think i understand JavaScript i am proven wrong. It was pretty fun read tho


Common confusion has to do with how JS overloads the "+" operator.

    '9' + 1
    = "91"

    +'9' + 1
    = 10
It's all very weird though...


Meh, prefix + converts a string into an integer, so it'd be something like toInteger('9') + 1. I agree prefix + is weird, but nothing mind-blowing.


What would you expect string + number to do?


Incompatible types error, either at runtime or during compilation depending on the language


ruby

    >> +"2"
    => "2"
    >> "2" + 1
    TypeError (no implicit conversion of Integer into String)

python

    >>> +"2"
    TypeError: bad operand type for unary +: 'str'
    >>> "2" + 1
    TypeError: can only concatenate str (not "int") to str

lua

    > +"2"
    stdin:1: unexpected symbol near '+'
    > "2" + 1
    3.0


Kind of depends on the language. Perl converts the string to an integer if you use a math operator on a numeric string. Python just throws an error.


When I want either addition or string concatenation, I know it, I can't think of a use case where I'm happy getting either randomly. Perl got this right:

  2 + 2 == 4
  2 . 2 eq 22
You can even declare that failed conversions should throw:

  use warnings FATAL => qw(numeric)


Yes, perl is consistent

perl

    print "2" + 1
    3
    print "2" - 1
    1
js

    > "2" + 1
    "21"
    > "2" - 1
    1


Another thing I thought was clever about Perl is it has two comparison operators, '==' for numeric values and 'eq' for strings.


In this case, they could be copying Java which allows an implicit cast from number to string.


Another fun one to figure out is:

  072 === 058 // returns true
Thankfully, doesn't work in strict mode.


If that is a combination of octal and decimal how is 058 not a parser error?


`072` is interpreted as octal, and `058` is interpreted as decimal (7·8+2 = 58). Ah, the dumpster fire that is JavaScript.


Prefixing octal numbers by zero was a common convention. The real dumpster fire was all those intermediate years without an octal notation in strict mode. (If you needed them, either forget strict mode, or choose between converting to meaningless decimals or using strings and feeding them through parseInt. What's sane about this?)

However, 058 should have been a parse error.


I agree. That inconsistency in how the `0` prefix is handled shouldn't exist.


In the second scenario, I don't think it's the pre-increment operator that's shown but the unary-plus.


Either you love it or you hate it, there's no middle ground with JavaScript


I used to hate it. Now I'm meh. I'm on the middle ground!


I used to love it, now I'm meh. I love some things about it (ecosystem, dev mindshare), now don't like some things (dependency hell, lack of proper debugger, always single-threaded).


I'm with you, but I think the parent comment is right. Because generally comments here are either over-criticizing or over-defending JS.


The language has finally achieved "meh" status if you can use only the latest version and ignore at least half of it completely. This may or many not still make it very bad or entirely fine depending on one's perspective.


" ['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 up very weird, but it actually has an elegant explanation. "

The elegant explanation is that JavaScript was taken over by psychopaths like bajcmartinez.

JavaScript is fine for button onClick code. Outside of that it's garbage.


parseInt has an optional second argument. map provides two arguments. Why is this an example of psychopathic design?


it's just something unexpected that can happen because of the way you can pass functions as callbacks. I liked this example because the result looks really weird, and it can really happen to anyone, it's a mistake you can easily make and can go unnoticed.


You can criticize the language to your heart's content but calling people names makes you look really bad.


I think the parseInt example is more of a gotcha of point-free syntax. Normally it would be written as ['1','7','11'].map(e => parseInt(e))


JavaScript is the upside down.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: