Alternate title for the article: "Lists of lodash features replaced by ES6 if you don't mind throwing an exception when given invalid input such as null or undefined".
All kidding aside, a lot of our lodash code ends up looking something like this:
function (xs) {
return _(xs).pluck('foo').filter().value();
}
That code clearly expects that xs is an array of objects. However, we might occasionally end up with xs being undefined, or with xs being an array but one of the elements is null, etc.
Most of the time, we want our function to just swallow those errors and return an empty array in such cases. This is exactly what lodash does, but if we tried to call xs.map(...) in that case we'd get an error. Similar caveats apply for grabbing the foo attribute if one of the array elements ends up being null or something.
For this reason, I recommend continuing to use lodash almost all of the time, even when there's a native Javascript method available.
Something I find very interesting about Swift is it's complete reversal of opinion from Objective-C on this topic.
Obj-C basically has the behavior GP wants: call a method on nil (aka: null) and it returns nil/false/0. It's possible to write concise & correct code that relies on this behavior.
Swift turns it into a fatal runtime error, and uses the type system to help prevent it from happening.
I think there's room for both (probably not in the same project though!). A lot of successful projects have been written in Obj-C, and some of them rely on the behavior of nil.
However, it's harder to maintain the code. You have to reason about whether nil is possible and does the code do the right thing, or did the original author forget to consider the possibility? It's really nice when the static type system forces the programmer to be explicit.
Having used both paradigms, I honestly don't know which I'd prefer for JS - especially considering it's lack of static typing. It might depend on whether I was writing application or library code.
Swift has the same syntax for its nullable types. It's also conceptually similar to Haskell's monadic bind over the Maybe type, where your example would look like
foo >>= bar >>= baz
The nice thing about this approach is that (>>=) is not specific to the Maybe type, and can be extended to other data structures, include one that passes along error messages in the "null" type through the sad path if it encounters them. This would be in the (Either a) monad.
Seriously. A really common example, ever have a website that completely fails to load when you're running adblock? Would you rather have a blank page, or a 99% functional website with a couple of error messages in the console?
The answer is: that's an expected failure, so you make the error handling explicit by catching the error and taking care of it appropriately (including proceeding to the rest of the page load, naturally). Implicitly handling errors behind JavaScript's loose typing rules is a recipe for disaster.
Explicitly handling errors is almost always a bad idea, no matter what programming language it is. There are very few errors you can reasonably handle, and they must be designed for. It's specific application domains that need different handling strategies: e.g. embedded, device drivers, software that's trying to maintain some very strict invariants. A web page isn't usually one of them.
Usually errors should be sent up the stack to the request / event loop and logged / telemetry / etc.
The question here is something different, though. It's what should be considered an error, vs what should use the null object pattern. I don't think anyone can make a categorical judgement on which is better without context. It's suggested here that the null object pattern implemented by lodash is desired; I don't think it's wrong in principle to rely on it, as part of a cohesive design - e.g. it's used in such a way that it doesn't make development or finding bugs harder than necessary.
Take a look at Common Lisp and Dylan and their respective condition/restart systems. This is how error handling should look like: they let you handle your errors where it makes sense and naturally recover from them if it's possible.
It's hard for me to imagine why would you want to .map() on a list of 3rd party tracking modules but let's go with your example.
That sort of thing should not be handled by a low level utility library, because at best it will only do the right thing 50% of the time. It should be handled explicitly on a single interface between your software and the tracking module.
I would probably wrap it and explicitly ignore things in the wrapping code if the tracking module is missing.
And yes, I would rather see an empty page so I know to temporarily disable the adblocker, than have a page (e.g. seamless) that silently fails to order your food at the very last step.
You will argue that it's better for the users. No it's not. If the site is broken, it's easy for them to understand. They can at least go somewhere else to order food. If everything looks like it's working but it isn't is very-very frustrating.
I can also catch and log errors and fix them, but hidden bugs will just stay longer and frustrate users, because they will think they did something wrong.
Perhaps if you are using null to represent something explicitly in your data.
But silently failing on undefined is just going to lead to an other bug somewhere else entirely and half a day of debugging.
I would much rather fail early and loudly than having to hunt down some
anecdotal bug that happens every prime-th national holiday and is impossible to reproduce.
I have found it better to have UI code to use undefined over exceptions, and back-end code to use exceptions over undefined as the client should have properly formatted the request.
Having functions/methods return undefined is a huge time and complexity saver for UI code as the application could still be in the process of getting input from the user that then would be passed off to the back-end code once the user was done changing their minds. No point in having a dropdown throw an error because the user is still deciding what they want to appear in the dropdown.
For client-side webapps? Users are just going to hit reload and move on. For just about every website I can imagine, if your two options are to leave an extremely rare bug that's impossible to reproduce, or to effectively take the website down for all users, the former is the unambiguous right choice.
Many times folks understand that values can be nullish. Libs like Lodash treat them as empty collections to avoid repetitive nullish check scaffolding.
What's wrong with that? For large systems, you'll never have a codebase that is 100% understood or 100% matches what the designers intended. If you want a robust, working, large system, you have to account for unintended things happening some of the time. In many cases, the right thing to do is to preserve or ignore nulls. Especially for client-side JavaScript (where clients are, by their nature, untrusted, and all authentication and data validation must happen on the server whether or not it also happens on the client), if some data fails to load due to a network blip, or the end-user does something unexpected and a div isn't initialized properly, or whatever, the right behavior for the software is to keep going, and the wrong behavior is to cause a minor error to turn into a major one.
In many other cases, of course, the robust thing to do is to catch a failure early and prevent some code from executing before it can do more harm, and err on the side of the system doing nothing instead of it doing something wrong. But neither of these is a universal rule.
If a bug causes an unexpected undefined value, it will lead your code to an undefined behavior. It might work well 999 times and wipe everything on the 1000th execution. Thankfully, Javascript is mostly limited to web browsers.
Exceptions make it so that nothing unexpected happen. This is especially useful when you do not know the whole codebase. Of course, there are many cases when you can just ignore the errors. Exceptions allow you to fine tune this.
> If a bug causes an unexpected undefined value, it will lead your code to an undefined behavior.
Let's not confuse "undefined", a JS value that is basically like C's NULL, with "undefined behavior", the concept from e.g. the C language spec. Operations on the JS value "undefined" are perfectly well-defined in the C sense; you can reliably test for it and have a case to handle it. In particular, the well-defined behavior for Underscore/Lodash in response to mapping over "undefined" is to return an empty array. The programmer upthread is using the library's documented and well-defined behavior; there is nothing wrong with that.
It is just like how, in C, some functions (like time()) are well-defined if you pass them a NULL pointer, and some functions (like strlen()) are not, and result in undefined behavior. In this case, the functions in question are all well-defined if you pass them "undefined".
> Exceptions make it so that nothing unexpected happen.
No, it makes it so that your program surprises the user by crashing, which is (hopefully!) pretty unexpected.
Unless, of course, you use the exceptions to provide some sane default code path that handles the problem, but that's exactly what lodash is doing for you in this example.
1. Where did anyone say we were hitting undefined behavior? We're hitting a well-defined JavaScript value whose name is "undefined".
2. Why is crashing correct? Don't answer in terms of language specs, answer in terms of desired behavior for whatever you're trying to do with the program. Software engineering is a tool for accomplishing other things. Sometimes, yes, software crashing is the right thing in service of that other goal. But not always.
Undefined behavior of a program is when a programmer didn't define how to handle a particular situation. If an array becomes "undefined" and the programmer didn't expect such result (e.g. there's no if (typeof array === "undefined") { ... }), then the behavior is undefined regardless of what kind of types there are in JavaScript.
Crashing in such case is used to avoid incorrect functioning of the program, such as overwriting user data or introducing security vulnerabilities. By definition, if the behavior wasn't expected, the program is in unknown state. Sure, you can gracefully handle such situations (e.g. allow website to load other scripts if the particular piece that "crashes" not important for its functioning, crash just some kind-of sub-process and restart it, or — if it's user input — end up with some predefined value regardless of the incorrect input, etc.), but the correct _default_ behavior is to crash, otherwise you'll end up with unknown state and the algorithm that you wrote will be incorrect.
I recommend anyone who wants to see how much unexpected behavior is in their JavaScript programs to install typescript@next and start adding types, compiling with tsc --strictNullChecks.
> If you want a robust, working, large system, you have to account for unintended things happening some of the time
Ever written a large code base with isolated I/O, functional code, and typed/static analysis? Because I have, and nothing unintended happens except at the I/O level.
When something unintended does happen, it throws an exception: something genuinely exceptional has happened.
This code base has yet to throw an exception in production, and it also hasn't had a bug in production (after running for 6 months with ~1,000 active users).
I'm going to have to dispute your definition of "large system" if it's been running for a mere 6 months and you describe it as if it had a single author. Let me know once it's changed maintenance twice and also once it's changed management twice. Robustness is not about how well a system performs in its initial conditions; it's about how well it responds to change.
Also, from the sounds of it, it doesn't seem like a distributed system. Client-side JS is by its nature a distributed system, dealing with network partitions all the time because end-user internet connections are unreliable.
There's a reason linters/smell-detectors try to catch things like unexpected type coercion, and there's a reason the web software industry is aggressively moving toward typed languages (JS -> TypeScript, for example).
If you tell a computer something it doesn't understand, it should tell you that it doesn't understand. There's no scenario where "just guess what you think I wanted to do and then do it" is a safe or reliable way for a program to run. By definition, sometimes it will work as intended and sometimes it won't. That's a bug. It's the worst kind of bug, actually: silent and undetectable until you catch the problem in the final output.
That's partly an argument for Flow or the eventual strict null flag in TypeScript.
That being said, that was my first reaction too. Null safe code is so important. How you do it (be it with Flow, Lodash, whatever), that doesn't matter, but I do find myself leaning toward libraries over native when payload size doesn't matter too much because of this.
A combination of Flow, Ramda and Sanctuary (for Maybes) if you want to be a bit more niche can give some pretty amazing results.
The need for Lodash and Underscore is diminishing, but not because of JS getting native support for a subset of utilities, but because there are already better alternatives. Utility libraries like Ramda[1] or pointfree-fantasy[2] with related modules which offer a better support for FP idioms and more useful higher-order functions are what made me drop Underscore.
I think that, with a language with such tiny stdlib, there always be a need for some kind of utility belt-style library ("if only for convenience", as another commenter write, which is actually the main point of such libraries). It's just a matter of choosing the best library for your coding style and project at hand. It may be a hassle, but an alternative is an stdlib the size of Python's - and I'm not sure if it's a good direction.
The Ramda API changes with every version. If you don't pay attention for a few months and accidentally upgrade (Ramda doesn't seem to want to do Semver, I'm not sure why), then suddenly half your code is broken.
We depend heavily on Ramda and I like its API better than Lodash and friends (it's more guessable, more consistent), but with the changes it keeps having I regret choosing it.
Our core product is currently on a Ramda version that's so old that there's no online documentation published for it, so we just look in the source. We ought to upgrade but I'm not convinced all code paths that use R are sufficiently covered by automated tests.
Lodash does a good job with version number, but upgrades are pretty damn dangerous (unless you feel with sticking to the same version forever).
The worse example was when they changed clone to be shallow from deep. Most code worked (shallow clone is a common case), but not quite all of it. Good luck finding which part of your code depended on deep cloning, even with tests.
So don't think you'd be able to upgrade much more often.
I mentioned Ramda without having used it very much and I didn't know about the frequent API changes.
From my personal experience, I got very good results using LiveScript with its default Prelude.ls lib. LS supports nearly all FP-related constructs and syntax known from many other languages, and Prelude.ls, while smaller than Ramda, is quite complete, too. That's not for every project, though: introducing another language is a bigger risk than introducing a single library.
It's really, really nice, but missing a lot of stuff Ramda has to get serious about FP. For example, lenses-like constructs in lodash are pretty damn limited (you have get/set, but...)
I find lodash infinitely useful to this day. It even provides a build with auto-curried, iteratee-first, data-last functions if FP is your jam. Just require it like this:
Instead of searching for the compatibility of each individual function I would much rather just use a custom build of Lodash with the collection of functions I need (which can even be handled by your build process now and some tree shaking). And I get a lot of utility functions with it too that I use a lot like Throttle and Debounce.
Lodash has published packages with their individual functions, so you can strip down to what you need (and put together your own build if you so desire). For example:
core-js is the truck factor single point of failure of modern JS development.
There's not many good alternative (there's a lot of alternative, but none that are as comprehensive), essentially has ONE person really working on it (and threatening to stop imminently), and the code is unreadable, so hopping into it once said maintainer goes poof will be tricky.
We still depend on it (no great alternative), but it's a ticking time bomb IMO.
'third party JS' is really a synonym for 'third party hosted JS' and they're not a very exact match. You might block mixpanel but I can make my own analytics.
Well you can avoid that with an inline script that checks for it and includes it from a local path if it fails.
But that's just more waiting, and you still end up downloading the ENTIRE script (unless you get REALLY fancy with webpack in a way I don't even want to think about...)
> But that's just more waiting, and you still end up downloading the ENTIRE script (unless you get REALLY fancy with webpack in a way I don't even want to think about...)
I have a friend who has done, in production, exactly what your subconscious is afraid of. I should get him to do a write up sometime: it's both amazing and horrifying at the same time...
I've thought about it a bit before, and always ignored it because there'd be no real benefit.
But i could see writing a crazy webpack config that breaks out all my lodash functions into their own bundle, then tries to add a script tag to the CDN version, doing some quick processing to determine if it's in the cache, and if it's not falling back to including my own custom-built script tag (and canceling the request to the CDN version).
I'm sure it wouldn't provide any benefit to anyone, but it would be a fun code-golf exercise!
If he is building his code then he is probably bundling and minifying his entire client source together and being selective just keeps the build code smaller. Having one request for all of the client is more preferable to some. Anyways, I'm just saying I don't think he is just building a minified fork of lodash.
We do plenty fine with just `import get from 'lodash/get', however we're fairly eager to upgrade to webpack to to take advantage of it's tree-shaking capabilities.
If you only need a couple of functions though, and makes your JS bundle just a few bytes bigger, getting the whole thing from the CDN is kind of silly.
Especially for those with empty cache (need to make 1 extra DNS request, download 1 extra file, which is mostly unneeded, etc).
Yup, this is my biggest issue with JavaScript's built-in functional utilities. It just totally ignores treating objects like dictionaries/maps. Even ES6 Maps/Sets lack functional features.
I think he's referring to `Object.keys(object).reduce`. We polyfill `Object.entries` so we do `Object.entries(object).reduce((result, [key, value]) => ...)`
>Important: Note that the native equivalents are array methods, and will not work with objects. If this functionality is needed, then Lodash/Underscore is the better option.
https://github.com/cht8687/You-Dont-Need-Lodash-Underscore#_... - the Array.prototype.find() method that's suggested has no support on Android's stock browser or Chrome for Android. That's a huge chunk of the browser market, but there's no mention of it on the list. In fact, there's no consideration for mobile at all on any of the suggestions.
If you're going to use any of the suggestions, check MDN or CanIUse first.
I understand why, and once upon a time I was the guy who would commit code where I replaced jQuery and underscore (at the time) calls with native calls. It works, and is totally fine. But what was the point? Some kind of optimization? It was pretty pointless in hindsight and just made my coworkers trouble. Now all my JS code is Ramda/lodash all the way, and it's great (although I opt for Clojure/script when possible)
These emperor has no clothes posts are very popular, and some may ask what's the point. It's a question worth considering. I think by instinct people especially programmer/engineer types like the idea of purity.
But if you consider that every decision must be put through a cost/benefit analysis, I think the appeal is that - I (or anyone in the future that will ever touch the code) must learn JS. OK. Now they must also learn framework X. That's the added cost.
Our memory is limited too, like a computer chip. When I know JS I can instantly access what I need. If you think of a Google or cheat sheet lookup like a disk access, well memory is faster than disk.
My rationale is that any one of these functions is probably going to be put into a utility function that's documented and named already, and of course tested with some optimizations, in some file somewhere in my code base. If it's useful enough, it'll probably be there. And while I don't often do front end, having these utilities support older versions of browsers or if there exist quirks in certain versions of browsers that render the solution nonviable is just not something I want to spend time caring about. When I did Java, the same articles would come out about Guava and Apache Commons.
One should not be afraid to use battle tested libraries, but don't bring dependencies for no reason.
Is that really true, though? I mean, there most probably is some theoretical limit how much data can fit in a structure of particular shape composed of particular number of neurons, but are we really hitting that limit? Are we even capable of hitting this limit after a lifetime of learning and experiencing? It may feel that way, but it could be a side-effect of some focus-switching and memory-compressing heuristics implemented by the brain.
What I mean is that there are obviously people who learned both JS and a framework, so why wouldn't anyone be able to do this? And I can guarantee that, once you learn it, both JS and a framework fit in your 'cache' perfectly. Actually, I felt no difference between recalling things before I learned a framework and after.
In other words, learning a new framework should be possible for anyone. And programmers are expected to continuously learn new things anyway, it's not "added" cost, it's just part of the job. From this point of view, I can't see any problems with introducing a library, given that it's a good library (docs, active maintainers, community, etc.)
> If you think of a Google or cheat sheet lookup like a disk access, well memory is faster than disk.
I like this analogy - not just its application to the current conversation, but because I consider myself to be understanding of a topic when I no longer have to look it up, but instead I know the topic in principle and all I lookup are minor details (maybe like an address in memory to find something on disk?)
At this point I would consider JS purity to be the One Standard Prelude with all the usually functional goodies in one nice package. You know, with real tests and whatnot. Underscore/Lodash comes pretty close to that.
> I (or anyone in the future that will ever touch the code) must learn JS. OK. Now they must also learn framework X.
Well you can forget the parts of JS covered by the framework. I don't really care about the native Array.map or whatever functions, because they won't always behave as I expect, while the lodash (or equivalent) will. I can't recall off the top of my head how to use XMLHttpRequest. But I don't need to and haven't needed to in a while, because every project I've worked on uses jQuery or another framework which provided better ajax methods.
The main point of this (and similar - you-dont-need-jquery et al) is [should be] for those people who need a tiny piece of functionality that would take maybe a couple of lines of code, and import a dependency to deal with it. That's not too unusual - in a similar vein I spent a fair part of last year tearing hundreds–thousands of Kb of jQuery plugins (jQuery UI put in for one thing was pretty common) out of Rails apps because of this.
Lodash I'd say is unequivocally great (particularly the FP version, which I prefer to Ramda now), but these kind of posts aren't really for those who really need the full suite of functionality. Or at least it's very useful for those starting out on a project, who can then add in Lodash/whatever if/when the need arises, rather than reflexively adding it at the start.
I literally just got into this debate with a coworker yesterday. They didn't want lodash or immutable.js or any utility library in our new project at all, citing their size and the relative ease of doing it all natively. We use lodash or immutable or both in all our other projects but we always start out having these "purity" discussions until cooler heads prevail when we realize it's more important to optimize for convenience than it is to optimize for purity or even size. We're transpiling a future version of our language into an older version of our language just to get it to run, I think using lodash adding size is the least of our problems.
Two things from my memory. 1. They hit some bad PR when they pushed a badly broken release in a point update. Turned out that their testing was not robust, while Lodash's is excellent, which further hurt them (this is when I first remember hearing about Lodash) 2. Lodash adapted better to the module landscape, especially by splitting all of their functions into separate packages.
It's great, but there are better alternatives. Ramda[0] is great, auto-curries everything and has an api similar to Clojures, which is really great. Lodash[1] is a fork of Underscore, with some OOP-isms replaced for FP-isms, which I and many others prefer.
(arr || []).filter... Is something I do when I'm not sure if the array is null or not. Looks shitty but works fine. (Now if arr is not falsy and not an array either, this blows up. But I don't know what lodash does then too)
To be honest, I'd rather check / handle nulls rather than to rely on an external library semantics. They might also change it tomorrow and upgrading would be a pain.
Still a pretty conservative breaking change. Something you'd expect from a committee of people. Lodash has more wiggle room to make absurd changes in future.
My main concern with Lodash is not necessarily the size, but that it adds to the API surface a new developer has to learn when entering a project. You get a lot more value from the cost of learning a dozen API methods from React compared to having a developer looking up pluck, pull, xor, zip, flow, etc from lodash.
For the sake of 25KB for the full Lodash library, is it really going to make much difference? That size will be eclipsed by just a single small image on your website.
Development is all about tradeoffs. Liberal use of Lodash can make for clean code that abstracts away subtle browser differences. For a small download size it sounds like a great tradeoff to me.
If the 25KB are packaged with each library you add to your webpage, + ads , + analytics then maybe.
One often doesn't need lodash. In fact, the only methods one does need are Object.keys to get the keys of an object and Array.prototype.reduce to iterate over an array, 90% of the time.
But yes, difference, zip, chunk, assign... are semantic and make for cleaner code. And yes flatten, xor are non trivial to implement, and the code one doesn't write is the code one doesn't have to test if already tested.
Of course, the best solution would be for the ECMA spec to implement most of lodash methods. I would also like to see an official way to curry, the same way Function.bind allows partial application.
You can even install each Lodash method separately as it's own module or use Rollup/Webpack 2 to tree-shake and ignore unused code. Size is no longer an issue with Lodash.
Talking about the size of a lib in kb will fall on deaf ears here at hacker news. It's all about micro libs and nothing you don't need. Never mind the first few images on the site you are working for change daily and come out to several megs each will completely eclipse any library that changes once in blue moon.
The stdlib (and whatever polyfill you're using for old browsers) also abstracts away subtle browser differences. However stdlib looks more like the rest of JS.
Totally agreed about size though: it's tiny. There are other reasons to prefer stdlib but size isn't one of them.
Well, Lodash is focused on performance, and 3.X.X supports IE8 and a lot of older browsers, it's a pretty solid util library. If you add in all the good utilities function. As long as you use them a lot in your code, Lodash is definitely worth it
A nice thing Lodash has that Native doesn't is the 'matches' shorthand for things like find. I find it easier to read and quicker to write than a callback.
I don't use Lodash, but I remember reading that it does lazy evaluation[1]. For example, if you map an array, then take the last 3 items, it only calls the map function for those 3 items.
Does anyone know if V8 can do any optimizations like this for native Array functions? That would be a good thing to know before downloading lodash.
This cannot be done with native array functions as they exist now.
The reason Lodash is able to do this is because the collection is wrapped in an a special object that keeps track of all of the functions you call on it. Those functions don't run until you call .value() on the whole pipeline that you have setup, and then the optimization happens.
Note that this requires a separate API. One API returns your results immediately, the other (lazy) API instead returns a wrapper object after each call without yet doing the mapping/filtering/etc, ready to add more instructions to the pipeline that you must ultimately call .value() on in order to actually run.
Native functions don't have this separate lazy API that does the wrapping with this special object, and don't have/return an object with a similar .value() method.
Getting rid of unnecessary dependencies can have a huge impact on site download performance, especially on mobile. Even in the case of having the site cached, every resource you link will have to perform an HTTP request to at least receive NOT-MODIFIED. It can add up, especially if you're on a semi-spotty connection and trigger TCP congestion avoidance.
Don't miss the forest for the trees. Re-implementing _.forEach, _.map, and _.reduce to do something like loop over objects yourself is going to most likely end up taking more code than just using those functions from lodash.
Not to mention that you most likely won't have the same amount of testing, speed/optimization, or "familiarity" across devs with a home-grown solution.
Yeah, don't pull in lodash if you are making a small library and you only need it once, but if you avoid lodash in a codebase where you use it a non-trivial amount, it's most likely going to be worth it.
Plus with modern (at least to js land) tree shaking, and/or the way that lodash can package each function individually, you don't need ALL of lodash in order to use one or 2 functions.
I do not agree at all. What is so hard about for loops? I do not agree that using the native-to-the-platform looping constructs will create as much code as half a megabyte. I'm looking at it right now: the full build of Lodash is 503KB. That's also half-a-meg of code that needs to be parsed by the JS engine. And nothing else you said has anything to do with site performance. Do users care if it took you 10 seconds versus 11 seconds to write a particular looping construct? Do clients care?
Well you conveniently skipped right over the part of my comment where I said that you shouldn't be using the whole 500kb lodash library... (also, it's disingenuous at best and borderline lying to say that lodash is 500kb. The DEVELOPER version is 500kb, the production version is 67kb before gzipping, and 22kb after gzipping)
If you are only using _.forEach, then you should only include _.forEach, which comes out to about 10kb before gzip compression, 5kb after (i just built it myself to test). And all but about 1.2kb of that is "core" code, so if you add additional lodash functions, most of them will increase the code size by about 2kb at the most.
That means that for an overhead of about 8kb, you can start adding extremely useful functions all throughout your codebase with very little overhead. 1.2kb to easily and quickly iterate over an object using the key and the value in each block;
_.forEach(obj, function(value, key) { // do stuff })
Pure js:
Object.keys(obj).forEach(function(key) { var value = obj[key] // do stuff })
Not that much of a difference space wise, but the performance is terrible if you are iterating over a large object, and by using Object.keys you exclude some UAs.
Now let's look at _.find in an object...
_.find(obj, { 'age': 1}) // returns the first object (in the object) that has a property 'age' that equals 1.
Pure JS:
var objKeys = Object.keys(obj)
var retval
for(var i; i < objKeys.length; i++) {
var value = obj[objKeys[i]]
if ('age' in value && value.age === 1) {
retval = value
break
}
}
And that's untested, will only work for an object of objects, has unknown performance, and sure as shit took me more than 1 extra second to write (about 5 minutes once i looked up what the compatibility of the 'in' operator is).
You could use the native .find() function, but that won't work on chrome for android, android browser, IE (any version), safari 7 and older, and opera. Plus you'd still need something like Object.values() in order to convert the "parent" object into an array to search through.
Plus what if i want to find an object in an array and in another object? (which I actually just had to do, which is why i thought of this use case) Well now you've just doubled your code, doubled the amount of tests you need to write, doubled the chance for random bugs to crop up (what happens if one of the object values is undefined? What happens if obj is null?).
With stuff like the lodash cli where you can make builds that literally only contain what you need, the lodash babel plugin that will allow you to require lodash functions and it will build only what you need, or webpack which will tree-shake away everything you don't need (and actually does a better job than the lodash-cli does!), there's no need to avoid lodash like the plague.
I'm not saying include all of it, i'm not saying use it in everything, i'm saying that you should use your brain and include it if it's going to increase the speed that you can develop, reduce the number of bugs you will face, simplify your codebase, and won't negatively impact your users. (after all, a single image will blow the 5kb of js you'll be saving out of the water)
I skipped over it because it's a solution in search of a problem.
I think developers should be expected to understand the native constructs of the language they use. You previously also talked about "being more familiar to onboarding developers". Why should Lodash be more familiar than the native looping constructs to anyone? Why would Lodash be more familiar to a developer you're onboarding who is more used to some other library completely?
If you're not used to using Lodash, then it would not be a time savings to use it, as you'd have to spend time learning its idioms. It means you've probably learned other idioms, instead (perhaps, you know, the ones you should have learned to be able to claim to know how to use the language). So if you know the native idioms, there isn't a point to learning Lodash.
You had to take time to look up operator-in because you're not used to using operator-in. Regardless, it was a waste of time. What's wrong with the following code?
for(var k in obj){
if(obj[k].age === 1){
// there you go
}
}
And I mean problems with that code itself, not problems that are actually a problem with upstream code? And how much of the problems with that upstream code are because your project is a Gordian Knot of 3rd party dependencies?
You default assume other people's code is better than yours. In the last 20 years of software development, I've seen code better than mine in several standard libraries. But the JS ecosystem is not one of them.
>You had to take time to look up operator-in because you're not used to using operator-in
I'm using in (`if('age' in value)`), but not for..in. There's a difference. I don't need to use that (you didn't in your example, even though you should have), but then my code would blow up in many of the same cases as yours will.
Anyway, what's wrong with that code is you'll need a hasOwnProperty check, you'll need to break out of the loop when done, it blows up if 'age' isn't a property on one of the objects in the object, it blows up if one of the properties of obj isn't an object, it blows up if obj is null/undefined, and it will deoptimize the entire function it's in in most cases in V8.
To at least fix the bugs that yours has that mine didn't, your code should be:
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var val = obj[key]
if ('age' in val && val.age === 1) {
// there you go
}
}
}
but again that still needs some undefined/null guards at a few spots, and it needs to be able to handle both an array and an obj at where obj is to really be an equivalent of what i was using _.filter for.
>You default assume other people's code is better than yours.
No, I know the lodash code i'm using is better than mine because i've looked at it, i've looked at it's test coverage, i've looked at it's performance numbers, and i've looked at it's usage.
Also...
>I skipped over it because it's a solution in search of a problem.
Then why did you skip this part in my last comment:
>it's disingenuous at best and borderline lying to say that lodash is 500kb. The DEVELOPER version is 500kb, the production version is 67kb before gzipping, and 22kb after gzipping
You made me curious, so i actually took a look at my current in-development app i'm writing that i include lodash. The grand total from all lodash functions is 22k un-gzipped, and 7k gzipped.
For that i've significantly reduced my code complexity, increased compatibility, reduced the amount of tests i needed to run and maintain, and improved performance in one pretty out-there edge case.
You don't need to use lodash, and in many cases you shouldn't. But don't act like your applications are better than others in any way because you aren't using lodash.
Because he's a better programmer than you are, and he knows it.
> It's just a loop.
There are many kinds of common patterns in loops, abstracting them into resuable higher order functions, aka a collections api is rather basic functional programming; "it's just a loop" is a blub phrase of someone who doesn't see the point because they have tried it long enough to understand the value in a different paradigm.
I know it's currently thought as so, but functional programming is not the be-all, end-all paradigm. If you've been around for a while, you'd have seen the ebb and flow a few times already.
No one said it was, but in this use case it's far superior to hand written loops. Virtually all modern programming languages have a functional collections API precisely because of this advantage. If you're still writing loops by hand, in nearly all cases, you're stuck in a less effective procedural programming mindset and have simply stopped updating your skills beyond the basics.
If you've been around for a while, you should have already accepted the benefits of functional programming in the places it's most effective and banished low level procedural programming of loops except in cases where extreme optimization is required. There's no eb and flow here, higher order map/reduce/filter style has been the best way since the 60/70/80's when Lisp and Smalltalk were doing it.
I would consider switching from underscore if there was a reliable polyfill so that I wouldn't need to be aware of all these browser compatibility constraints. That way I can just drop the polyfill in a couple of years instead of replacing every underscore use.
This seems like a great post on why you should be using lodash.. especially all those IE unsupported things. Like mentioned in the comments, it's not just about having one or two of these features, it's about how they compose so nicely in lodash
I think the point of the page is to demonstrate that for many, many lodash functions, there are direct equivalents in native JS - not rewritten features.
That's interesting why some of functions (_.repeat, _.findIndex) were marked as "Not supported" in Opera but all of them are supported in Chromium-based browsers. Did the author only consider Presto-powered Opera?
Would it be possible to build an tool that scans your code for simple helpers you wrote yourself or that you include from dependencies and replace them with equivalent functions of lodash or native?
"However, when you are targeting modern browsers" - possibly one of the worst mistakes companies do. Then they decide to support older browsers after customers complaints. Then shit goes down!
It depends. Most of the really bad internet explorers are officially deprecated by now, so unless your customers are big enterprises with badly managed IT, it's completely fine to scrap support.
You might also not need to write any code at all, just set up a form for customers to send in their requests and hire a bunch of people to manually process them. Bam! 0K, no dependencies, almost instantly working web service.
But that's not your priority when building website, it doesn't matter if your JS is 60Kb or 20. No one cares how much dependencies you build system pulls. You need tested, reliable and working libs to do their job, and not write your own routines for 3 months.
All kidding aside, a lot of our lodash code ends up looking something like this:
That code clearly expects that xs is an array of objects. However, we might occasionally end up with xs being undefined, or with xs being an array but one of the elements is null, etc.Most of the time, we want our function to just swallow those errors and return an empty array in such cases. This is exactly what lodash does, but if we tried to call xs.map(...) in that case we'd get an error. Similar caveats apply for grabbing the foo attribute if one of the array elements ends up being null or something.
For this reason, I recommend continuing to use lodash almost all of the time, even when there's a native Javascript method available.