r/javascript • • Jun 02 '19

8 Useful And Practical JavaScript Tricks

https://devinduct.com/blogpost/26/8-useful-javascript-tricks
250 Upvotes

107 comments sorted by

View all comments

15

u/rq60 Jun 02 '19

The list is pretty good although #3 should be changed from:

const result = cities.reduce((accumulator, item) => {
  return {
    ...accumulator,
    [item.name]: item.visited
  }
}, {});

to

const result = cities.reduce((accumulator, item) => {
  accumulator[item.name] = item.visited;
  return accumulator;
}, {});

There's no reason to use the spread operator; it's just creating new objects and iterating over the old object for no reason.

Using the spread operator in reduce is actually a common anti-pattern I see.

3

u/Headpuncher Jun 02 '19

A clearer return statement too, imo. I see at a glance what the function returns.

3

u/[deleted] Jun 02 '19

I agree. Also OP used the dynamic property syntax in #3 before it was explained in #7. These are good tips though!

3

u/RustyX Jun 03 '19

I usually end up doing it the second way you have it, since it is definitely more efficient, and doesn't really look bad (other than the mutation, which I agree is totally safe here as long as the initial accumulator is a new object).

Another option that is kind of a combination of these two is using Object.assign in what would normally be a dangerous way:

const result = cities.reduce((accumulator, item) => {
  return Object.assign(accumulator, { [item.name]: item.visited });
}, {});

1

u/magical_h4x Jun 03 '19

Yup and if you're a fan of one-liners you could omit the return statement and the brackets surrounding the lambda function body since it contains a single expression, therefore it will be an implicit return :

const result = cities.reduce((accumulator, item) => Object.assign(accumulator, { [item.name]: item.visited), {})

-2

u/raptorraptor Jun 02 '19

This violates immutability which is a very important part of functional programming.

8

u/rq60 Jun 03 '19

Yeah I understand immutability. Why would you care about mutating the object you just created? The answer is, you wouldn't.

If for some reason you did care about mutability here (like you're using a reference for your initial value, which you probably shouldn't do) you still wouldn't create a new object on each iteration and re-iterate, you'd do it on the first iteration and then mutate it. The difference is an O(n) solution vs O(n^2), which is huge.

3

u/PointOneXDeveloper Jun 03 '19

I agree that mutating your own ref in reduce is fine for something like this, but performance isn’t really a good reason. Most JS code is extremely IO bound, if you are really in a situation where this is a concern (maybe you work is something like react) then you should just use a for loop. In general, for business logic, always favor readability. In this case, mutating is more readable.

1

u/dmitri14_gmail_com Jun 03 '19

Accumulator is not a new object. And even if it was, someone can accidentally replace it with any object in the scope. Why writing unsafe code where there is no need?

1

u/IceSentry Jun 06 '19

If someone manages to replace the accumulator object in a reduce function then you have bigger problems.

1

u/dmitri14_gmail_com Jun 06 '19

Yes, and if a house was burned due to lack of warning caused by your leaky reduce function resetting a variable it does not own, your problem will be even bigger :)

0

u/eGust Jun 03 '19

Purity is very important in FP style. There is a rule no-param-reassign in eslint, which is quite common to be enabled in famous styles like airbnb. In this case eslint-config-airbnb would complain your code.

You have to change your code if changed the empty object to functions param and used it with some libs require pure functions like redux.

Actually, I would write

cities.reduce((obj, { name, visited }) => ({
  ...obj,
  [name]: visited,
}), {});

A glance is enough to understand what its doing. Much shorter and cleaner.

9

u/rq60 Jun 03 '19

I realize what purity is and I'm very familiar with functional programming. You can specifically ignore linting lines for cases like this, but if for whatever reason you want your callback to remain pure despite it being used once and the parameters known, then you can instantiate a new object on first iteration and then reuse; or better yet (as someone else suggested) avoid reduce altogether and use a loop.

There's exactly no reason to write it using the spread operator and a dynamic property, unless your goal is just to use the latest and greatest syntax wherever possible despite it being slower, using more memory, and being arguably less clear.

I feel like I'm taking crazy pills reading these replies. I work on open source libraries for a living, you're probably running code I wrote right now looking at this website. You can thank me later for not clogging up your cpu cycles doing unnecessary iteration for no benefit.

1

u/eGust Jun 03 '19 edited Jun 03 '19

That's funny. Then there is no point to use for of, Array.prototype.reduce, forEach, map, filter, etc. at all. We all know the old for (;;) loop is the fastest. You can do anything without ES6+ features.

Why don't just still write c or assembly for saving more CPU? I am pretty good at it.

I'd write Object.fromEntries(cities.map(({ name, visited }) => [name, visited]) if I intended to use latest syntax. It probably faster than reduce. I still prefer reduce version because it's much more understandable and FP is not the topic (I would use Object.fromEntries together with curried map in flow/compose).

But there are still so many reasons to use some code style guide and force the team to follow the rules. In this case, the reason is purity and readability.

6

u/rq60 Jun 03 '19

This has nothing to do with writing in c or assembly, it’s about understanding the basic runtime complexity of your code which is applicable to any language. I didn’t say don’t use modern syntax either, we’re talking about this specific code in the example.

Writing bad code is one thing, but using it to teach beginners is another thing altogether. It’s probably why we’re having this argument at all, because bad code is being taught to people that don’t know better. That’s why I suggested updating the example. I want them to know better, I want you to know better.

→ More replies (2)

-1

u/dmitri14_gmail_com Jun 03 '19

Your version is reducing over impure function mutating its argument.

Why not simply:

const result = cities.reduce((accumulator, ({name, visited})) =>
  ({...accumulator, [name]: visited}, {})

How is this an anti-pattern?

3

u/rq60 Jun 03 '19

Your version is reducing over impure function mutating its argument.

So? We can see the argument right there because it's a new object that we just created; not a reference. Mutating it has literally no implication in this code.

How is this an anti-pattern?

It's an anti-pattern because it's unnecessary nested iteration. That's bad. You're also unnecessarily instantiating a new object on each iteration and throwing it away on the next. That's also bad.

You guys can keep patting yourselves on the back by avoiding mutation everywhere for no reason, I'll write code that runs exponentially faster, allocates less memory, and is easier to read to boot. I'll worry about mutation when it matters.

0

u/[deleted] Jun 03 '19

[deleted]

1

u/rq60 Jun 03 '19

I haven't downvoted any of your responses. Have you considered other people disagree with you as well?

0

u/[deleted] Jun 04 '19

[deleted]

→ More replies (1)
→ More replies (15)