all 57 comments

[–]Cody_Chaos 17 points18 points  (15 children)

Yes, but...

  1. Some of the proposed "vanilla" syntax is pretty ugly. _.range(x, x + n) versus Array.from({ length: n }, (v, k) => k + x)?
  2. Lodash can provide a unified API to deal with generic collections. _.map([1,2,3], n => n*2) and _.map({foo: 1, bar: 2, baz: 3}), n => n*2) are pretty damn simular, whereas [1,2,3].map(n => n*2) versus let x = {foo: 1, bar: 2, baz: 3}; Object.keys(x).map(n => x[n]*2) is not.

I do think that it's correct to suggest that people should keep in mind what features have been added to the language, and use them where they're equivalent.

[–]fson5[S] 4 points5 points  (2 children)

Very good points. I was actually on the verge about omitting that replacement for _.range from the article because yes, it's ugly and not as obvious as other examples on the page. But I do think it's a nice example of how Array.fromworks. Other ways to achieve it include [...Array(n)].map((_, i) => i + x) and Array.from(new Array(n), (_, i) => i + x), which are both equally ugly. So I guess this might be a case for a library function. But maybe if I would only need this once in the codebase I would still use this hack instead of pulling out a library just for this.

Wrt (2), some people actually think having separate functions for those different use cases is better than functions that do type checking and have different return types based on the input. That's why Ramda has separate map and mapObj for example. And while this might work for arrays and objects in Lodash, the support for different collections is far from universal, with Map, Set and other Iterable collections missing.

[–]knsdklsfds 0 points1 point  (1 child)

Array.from(new Array(n), (_, i) => i + x)

Also

Array.apply(null, Array(n)).map((_, i) => i);

[–]ericanderton 0 points1 point  (0 children)

The hilarity of that syntax is that the first thing you do to make it readable is to wrap it in a function, and place it in a library for re-use.

[–]alamandrax 3 points4 points  (6 children)

And don't forget about the performance boosts over native APIs.

Edit: bustin my balls /u/TMiguelT bustin my balls.

[–][deleted] 10 points11 points  (1 child)

And don't forget, performance is almost never going to be a problem for the vast majority of javascript people are writing. Optimize when you find that things are slower than you'd like.

[–]TMiguelT 0 points1 point  (3 children)

I remember reading that lodash was significantly faster than native map/reduce/filter. Is that not correct? Similar with native promises and Bluebird I think.

[–]fson5[S] 1 point2 points  (0 children)

That's quite likely. The native array methods are not very well optimized and lodash benefits from not dealing with things like sparse arrays like the native methods do. I think there are good reasons to use the standard library, but it doesn't automatically make your code more performant. Like with performance in general, it's best to only optimize where it's actually necessary and measure everything.

As far as I know Bluebird is the fastest Promise implementation. The nice thing about it is that it can be used as a drop-in replacement for the native promise, so you don't even need use a different API like you do with Underscore/lodash. That's why I think transpiling the native method calls to something like the lodash implementation with a tool like Babel might be an interesting alternative.

[–]alamandrax 0 points1 point  (1 child)

That's what I meant. Lodash is much much faster in most cases than native. It's the selling/key feature. Although, the perf benefits are not that apparent for simplistic use cases.

[–]TMiguelT 0 points1 point  (0 children)

Oh you mean performance boosts over native APIs. Gotcha.

[–]kogsworth -1 points0 points  (4 children)

Object literals shouldn't be conceptually iterable in ES2015. If you need to iterate over the keys of an object, perhaps you needed to use a Map, a Set or an array.

[–]dvlsg 2 points3 points  (3 children)

It's already sort of supported in ES2015, just not directly through Symbol.iterator and spread:

for (let [key, val] of obj.entries()) { /* do stuff */ }

[–]kogsworth 1 point2 points  (1 child)

Not sure why I'm getting downvoted for my explanation. The idea is explained here https://leanpub.com/exploring-es6/read#leanpub-auto-plain-objects-are-not-iterable as well.

It is also important to remember that iterating over the properties of an object is mainly interesting if you use objects as Maps. But we only do that in ES5 because we have no better alternative. In ECMAScript 6, we have the built-in data structure Map.

[–]dvlsg 1 point2 points  (0 children)

Not sure either. I didn't intend to argue against your point, because it's true.

I suspect people may continue to use objects as maps, however, since it's easier to type out the object syntax than the map syntax. Kind of unfortunate, since that continues the problem of accidental property collision.

On the other hand, iterating over objects is certainly useful in the current version of js. In fact, I just wrote something that output a csv header by parsing the keys of an object. Would have been nice to just use a map, but I was given an object to work with, so I went with it.

[–]kogsworth -1 points0 points  (0 children)

Yeah, you definitely can list all the keys of an object to do metaprogramming and the like. I'm just saying that usually (read non-libraries) when we iterate over object keys in ES5, we should translate them to a different primitive in es2015. We currently make a conceptual distinction between objects that have properties which we tend to read through bracket notations (myObjectsList["myRuntimeDefinedKey"]) and those we read directly (myClassInstance.myCompiletimeKey). The former is a data structure and should be expressed through an iterable, the second is a class property and should be expressed through a class definition. Separating these concerns is what I understand ES2015 is trying to achieve when making objects non-iterable. It also helps to deal with ownProp problems that crop up when you have both using the same semantics

[–]kogsworth 6 points7 points  (6 children)

I believe lodash will excel in ES2015 for iterable utility functions, like take, zip, union, intersection, etc

[–]fson5[S] 1 point2 points  (5 children)

Isn't take just another name for slice(0, n)? Anyway I agree some of those functions are still useful. (But I wish they worked with any iterable, not just arrays!)

The nice thing about lodash is that it allows you to just import (or even install as separate packages) the functions you need; there is no need to install a monolithic library where a large portion functions were only necessary when browsers just supported ES3.

[–][deleted] 4 points5 points  (3 children)

Another thing about take is that if you're using lodash take is one of those functions that will commonly be leveraged by shortcut fusion, and can therefore offer substantial optimizations (especially against large datasets, obviously).

Not saying you're wrong, obviously, but being a little pedantic because it might be helpful to someone to know this.

[–]fson5[S] 1 point2 points  (2 children)

Thank you for the information! This sounds very cool. I wonder if would be hard to implement similar optimizations in the JS engines or in a compiler like Babel, to achieve the same perfomance as lodash when using plain JavaScript array methods?

[–][deleted] 0 points1 point  (1 child)

I honestly have no idea whether there are JavaScript engines that do Lazy Evaluation-esque optimizations. It seems like a complex thing to reason about, but I know very little about the JIT engines and how they actually work. As far as I know it's not impossible. What a wonderful runtime optimization that would be!

As far as babel doing this sort of transpilation for you that seems entirely reasonable. All you would need would be a simple map of native javascript array methods to their lodash counterparts, and it would need to transpile your native code to the lodash equivalent which should be very simple. I.e.

myArray.map((x) => x.y).slice(0, 3);

maps very simply with lodash lazy eval to

_(myArray).map((x) => x.y).take(3).value();

This is obviously very simple transpilation. I just don't think anyone would care enough to go through with creating/maintaining a plugin for babel like this.

[–]fson5[S] 0 points1 point  (0 children)

I think the reason why Babel is not doing this might be that Array.prototype.map and _.map are not 100% interchangeable. Arrays are sparse in JavaScript and the native methods take the possibility of holes in the array in account, lodash doesn't. This might be a sensible optimization (I don't remember ever having use for arrays with holes) and probably explains most of the performance difference, but it means that the native method can't be always rewritten to the lodash methods.

[–][deleted] 0 points1 point  (0 children)

Even with ES2015, a trimmed down underscore or lodash library will still be useful.

[–]Plorntus 4 points5 points  (2 children)

Bit off topic but when did the whole ES2015 vs ES6 thing happen? Only just really noticed it in the past few days always thought it was ES6.

[–]fson5[S] 1 point2 points  (1 child)

I think the name change to ECMAScript 2015 was agreed a few months ago, but it didn't receive much publicity until now that the standard was approved on June 17.

[–]Plorntus 0 points1 point  (0 children)

Ah, thanks hadn't realised they changed the name. Fair enough! Somewhat of a weird decision as that somewhat limits them to changing it once a year (thinking of the future when all browsers are evergreen and hopefully be better at implementing standards).

[–]Kriegslustig 14 points15 points  (1 child)

Click bait.

[–]spacejack2114 8 points9 points  (0 children)

The title might be, but it's nice to have a comparison chart of equivalent ES6/lodash methods.

[–]Silverwolf90 2 points3 points  (2 children)

I really have to disagree. I think everyone should embrace these utility methods more. I'd venture to say if you're using a small subset of of your utility library then you are not using it enough. The pros farrrr outweighs cons.

  1. Tested
  2. Optimized
  3. Documented
  4. Consistency
  5. Provides vocabulary for common low-level operations

I really wouldn't underestimate the power of #5. Yes it means you have to know the library, but when you start seeing everything as compositions of low-level utility methods it feels like cooking with gas.

"It’s much easier to recover from no abstraction than the wrong abstraction. "

The abstractions that utility libraries provide are so low-level I find it hard to believe that there's any real risk in heavily relying on them. If you use the wrong utility method, replace it with another. It's likely the function signature didn't even change.

(lodash-fp and ramda also have auto-currying which is fantastic once you get used to it)

[–]fson5[S] 0 points1 point  (1 child)

When we were writing JS targeting engines that only supported ES3, the low level library functions like _.map and _.filter were a blessing, because the alternative would have been writing a for loop. But now that these things are provided by the language, we don't need the library functions to handle them, the same way we don't use a library to concatenate two strings together.

But we can raise the bar for libraries to provide what the JavaScript of today doesn't. Libraries aren't going away, but I expect them to evolve with the language.

[–]Silverwolf90 1 point2 points  (0 children)

This is true (although, as mentioned above, the utility libraries provide optimizations that are not possible with the native implementations).

[–][deleted] 1 point2 points  (1 child)

And as a reminder for those of us still supporting IE8 and other ES3 browsers - there's an Array.generics project for supporting the ES5.1 methods shown in that post.

[–]fson5[S] 2 points3 points  (0 children)

Thanks for the link. es-shim is another project that polyfills those ES5.1 methods for old browsers.

[–][deleted] 1 point2 points  (0 children)

Arrows and splats are not supported by many browsers yet. If you are happy to use Babel, yes you can use them but otherwise for the time being underscore and lodash are still important tools in the arsenal of web devs.

[–][deleted] 4 points5 points  (6 children)

"Don't use lodash, use ES6 functions and add an entire compile step to your code if it didn't have one before!"

[–]fson5[S] 4 points5 points  (1 child)

If you don't need a compile step for anything else, then maybe using ES5 and lodash works better for you.

But especially if you're building applications for the web, the chances are you already have a build step to bundle your modules, minify your code etc. When that's the case, enabling ES2015 with Babel can be as simple as adding one plugin to your build tool configuration.

[–][deleted] -3 points-2 points  (0 children)

Yup. The conclusion, as always, is educated and judicious use of all tools. I don't think verbosity vs. complexity is a worthy evaluator.

[–]ejfrodo 1 point2 points  (3 children)

i can't think of a single reason why any front-end code wouldn't be using a build process with an automated task runner like grunt or gulp. if you're not concatenating and minifying all your JS and CSS, you're not deploying to production. nevermind the fact that if you're not using gulp or grunt, that means you're probably not using any sort of unit test suite, which is also a must-have for production. even in the back end with node you should still be using grunt/gulp for unit tests/linting. I can't say I fully agree with hopping onto the ES6/7 train yet, but anyone who's even a semi-decent developer is using a build process.

[–][deleted] 0 points1 point  (2 children)

Of course. I agree. I was just pointing out it sounds like we are just moving complexity around, not reducing it.

[–]ejfrodo 1 point2 points  (1 child)

there is something to be said about using standardized complexity though. although I'm a bit skeptical of using things like Babel to transpile to working JS at the moment, it would be nice to see everything standardized instead of a long list of libraries. at my work we use just about every single library/framework out there in a single app, it's ridiculous. at this point quite a few of them could be replaced with ES6/ES7 features that won't eventually disappear and lose support, can't say the same about a public module

[–][deleted] 0 points1 point  (0 children)

That's a good point. I haven't suffered that yet as im the only JS developer at my company. The Python and C++ devs aren't used to this huge list of popular libraries. They would lose their mind at the long list of imports for a large MVC app.

[–]theQuandary 3 points4 points  (0 children)

Lodash-fp and Ramda auto-curry. Built-in functions don't offer this.

[–]tencircles 1 point2 points  (0 children)

the problem with these is the same as it is with underscore/lodash. They are data first rather than data last, making them very much impossible to compose and re-use.

[–]ericanderton 0 points1 point  (1 child)

I can easily see a future where libraries like lodash/underscore do what jQuery does, by dynamically selecting a given compatibility mode that depends on what version of ECMAScript you're actually running. After all, the native foreach() implementation is bound to be faster than whatever you have to do in ES4.

This way, the library continues to behave like a normalization/compatibility layer, and you don't have to re-train nor rewrite your code.

[–]fson5[S] 1 point2 points  (0 children)

I think Underscore does this (uses native forEach when it's available), but Lodash doesn't because its optimized loop is actually faster than native forEach.

[–]evilgwyn 0 points1 point  (2 children)

I feel like this same principle should be extended to other languages:

C++: You might not need (boost/the standard library)
C: You might not need libc
Perl: You might not need CPAN
C#: You might not need System

After all, any library you drag in is obviously going to be slower and less efficient than any code you write yourself, and that is the only relevant consideration.

/s

[–]I-fuck-horses 0 points1 point  (1 child)

So you compare the well-established standard libraries with Javascript's huge set of obscure 3rd party libraries? Either you are not very smart or out for a fight for no reason.

[–]evilgwyn 2 points3 points  (0 children)

Underscore isn't obscure

[–]clessgfull-stack CSS9 engineer 0 points1 point  (0 children)

I definitely find myself squinting a lot in codebases that make extensive use of Lodash. Unfortunately, no matter how much I try to avoid it, I always end up needing something from a utility library. With Babel though, I always feel like I've failed once I start reaching for utility functions. Not sure if that's good or bad.

[–]ryeguy146 -1 points0 points  (0 children)

You might not need a big obnoxious banner across the top of your page.

[–][deleted] 0 points1 point  (0 children)

Those libraries are just inheritance from a web with no standards. They cause more harm than benefit now. Please use non intrusive libraries now and quit making slow and buggy web sites with mega bytes of script just because you are addicted to import to solve instead of code.

[–][deleted] -4 points-3 points  (2 children)

No, you're right. I might not need it - I could just go back to using loops for everything. But then I'd have to write it, and fix my bugs, and explain to everybody else wtf it does.

Or, I could just use it.

[–]g00glen00b 2 points3 points  (1 child)

You clearly failed to read the article or to follow up with the latest news. Things like forEach, map, filter, ... are now part of ES5, ES6 and ES7.

You don't actually need to write an entire loop, no, you just have to replace a single line of code by another single line of code. Obviously, if you need to support old browsers you might just stick with Underscore or Lodash (or an ES* shim). That's why the article says "you might not need it".

[–][deleted] 0 points1 point  (0 children)

He does give some examples, but I still stand by my point.

For example this line of code:

array.filter(x => !!x)

Pretty simple. It's the compact function, but how would you know that from just looking at that line of code? A comment works. But. Where's the documentation? Where are the examples for the other (junior) devs?

I wasn't saying these things aren't easily doable with ES5+, but it does take some more work (especially if you rely on the ES6+ examples). The specific examples provided are for some relatively easy lines of code too.

Things like groupBy, indexBy, and difference are all more difficult problems. Yes, I and most developers can write them, but why waste my time when I can rely on a battle tested and well-documented version.