
A Clever Line of JavaScript - fagnerbrack
http://blog.bloomca.me/2017/11/08/the-most-clever-line-of-javascript.html
======
peterkelly
Please don't do this.

It might be clever, and it's the sort of thing that would be pretty cool to
include in a CS lecture about higher-order functions, but it won't earn you
the gratitude of fellow team members who have to maintain your code after you
leave.

~~~
danieltillett
I would not go this far - writing code like this is fun. What you should never
do is use code like this in anything important.

~~~
ocharles
Important code has a way of starting out unimportant.

~~~
danieltillett
This is true, but including code like this should make it easier to know you
should rewrite if you are going to use it in something important. I have been
bitten more often by quick hack code that looks OK on the surface than code
that is full of clever code like this.

------
amptorn
Take care with this technique. We know that `Array.prototype.map` passes not
one but three parameters to the callback function: the array element, the
array index and the array itself. As a result of this,

    
    
        addressParts.map(Function.prototype.call, String.prototype.trim)
    

is roughly equivalent to

    
    
        addressParts.map((str, i, arr) => str.trim(i, arr))
    

Fortunately for us, `String.prototype.trim` ignores those additional
parameters and only uses `this`. But if it didn't, you could end up with some
head-scratching behaviour.

~~~
partycoder
If you wish you verify why is this, consider the following.

Forget about String.prototype.trim at the moment. Consider this function:

    
    
        const f = function() { console.log('receiver is', this, '. arguments are', arguments) }
    

This function prints the receiver (aka "this"), and arguments.

If you use it in the following way:

    
    
        ['a', 'b', 'c'].map(Function.prototype.call, f)
    

The output is

    
    
        receiver is [String: 'a'] . arguments are { '0': 0, '1': [ 'a', 'b', 'c' ] }
        receiver is [String: 'b'] . arguments are { '0': 1, '1': [ 'a', 'b', 'c' ] }
        receiver is [String: 'c'] . arguments are { '0': 2, '1': [ 'a', 'b', 'c' ] }
    

Meaning, that the receiver (aka "this") of the function will be the array
element, and the arguments will be the array index and the array itself.

------
z3t4
> we strive here to avoid creating new functions

Avoiding creating new functions or objects is a good optimization strategy,
but there's most likely very little gain as the optimizer will most likely
inline the functions.

Always do the super naive, easier to understand version first, and only
optimize after you have identified the bottleneck and measured the
performance!

~~~
throw_away
Yeah, I didn't understand this line, especially as at the very end, the author
suggests just using the anonymous function anyways.

------
hdhzy
These kind of hidden extensibility in built-in functions make even seemingly
simple code not work as expected:

    
    
      ["1", "2", "3"].map(parseInt)
    
      > [1, NaN, NaN]

~~~
prolurker
I would argue this particular example has more to do with the radix parameter
of parseInt being optional and having a complex behavior.

More generally, javascript functions accepting any number of parameters,
regardless of those specified in the function declaration, is quite error
prone when passing functions around.

I always use anonymous functions or 'bind' to explicitly match the parameters
unless all functions involved are curried.

The other reason to avoid passing 'naked' functions around too happily is the
behavior of this.

I also find most optimizations to focus on simple, explicit code. Nothing like
using the less common, more dynamic features of the language to hit
deoptimizations.

~~~
ece
Yep, ["1", "2", "3"].map(x => parseInt(x)) works.

------
arh68
Is this really better than the regex way?

    
    
        > '\t1  ;2;3;4;5;6;7;8;9\r\n;10\n'.split(';').map(Function.prototype.call, String.prototype.trim)
        [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]
    
        > '\t1  ;2;3;4;5;6;7;8;9\r\n;10\n'.match(/[^;\s]+/g)
        [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]
    
        > '\t1  ;2;3;4;5;6;7;8;9\r\n;10\n'.match(/\d+/g) // this ones' a bit different
        [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]
    

EDIT: or even (I'm still thinking here)

    
    
        > '\t1  ;2;3;4;5;6;7;8;9\r\n;10\n'.trim().split(/[;\s]+/)
        [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]

~~~
coldtea
> _Is this really better than the regex way?_

Considering that it's built in, probably optimized C++, and does just what it
says on the tin, then yes.

~~~
arh68
Well, you'd be wrong by a mile! On my machine, the .map(..) way is almost
twice as slow. It ain't even close.

But you're right, I failed to consider that it's "probably optimized". And
"C++". /s

~~~
coldtea
I don't see how the "sarcasm" (I use the term loosely) is justified.

You indeed failed to consider all those things, and all those things are
things one should have in mind, whether a particular built-in function is
faster than a custom implementation or not.

Not to mention that I said "probably optimized C++" \-- I didn't claim
straight out that this is the case. The other two claims still hold: it's
built in, so you don't need to roll your own regex based one (complete with
bugs the first time for most people), and it conveys the intention to trim
perfectly.

That's even better than being faster, unless there's a real need to have that
particular operation be fast in your code. Heck, the fact that you got them
wrong in your initial regexes (not capturing what trim does in general)
shouldn't give you enough pause? If you have gone with those BS regexes
instead of trim you would have introduced a bug in the codebase.

If one wants to optimize some such method, running on a tight loop, and on a
verified hot spot of their code, they would of course need to measure to see
which method is faster, not just rely on someone's suggestions. That's
computer science 101.

That it doesn't work "on your machine" (and on whatever browser you've
checked) doesn't mean anything. One would need to check on various browsers,
teat what browsers they want to target (e.g. perhaps their app is Electron and
only needs to work there), and which method is faster there.

------
cominous
I think it was Bjarne Stroustrup who said something like - "don't be clever,
be simple and obvious".

... still nice article... ;)

~~~
k__
Was he trying to be funny after he did c++?!

~~~
krylon
It is possible to write simple and obvious code in C++. A lot of people just
choose not to. ;-)

~~~
camus2
> It is possible to write simple and obvious code in C++. A lot of people just
> choose not to. ;-)

You still need to be C++ fluent enough to be able to read other people's code.
And it's not like everybody has the same definition of "simple and obvious".

~~~
krylon
> And it's not like everybody has the same definition of "simple and obvious".

That is a very good point. I had an interesting discussion with a Perl
programmer once regarding the use of Perl's default variable $_ - to him, code
that relied heavily on it was straightforward and readable. But to a newcomer,
it can be very tricky to figure out what is going on. And even to an
experienced Perl programmer, it can increase the mental overhead of parsing
code significantly if s/he is not used to it.

------
JoshTriplett
And here I was hoping that the title referred to something like "The most
clever line of JavaScript is the line you don't write".

~~~
tomalpha
I realise you might be being lighthearted, but just to be boring I’ll thrown
in “The fastest and most bug free line of code in any language is the line you
don’t write”.

------
inglor
I wrote one of these a few years back:
[https://stackoverflow.com/questions/18947892/creating-
range-...](https://stackoverflow.com/questions/18947892/creating-range-in-
javascript-strange-syntax) \- honestly I hate this sort of cleverness in real
code but it makes for interesting puzzles.

(As long as no one actually asks these questions in interviews)

~~~
bonzini
Is it at least faster than the anonymous function?

~~~
sjclemmy
That was my initial thought. Is there performance benefit in constructing a
function call in this manner?

~~~
inglor
No, there is not - it is purely "language lawyer", it is actually slower in
most cases - especially on newer versions of V8 that use TurboFan.

------
nikanj
Debugging code is twice as hard as writing it. If you write code as cleverly
as you can, you'll be too stupid by half to debug it.

~~~
reitanqild
Or, more realistically: it will take twice the time. :-)

------
CapacitorSet
That might be clever, but aside from possible performance gains it's worse
than the trivial alternative arr.map(it => it.trim()).

------
sAbakumoff
What's the point of writing a code like this? Giving maintainers a hard time?
Showing off?

------
scarlac
I just benchmarked it:
[https://gist.github.com/scarlac/87dc480c9f5893060a17a4c6f61c...](https://gist.github.com/scarlac/87dc480c9f5893060a17a4c6f61cbc85)

Personally I don't find the results convincing. With 20k iterations and 18
trials I have to squint to see a practical difference. But please, judge for
yourselves. Numbers:
[https://docs.google.com/spreadsheets/d/1mZx7ENLTeCG0G_AwjCCv...](https://docs.google.com/spreadsheets/d/1mZx7ENLTeCG0G_AwjCCv71Bc2MYICfvGoyDvHAeOOYY/edit#gid=0)

~~~
susi22
If performance matters a manual for loop will be faster. `arr.map()` isn't all
that fast (except in the recent Firefoxes).

~~~
masklinn
I get similar performances for the "naive" map:

    
    
        let trimmed = strings.map(str => str.trim());
    

and a c-style for

    
    
        let trimmed = [];
        for(var i=0; i<strings.length; ++i) {
            trimmed.push(strings[i].trim());
        }
    

in both Chrome (62) and Firefox (58). The c-style for is very lightly faster
in FF (not Chrome) but it's 767 ops/s to 725 ops/s so not exactly brain-
breaking (chrome is at 454 and 452).

Safari (11) is the only one where map is a fair bit slower than c-style (420
to 377).

In all cases, both the clever map and for..of are slower than the naive map,
though the magnitude varies Chrome yields very little difference but Firefox
and Safari dislike for..of and like "clever map" even less.

~~~
jostylr
I added the floor loop to the code and made a console based webpage for it:
[http://jostylr.com/cleverdumb.html](http://jostylr.com/cleverdumb.html)

Kind of hackish, but you can open it up in a browser and look at the console
for the run times.

Basically, the clever code is generally worse, and the arrow versus for loop
in Firefox and Safari are similar. But in Chrome, the for loop is quicker; it
was far more noticeable in node than in chrome. One quirk of chrome is that
the first time the for loop is run, it seems to take longer than subsequent
runs and I am not sure why that is. I tried moving around initialization stuff
and it did not seem to make any differences.

Times from node:

    
    
      Prototype: 105.467ms
      Arrow function: 106.674ms
      For: 98.820ms
      Prototype: 105.743ms
      Arrow function: 106.192ms
      For: 82.733ms
      Prototype: 105.512ms
      Arrow function: 106.517ms
      For: 82.625ms
    

Basically, code in the comfortable style. I used to love for loops, but have
now switched over to map and forEach when it makes sense to do so.

------
amptorn
We can go one cleverer[1]:

    
    
        functions.forEach(Function.prototype.call, Function.prototype.call)
    

this is approximately equivalent to

    
    
        functions.forEach(f => f())
    

although in practice it's actually more like

    
    
        functions.forEach((f, i, arr) => f.bind(i)(arr))
    

so let's hope `f` isn't too sensitive to the value of `this` or that `arr`
parameter...

[1] [https://zpao.com/posts/calling-an-array-of-functions-in-
java...](https://zpao.com/posts/calling-an-array-of-functions-in-javascript/)

------
partycoder
I would argue that it is not so clever.

1) It is less idiomatic

2) Does not provide a performance gain

~~~
emacsgifs
Should be the top comment.

1) Accurate

2) Succinct

------
tomatsu
Is it actually any faster? Let's see...

[https://jsperf.com/trimcall/1](https://jsperf.com/trimcall/1)

For me, the "clever" one is slightly faster in Chrome while the supposedly
naive one is significantly faster in Firefox.

Looks like the naive approach not only wins in terms of readability, but
performance, too. (Unless I seriously screwed this up which is always a
possibility with micro benchmarks.)

~~~
cominous
Can confirm this. Clever version is actually about 10% faster with node v9.1

~~~
cominous
Actually, the for-based loop is another 10% faster - so if performance is
relevant, just use the for-loop!

Node v9.1 + benchmark.js clever-solution x 249,543 ops/sec ±0.36% standard-
arrow-function x 213,926 ops/sec ±0.35% old-for-loop x 261,449 ops/sec ±0.54%

------
jwdunne
This is a great example of why you should avoid writing clever code. A clever
one-line piece of code requires a blog post to make sense of it.

Not slating the article. I thought it interesting and the author does mention
this caveat, making a point of saying the typical way is honestly more
readable.

As for the project they found this in, I hope this stuff isn't thread through
it, for the maintainer's sake and sanity.

------
Tarean
I kind of wish x.f(y) was the same as f(x, y) in javascript.

~~~
masklinn
How would dispatching work exactly, e.g. how would you override valueOf or
toString?

~~~
Tarean
On retrospect I probably want it to be more like lua?

    
    
        f(this, other) -> f.call(this, other)
        obj.f(other) -> obj.f.call(obj, other)
    

and then replace implicit this with the first argument? It is a tiny but more
verbose but gets rid of all the confusion around implicit this rules so it
seems like a win.

------
dsego
Seems pretty obvious for us who worked with JS before the arrow functions.

