JavaScript's utility _ belt

Overview
                   __
                  /\ \                                                         __
 __  __    ___    \_\ \     __   _ __   ____    ___    ___   _ __    __       /\_\    ____
/\ \/\ \ /' _ `\  /'_  \  /'__`\/\  __\/ ,__\  / ___\ / __`\/\  __\/'__`\     \/\ \  /',__\
\ \ \_\ \/\ \/\ \/\ \ \ \/\  __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\  __/  __  \ \ \/\__, `\
 \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/
  \/___/  \/_/\/_/\/__,_ /\/____/ \/_/ \/___/  \/____/\/___/  \/_/ \/____/\/_//\ \_\ \/___/
                                                                              \ \____/
                                                                               \/___/

Underscore.js is a utility-belt library for JavaScript that provides support for the usual functional suspects (each, map, reduce, filter...) without extending any core JavaScript objects.

For Docs, License, Tests, and pre-packed downloads, see: https://underscorejs.org

For support and questions, please consult our security policy, the gitter channel or stackoverflow

Underscore is an open-sourced component of DocumentCloud: https://github.com/documentcloud

Many thanks to our contributors: https://github.com/jashkenas/underscore/contributors

This project adheres to a code of conduct. By participating, you are expected to uphold this code.

Comments
  • The Big Kahuna: Underscore + Lodash Merge Thread

    The Big Kahuna: Underscore + Lodash Merge Thread

    Dear Everyone,

    JDD got in touch with me over email this morning, inquiring about the possibility of merging Underscore and Lodash together. I think it's high time to talk about the possibility again.

    Some initial points for discussion:

    • The Name: "Lodash" is very cute, but keeping "Underscore" makes the most sense, both for historical and _ variable reasons. Because npm doesn't allow name redirects (AFAIK), the merged project would be available as a module under both names for the foreseeable future.
    • Lodash is currently > 12k lines of code, and Underscore is 1.5k. I understand that part of that is JSDoc noise, but it still seems potentially problematic and worth talking about.
    • Documentation style. Ideally I'd like the Underscore docs to be written even more conversationally than they are at the moment.
    • We should keep nicely annotated source code as a priority.
    • And then the actual main work — I'm not familiar with the Lodash codebase. What parts of it do folks have objections to merging? What might not fit in well within a merged Underscore umbrella? Anything? Maybe not?

    Edits in response to comments below:

    • Strict(er) SemVer. Totally fine with me. I'm still of the opinion that true believer SemVer isn't something that actually exists in the real world (most changes are subtly backwards-incompatible in minor ways), but I have no problem praying to this golden cow to keep the philistines happy ;)
    • "Governance Model": I don't think we need anything fancy here. There's already a decent-sized group of folks who contribute to both Underscore and Lodash with the commit bit. They can keep it. I do think we'd need a clear mission statement about the spirit of what the library should be. Utility libraries have a tendency to grow to include the kitchen sink, and the more we can successfully keep things focused, the better.
    • _(obj) wrapper chains lazily by default. (Breaking change). Sounds fine to me.
    • How does Underscore-Contrib fit in to all this?
    • Folks. Please STOP +1-ing, :+1: -ing and meme-ing GitHub tickets. We get it, but it makes tickets hard to have conversations on. Contribute with something substantive instead.

    Although it would be nice to keep the bulk of the discussion here ... there's also a Contributors-only cross-thread here: https://github.com/jashkenas/underscore/issues/2184

    breaking change 
    opened by jashkenas 204
  • Add _.exclude(object, *keys)

    Add _.exclude(object, *keys)

    Return a copy of the object, filtered to exclude the blacklisted keys (or array of valid keys).

    Similar to _.pick, but using blacklisting instead of whitelisting which can be more appropriate in some cases.

    enhancement 
    opened by bmaland 69
  • Fix the versioning

    Fix the versioning

    1.7.0 introduced loads of breaking changes.

    The number of dependant modules which are now broken as a result is huge, personally I think that 1.7.0 should be killed (removed from npm) and 2.0 released - the longer the delay the harder it will be to do this.

    underscore.js is solely consumed via package managers that mandate the use of semver, you may personally not like semver but that is what is used by the installers to determine compatibility. Last time this was brought up you stated that if you used semver then we would be on underscore version 47 now - well that is much better than having broken code everywhere and lodash has managed to keep the version number below 4.0.0 without breaking everyone's code.

    breaking change 
    opened by danielchatfield 68
  • Add a code of conduct doc.

    Add a code of conduct doc.

    Adding a code of conduct to help avoid issues over moderation and behavior. This will spell out what is and what isn't acceptable to help foster a better environment and prevent infighting on how moderation should be handled.

    fixed enhancement 
    opened by jdalton 65
  • better isXXX checks

    better isXXX checks

    The current tests allow for too many false positives for my liking. This also changes _.isNumber(NaN) from false to true. It really should have been true in the first place.

    breaking change 
    opened by michaelficarra 56
  • Each iterates badly when it contains the `length` field

    Each iterates badly when it contains the `length` field

    Each iterates correctly on most objects:

    _.each({a:2}, function(value, key) { console.log('value: ' + value + ', key:' + key) });
    value: 2, key:a 
    

    It fails if the object contains a length property:

    _.each({a:2, length:4}, function(value, key) { console.log('value: ' + value + ', key:' + key) });
    value: undefined, key:0
    value: undefined, key:1
    value: undefined, key:2
    value: undefined, key:3 
    

    The source of the bug is that the object is considered an "Array-like": https://github.com/jashkenas/underscore/blob/master/underscore.js#L69

    breaking change duplicate 
    opened by sgenoud 46
  • _.isNumber(NaN) returns true

    _.isNumber(NaN) returns true

    As NaN stands for "Not a Number" the isNumber check in this case seems like it should return false. I notice from other discussions that this is in fact on purpose. Maybe the documentation should reflect this fact explicitly.

    fixed enhancement 
    opened by park9140 38
  • _.resolve: Simple dot & bracket notation parser

    _.resolve: Simple dot & bracket notation parser

    Recently I've been finding needs for simple evaluation of dot and bracket notation and I've been unable to find a good solution. At one point I considered using _.template for this but this seemed like overkill, and the performance and security overhead of using the underlying Function constructor (much like evil eval) make it very off-putting, at least for this behaviour.

    With _.resolve you can easily do the following:

    var data = { foo: 'bar', fu: [{ baz: 'bingo' }] };
    
    _.resolve(data, 'foo');
    // "bar"
    
    _.resolve(data, 'fu[0].baz');
    // "bingo"
    
    _.resolve(data, 'fu[1].baz');
    // undefined
    
    _.resolve(data, 'foo["length"]');
    // 3
    
    _.resolve(data, 'fizz');
    // undefined
    

    One thing I'd like to note is that, due to the lightweight nature of the code, the following patterns are valid in _.resolve but not in JavaScript:

    // Only numbers in dot notation would throw "Unexpected number" in JavaScript
    _.resolve(data, 'fu.0.baz');
    // "bingo"
    
    // Optional quotation marks (single or double) in bracket notation for cleaner/simpler paths
    _.resolve(data, '[foo][0]["baz"]');
    // "bingo"
    

    Before I start copying this code to all my projects I thought it would be best to see if this feature would be welcomed in Underscore. Possible uses:

    <body data-log="style.direction" style="direction: ltr">
      <a href="http://underscorejs.org">Underscore.js</a>
      <script>
        if (_.resolve(myLibrary, 'options.custom.parseLogs')) {
          _.each(document.querySelectorAll('[data-log]'), function (element) {
            console.log(_.resolve(element, element.dataset.log));
          });
        }
      </script>
    </body>
    

    The idea of parsing either user-defined, untrusted, dynamic, or hard coded (e.g. property paths taken from element attributes or a JSON response value) is obviously the main benefit here as raw JavaScript will clearly out-perform standard usage (i.e. var value = foo.bar.fu.baz will be more efficient than var value = _.resolve(foo, 'bar.fu.baz')).

    Finally, I can see this being a great asset when couple with something like _.memoize.

    Update:

    Support for bracket notation has been removed in order to optimise dot notation, which is more common and user-friendly.

    Also, leading and trailing dots are no longer ignored as it should be up to the user to ensure that the path argument isn't malformed.

    contrib 
    opened by neocotic 36
  • Cut support for IE6-8 and other older browsers.

    Cut support for IE6-8 and other older browsers.

    With jQuery and Dojo moving away from supporting older browsers (FF3, IE6-IE8, older Opera) I think Underscore is in a good position to drop support as well.

    Devs needing older browser support can always use a compat build of Underscore or Lo-Dash (no troll), which has extensive older browser support and a build system to allow selectively supporting older/newer environments.

    wontfix enhancement 
    opened by jdalton 34
  • [Request for feedback] Add deep property access via dot notation

    [Request for feedback] Add deep property access via dot notation

    This pull request should be safe to merge, but it lacks support for escaping characters in path strings. In most cases this is fine, since the more explicit array syntax can be used as a fallback. However, in the case of _.matches and friends, there is no such fallback available.

    Should we explore an escape syntax (similar to Lodash's) which allows for complex paths via a string syntax? If so, should it be solved as part of this pull request?

    As part of this pull request we get _.get essentially for free.

    enhancement question breaking change 
    opened by captbaritone 32
  • Underscore does not follow SemVer

    Underscore does not follow SemVer

    It would be extremely useful if Underscore followed Semantic Versioning. It currently does not, as things like breaking bindAll and removing unzip have occurred without major version bumps.

    This would allow library consumers to upgrade bugfixes and performance improvements and gain additional functions without fear of code breaking.

    duplicate 
    opened by wavebeem 32
  • Possibility to update a nested object's property in a non-mutable way

    Possibility to update a nested object's property in a non-mutable way

    Currently I am using something like:

    const foo = bar.map((baz) => {
      return {
        ...baz,
        qux: baz.qux.map((quux) => {
           return {
             ...quux,
             aNewProperty: 'yay!'
           }
        }
      }
    });
    

    To make the point clear, it would be ideal to have something similar to the set method in lodash:

    const foo = bar;
    set(foo, 'bar.baz.qux.quux', { bar.baz.qux.quux, aNewPorperty: 'yay!' });
    

    Note: I am not aware of how lodash and underscore are related but the current project I'm working on is using underscore. And at this time it's not possible to convert to lodash just for this single use-case.

    enhancement starter 
    opened by StudioSpindle 3
  • Utility functions for prototype-based programming

    Utility functions for prototype-based programming

    While class emulation is widespread nowadays, prototypes are JavaScript's true vehicle of inheritance. The following snippets of code, which contain equivalent pairs, demonstrate that prototype-based programming is more fundamental and explicit:

    // define constructor and prototype
    
    // class emulation version
    class BaseConstructor {}
    const basePrototype = BaseConstructor.prototype;
    
    // prototype version
    const basePrototype = {
        constructor() {}
    };
    const BaseConstructor = basePrototype.constructor;
    BaseConstructor.prototype = basePrototype;
    
    // inheritance
    
    // class emulation version
    class ChildConstructor extends BaseConstructor {}
    const childPrototype = ChildConstructor.prototype;
    
    // prototype version
    const childPrototype = Object.create(basePrototype, {
        constructor() { return basePrototype.constructor.apply(this, arguments); }
    });
    const ChildConstructor = childPrototype.constructor;
    ChildConstructor.prototype = childPrototype;
    
    // instantiation
    
    // class emulation version
    const baseInstance = new BaseConstructor();
    
    // prototype version
    let baseInstance = Object.create(basePrototype);
    baseInstance = baseInstance.constructor() || baseInstance;
    

    Since prototypes are so fundamental, I believe there is space in Underscore for utility functions that make prototype-based programming more convenient. In draft, I propose the following. A real implementation would need more sophistication for ES3 compatibility, performance and possibly corner cases.

    // get the prototype of any object
    function prototype(obj) {
        return Object.getPrototypeOf(obj);
    }
    
    // mixin for prototypes that lets you replace
    //     var instance = new BaseConstructor()
    // by
    //     var instance = create(basePrototype).init()
    // Of course, prototypes can also skip the constructor and directly define their
    // own .init method instead.
    var initMixin = {
        init() {
            var ctor = this.constructor;
            return ctor && ctor.apply(this, arguments) || this;
        }
    };
    
    // mixin for prototypes so you can replace
    //     var instance = create(prototype).init()
    // by
    //     var instance = prototype.construct()
    // Of course, prototypes can also directly define their own .construct method
    // instead.
    var constructMixin = extend({
        construct() {
            return this.init.apply(create(this), arguments);
        }
    }, initMixin);
    
    // standalone version of the construct method, construct(prototype, ...args)
    var construct = restArguments(function(prototype, args) {
        return (prototype.construct || constructMixin.construct).apply(prototype, args);
    });
    
    // inheriting constructor creation for class emulation interop
    function wrapConstructor(base, derived) {
        return extend(
            derived && has(derived, 'constructor') && derived.constructor ||
            base && base.contructor && function() {
                return base.constructor.apply(this, arguments);
            } || function() {},
            // The following line copies "static properties" from the base
            // constructor. This is useless in prototype-based programming, but
            // might be important for class-emulated code.
            base && (base.constructor || null),
            { prototype: derived }
        );
    }
    
    // mixin for prototypes with a constructor so you can replace
    //     class ChildConstructor extends BaseConstructor {}
    // by
    //     var childPrototype = basePrototype.extend({})
    // Of course, prototypes can also directly define their own .extend method.
    var extendMixin = {
        extend() {
            var derived = create.apply(this, arguments);
            // note: using the pre-existing standalone _.extend below
            return extend(derived, {constructor: wrapConstructor(this, derived)});
        }
    };
    
    // standalone version of the extendMixin.extend method, named differently in
    // order to avoid clashing with the pre-existing _.extend. inherit also seems a
    // more appropriate name for this function when used standalone.
    var inherit = restArguments(function(base, props) {
        return (base.extend || extendMixin.extend).apply(base, props);
    });
    
    // collection of mixins for quick and easy interoperability with
    // constructor-based code
    var prototypeMixins = {
        init: initMixin,
        construct: constructMixin,
        extend: extendMixin,
        all: extend({}, constructMixin, extendMixin)
    };
    

    Note how construct and .extend/inherit are both based on create. This is no coincidence; in prototype-based programming, there is no fundamental distinction between prototypes and instances. Every object can have a prototype and be a prototype at the same time. From this point of view, .extend/inherit is just a special variant of construct that enables interoperability with class-emulated code. Without any class-emulated legacy, prototype, create and .init/construct would already cover all needs.

    With the above utilities in place, we can revisit our examples from the beginning and find that the prototype-centric code is just as concise as the class-centric code:

    // define constructor/prototype
    
    // class-centric
    class BaseConstructor {}
    
    // prototype-centric
    const basePrototype = {};
    
    // inheritance
    
    // class-centric
    class ChildConstructor extends BaseConstructor {}
    
    // prototype-centric
    const childPrototype = inherit(basePrototype, {});
    
    // instantiation
    
    // class-centric
    const baseInstance = new BaseConstructor();
    
    // prototype-centric
    const baseInstance = construct(basePrototype);
    

    Related: https://github.com/jashkenas/backbone/issues/4245.

    enhancement 
    opened by jgonggrijp 1
  • Uncaught TypeError: Cannot read property '_' of undefined

    Uncaught TypeError: Cannot read property '_' of undefined

    This is my first time posting a problem here, so if I'm doing something wrong, sorry! I'm getting this error and I don't quite get it.

    Here I am again with a problem I can't solve ! I'm getting this error and I don't quite get it.

    enter image description here

    After opening the DOM and reading the whole error and a lot of hours spend on Google, the only thing I got was that it looks like the error is triggered by this line of code:

    const [user, setUser] = useState(null) This is the Code where this line is :

    import React, { useState, useEffect } from 'react';
    import { StyleSheet, View, Text, Image, FlatList } from 'react-native';
    import { connect } from 'react-redux';
    
    import firebase from 'firebase';
    require('firebase/firestore');
    
    function Profile(props) {
      const [userPosts, setUserPosts] = useState([]);
      const [user, setUser] = useState(null);
    
      useEffect(() => {
        const { currentUser, posts } = props;
        console.log({ currentUser, posts });
    
        if (props.route.params.uid === firebase.auth().currentUser.uid) {
          setUser(currentUser);
          setUserPosts(posts);
        }
      });
    
      if (user === null) {
        return <View />;
      }
      return (
        <View style={styles.container}>
          <View style={styles.containerInfo}>
            <Text> {user.name} </Text>
            <Text> {user.email} </Text>
          </View>
          <View style={styles.containerGallery}>
            <FlatList
              numColumns={3}
              horizontal={false}
              data={userPosts}
              renderItem={({ item }) => (
                <View style={styles.containerImage}>
                  <Image style={styles.image} source={{ uri: item.downloadURL }} />
                </View>
              )}
            />
          </View>
        </View>
      );
    }
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        marginTop: 40,
      },
      containerInfo: {
        margin: 20,
      },
      containerGallery: {
        flex: 1,
      },
      containerImage: {
        flex: 1 / 3,
      },
      image: {
        flex: 1,
        aspectRatio: 1 / 1,
      },
    });
    
    const mapStateToProps = (store) => ({
      currentUser: store.userState.currentUser,
      posts: store.userState.posts,
    });
    
    export default connect(mapStateToProps, null)(Profile);
    

    I'm making an Instagram Clone and if you need any other code I have, please tell me because I don't know what you all need to help me.

    question 
    opened by chin14 4
  • Add dark mode support for homepage

    Add dark mode support for homepage

    Tried adding dark mode support for the docs on the underscorejs.org homepage.

    Here's how it looks before and after:

    Screen Shot 2021-02-07 at 15 35 44 Screen Shot 2021-02-07 at 15 24 45

    The code is available in a very short gist here:

    doccy-dark.css

    To submit a pull request I am not sure where to add this... in index.html in source root? or in docco.css?

    In any case, this can be tweaked as required and added. Also, the logo lost the signature blue color, but can be avoided if an alternative logo image with white text is available.

    enhancement documentation starter 
    opened by gprasanth 2
  • Functional style

    Functional style

    This is a substantial refactoring of the Underscore source code, which I've been working on since shortly after the modularization (#2849). Before merging, I would like this to be reviewed extensively, by multiple people, in multiple ways and in multiple stages. Please see the final third of this post for review details.

    Edit: I need help from people who can contribute benchmarks based on real-world applications. Please leave a comment if you think you might be able to help!

    Goals

    • Over the years, the source code has lost a substantial portion of its former elegance. This is partly due to bugfixes and feature additions, but also substantially due to micro-optimizations of which the added value is often questionable. I wanted to bring back some of that former elegance, prioritizing a clean, readable, DRY code style over absolute maximum performance.
    • Making the code more DRY and more functional would also contribute to a smaller bundle size (#2060), so I made this an explicit goal as well.
    • Preparing the source code for future code sharing with Underscore-contrib and Underscore Fusion. I theorized that some of Underscore's internal logic could be factored out. Once factored out, it could potentially be made public, or at least be made stable enough to enable reuse in Underscore's sister projects.
    • Preparing the source code for adding new core collection types in the future, in particular Map, Set and iterators in Underscore 2.0. This can only be done in a maintainable way if there is a single source of truth about how to iterate any given collection. More about this below.

    Principles

    • No interface changes (yet), in order to keep distractions at a minimum. While the code changes contain some seeds for possible future interface changes (mostly additions), I wanted this branch to focus entirely on the implementation of the existing interface.
    • No assumptions. I measured the effect on minified+gzipped UMD bundle weight after every change and did extensive microbenchmarking in order to assess impact on performance.
    • Only meaningful changes. If, after measurement, it turns out that a change benefits neither readability nor bundle size nor performance, don't make the change. There are quite a few changes that I reverted for this reason. I have interactively rebased this branch several times in order to clean up history.
    • Mild performance degradations are acceptable. 99% of application code is not performance-critical; programmer productivity and code size are usually much more important. Neither of those interests is served by trying to squeeze every drop of performance out of the hardware. In the remaining 1% of cases, if a slight slowdown is really a problem (i.e., fairly trivial code in a hot loop), users can replace their call to an Underscore function by a simple hand-written loop and easily achieve much better performance than they ever could with a library function.

    Approach

    I started by asking myself which existing Underscore function should be the single source of truth about collection iteration. Initially, I considered _.reduce. It is a very general function and many other collection functions can be cleanly expressed in terms of it, for example _.map, _.filter and _.min/_.max. I also considered _.each, because it doesn't use an accumulator by default. _.each and _.reduce are equally powerful, in the sense that either can be cleanly expressed in terms of the other.

    I soon realized, however, that there is a function that can't be cleanly expressed in terms of _.each or _.reduce: _.find. Like the procedural for-in loop with a break option, _.find may stop iteration early. Otherwise, it basically does the same thing as _.each. All Underscore collection functions can be cleanly expressed using _.find, including not only _.each, _.reduce, _.map and _.min but also _.some, _.every and _.contains. For this reason, I have chosen _.find to be the one function that defines how to iterate a collection.

    Conveniently, _.find was already implemented by branching over the collection types: it calls _.findIndex on arrays and _.findKey on objects. These latter functions, in turn, are the single sources of truth about iterating arrays and objects, respectively (although the situation is a bit more nuanced with regard to arrays; more on this shortly). In Underscore 2.0, I plan to add _.findEntry for Map/Set and _.findIteration for iterators. By including these in _.find and implementing all other collection functions in terms of _.find, all collection functions would automatically support all five collection types in a consistent way.

    Besides using _.find in all collection functions, one of the first things I did was factoring out very general linearSearch and binarySearch functions. These are the actual single sources of truth on how to iterate/search arrays; _.findIndex, _.findLastIndex, _.indexOf, _.lastIndexOf and _.sortedIndex are all implemented using linearSearch and/or binarySearch under the hood.

    linearSearch is so general that I was able to substitute it for nearly all hand-written for/while loops in the source code. This proved very effective in both producing cleaner code and reducing the bundle size. However, I have reverted this in many places because function call overhead turned out to be costlier than I expected. It seems that even modern JS engines rarely perform inlining, if ever. After discovering this, I adopted the following "safe" rule to choose between linearSearch and a hand-written loop: if the loop body didn't previously involve any function call, keep the hand-written loop; otherwise, replace it by linearSearch or another Underscore function. In this way, the extra function call introduced by using an Underscore function can never slow down the loop by more than a factor two. I expect the slowdown to be less in real-world scenarios, because a loop body that purely consists of two function calls (i.e., with functions that don't do anything) is trivial. Also, loops that already involved a function call often already involved multiple function calls.

    I wrote an extensive microbenchmark suite to measure performance, in which each operation is repeated at least 3.6 million times and during at least a whole second. Please follow that link for details. All performance claims in the current post are informed by repeated measurements in Node.js 10, mobile Safari 14 and desktop Firefox 84 (on macOS) with those microbenchmarks. However, I believe that a final performance impact assessment should be made by comparing execution time in real-world applications and across a wider range of engines. More on this in the review section at the bottom of this post.

    With this overall approach in mind, I made several passes over all the source modules, looking for opportunities to make the code cleaner, DRYer and more functional. The code I'm presenting today is the result of this effort and the subject of the first round of reviews. Once I have received and processed the first round of reviews, I plan to make one more pass over the source code, in order to make function parameter names more consistent and to fix the linear order of the bundles. After that, I'll take one smaller, final review before finally merging the branch.

    Results (stage 1)

    • All collection functions except for _.find are now written in a collection-agnostic way, i.e., they use _.find or another Underscore function to abstract over the collection type.
    • New generic functions: linearSearch, binarySearch, extremum, greater, less, lessEqual. I believe that linearSearch and binarySearch should remain internal, in favor of the more user-friendly _.*index* family of functions (which can however expand parameters to support more options from the underlying primitives); the others could potentially be made public in a future update.
    • It is trivial to add _.sortedLastIndex in the future (in fact it's implemented but not added to the index.js yet) as well as an isSorted option for _.lastIndexOf (which is manually disabled for now).
    • Hand-written loops have in many cases been replaced by a call to linearSearch or another Underscore function. Besides linearSearch, the main loop primitives are _.findKey and _.times. Besides those primitives, 20 functions (out of the original 38) still contain hand-written loops for performance reasons.
    • While factoring out extremum, I fixed #2688 and opened the possibility of making _.min and _.max work with non-numbers in the future. This is also how I uncovered the "crazy comparison semantic" that I discuss in the next bullet.
    • I was able to deduplicate the slow path of _.min and _.max, which transforms the elements through an iteratee before comparing, through the outfactoring of extremum. I couldn't deduplicate the fast path (without iteratee) yet, because, for historical reasons, they currently have a rather crazy comparison semantic; they do something that is wildly different from just the builtin </> operators. I could abstract this semantic in a function and pass it to extremum, and in fact, this is exactly what I do in the slow path, but the fast path would suffer a performance degradation of roughly a factor 25 if I were to do the same there. Alternatively, I could inline the comparison logic in extremum, but I don't want to do that because it is really crazy. I want to change it back to regular </> in Underscore 2.0, in which case I'll be able to move the fast path to extremum as well and finish the deduplication. Please see my pre-review comments after this post for more details.
    • The "switch pyramids of doom" (as @jashkenas once aptly called them) in _.restArguments and optimizeCb are gone. As far as my benchmarks could tell, they had no performance benefit whatsoever over the simpler code that I put in their place.
    • The infrastructure around _.iteratee and the internal cb has been simplified substantially.
    • Some functions, including _.map and _.each, take a slight performance hit due to the changes. In my microbenchmarks, with very trivial loop bodies, this could be up to a factor two. I consider this unavoidable, due to the need for a single source of truth about collection iteration, though I have tried hard to keep the sacrifices at a minimum. I'd like to see real-world performance comparisons before drawing any conclusions.
    • In cases where really tiny collections with only one or two elements are passed to an Underscore function, performance hits might be worse than a factor two due to the increased stacking of function calls. Again, I would prefer to benchmark real-world applications before drawing any conclusions.
    • In contrast, some functions actually seem to have sped up a bit due to the simplifications, for example the anonymous functions returned by _.restArguments, which are used internally throughout Underscore.
    • The minified+gzipped UMD bundle size shrank by 225 bytes. I think this is as close to resolving #2060 as we will ever get (edit: other than some breaking changes that can be made in version 2.0, such as removing IE<11 support).

    Review

    This PR is unusual because it may lead to changes that sacrifice performance in favor of other considerations. For the sake of accountability, I'd like the review to be very thorough and entirely public. This is going to be a lot of work and I don't want all that work to land on the shoulders of only one or two people. Instead, I hope to involve many people, each doing only light work. As a token of appreciation, everyone making a substantial contribution to this review will get a permanent honorable mention in the changelog.

    I propose to divide the review work as follows. I'll be inviting specific people for specific parts, but please rest assured that anyone is welcome to participate in any part of the review. I'll keep track of review progress below.

    • [ ] Stage 1 (before final polishing)
      • [x] High-level review of the goals, principles, approach and results described in this post. I'd like to specifically invite @jashkenas for this part, because he is the original creator of Underscore.
      • [ ] Review of the code changes in modules/, preferably by at least two independent expert Underscore users. I would like to specifically invite @cambecc, @reubenrybnik and @joshuacc for this part. Please spread the word if you know other expert Underscore users who might be willing to contribute a code review.
        • [ ] Expert code review 1
        • [ ] Expert code review 2
      • [ ] Preparation of benchmarks based on real-world applications/libraries. I'd like to spread this work over at least three people, each preparing one application; one for Node.js, one for the browser and one more that can be for any environment. "Exotic" environments such as Electron and ExtendScript are also welcome. Please see the section "Benchmark preparation" below for details. Again, please spread the word if you know someone who might have a suitable application or library!
        • [x] Benchmark 1 (Node.js and also browser)
        • [ ] Benchmark 2 (browser)
        • [ ] Benchmark 3 (any environment)
      • [ ] Performance comparisons between Underscore at commit c9b4b63 (master) and Underscore at commit eaba5b5 (functional), one for each applicable combination of a benchmark as listed above and an engine as listed below. This can be spread over many people. Please spread the word if you know anyone who might be willing to help with the measurements. See the "performance measurements" section below for details.
        • [ ] Node.js 10
        • [ ] Node.js 14
        • [ ] Desktop Chrome 80+
        • [ ] Desktop Firefox 80+
        • [ ] Mobile Safari 13+
        • [ ] Internet Explorer 11
        • [ ] Other (optional)
    • [ ] Stage 2 (after processing the outcomes of stage 1 and polishing)
      • [ ] Esthetic review of the generated bundle. I'd like to specifically invite @jashkenas again for this part, when the time is there.

    Benchmark preparation

    If you have a real-world JavaScript application (or library function) that meets all of the following criteria:

    • already in production, i.e., deployed/released and in use by real people in order to accomplish a real task;
    • has a performance-critical section, i.e., a part that required some attention in the past in order to ensure that it would be fast enough, or of which you could easily imagine that it might become problematically slow;
    • the performance-critical section involves calling at least one Underscore function;
    • the application can be adjusted to focus on testing the speed of that performance-critical section (so that testers don't need to wait for more than a few seconds before they can start measuring);

    and you are willing to do the following things, with help as needed:

    • to create a version of your application that is suitable for quickly measuring the speed of the performance-critical section (without changing anything about the internal logic of that section itself);
    • to publish the source code of the benchmark program thus created and to keep the source code open for an extended period of time (at least one year but preferably indefinitely);
    • to invest some additional effort to make it as easy as possible for testers to run the program, to measure the speed and to switch between both versions of Underscore;

    then please let us know by leaving a comment below!

    Performance measurements

    You can contribute performance measurements by doing the following.

    1. Pick an engine that you can run benchmarks on (such as Firefox) and a benchmark program that can run on this engine.
    2. Run the benchmark on the chosen engine, both with Underscore at commit c9b4b63 (master) and Underscore at commit eaba5b5 (functional), and compare the speed.
    3. If you find a small speed difference, please repeat your measurements as often as is required in order to determine whether the difference is significant.
    4. If you do find a significant difference, please profile the benchmark with both versions of Underscore to determine what change in Underscore is causing the difference.
    5. Report your findings by leaving a comment below. Please include details such as hardware specifications, exact versions of the operating system and engine used, number of repeated measurements, exact measured speed on each repetition, and exact numbers obtained from profiling.

    Final words

    I make major contributions to Underscore, like the current one, because I believe it is an excellent library and because I want to help lift it to the next level. If you like what I'm doing, please consider supporting my open source work on Patreon. Shoutout to my existing backers, who encouraged me to do this work, and to @jashkenas, who created this awesome library.

    opened by jgonggrijp 3
Owner
Jeremy Ashkenas
🏍 🛣 🌎 I miss _why.
Jeremy Ashkenas
utility library for async iterable iterators

⚠️ This library is no longer maintained, and should not be used in production applications. Mesh is a utility library for async iterable iterators. Mo

Craig Condon 1k Jul 29, 2022
JavaScript's utility _ belt

__ /\ \ __ __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __

Jeremy Ashkenas 26.7k Jan 4, 2023
Simple translation for your javascripts, yummy with your favorite templates engine like EJS.

jsperanto Simple translation for your javascripts, yummy with your favorite templates engine like EJS. Pluralization, interpolation & "nested lookup"

Jean-Philippe Joyal 62 Oct 21, 2021
A utility for creating toggleable items with JavaScript. Inspired by bootstrap's toggle utility. Implemented in vanillaJS in a functional style.

LUX TOGGLE Demo: https://jesschampion.github.io/lux-toggle/ A utility for creating toggleable dom elements with JavaScript. Inspired by bootstrap's to

Jess Champion 2 Oct 3, 2020
A utility-first CSS framework for rapid UI development.

A utility-first CSS framework for rapidly building custom user interfaces. Documentation For full documentation, visit tailwindcss.com. Community For

Tailwind Labs 63.5k Dec 30, 2022
Low-level CSS Toolkit – the original Functional/Utility/Atomic CSS library

Basscss Low-level CSS toolkit – the original Functional CSS library https://basscss.com Lightning-Fast Modular CSS with No Side Effects Basscss is a l

Basscss 5.8k Dec 31, 2022
A simple javascript utility for conditionally joining classNames together

Classnames A simple JavaScript utility for conditionally joining classNames together. Install with npm, Bower, or Yarn: # via npm npm install classnam

Jed Watson 16.3k Dec 31, 2022
⏳ Modern JavaScript date utility library ⌛️

date-fns provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in a browser & Node.js. ?? Documentation

date-fns 30.6k Dec 29, 2022
A keyboard input capturing utility in which any key can be a modifier key.

Keypress Version 2.1.5 Keypress is a robust keyboard input capturing Javascript utility focused on input for games. For details and documentation, ple

David Mauro 3.2k Dec 28, 2022
Tiny millisecond conversion utility

ms Use this package to easily convert various time formats to milliseconds. Examples ms('2 days') // 172800000 ms('1d') // 86400000 ms('10h')

Vercel 4.4k Jan 4, 2023
A modern JavaScript utility library delivering modularity, performance, & extras.

lodash Site | Docs | FP Guide | Contributing | Wiki | Code of Conduct | Twitter | Chat The Lodash library exported as a UMD module. Generated using lo

Lodash Utilities 55.3k Jan 1, 2023
utility library for async iterable iterators

⚠️ This library is no longer maintained, and should not be used in production applications. Mesh is a utility library for async iterable iterators. Mo

Craig Condon 1k Jul 29, 2022
CasperJS is no longer actively maintained. Navigation scripting and testing utility for PhantomJS and SlimerJS

CasperJS Important note: the master branch hosts the development version of CasperJS, which is now pretty stable and should be the right version to us

CasperJS 7.3k Dec 25, 2022
A tiny JavaScript utility to access deep properties using a path (for Node and the Browser)

object-path Access deep properties using a path Changelog 0.11.5 SECURITY FIX. Fix a prototype pollution vulnerability in the set() function when usin

Mario Casciaro 1k Dec 29, 2022
An isomorphic and configurable javascript utility for objects deep cloning that supports circular references.

omniclone An isomorphic and configurable javascript function for object deep cloning. omniclone(source [, config, [, visitor]]); Example: const obj =

Andrea Simone Costa 184 May 5, 2022
Tiny millisecond conversion utility

ms Use this package to easily convert various time formats to milliseconds. Examples ms('2 days') // 172800000 ms('1d') // 86400000 ms('10h')

Vercel 4.4k Jan 4, 2023
Scaffolding utility for vanilla-js

scaffold-static lets you automate the local development environment setup and build static sites (vanilla-JS) with ease. Installation npm install -g s

James George 117 Nov 11, 2022
A utility-first CSS framework for rapid UI development.

A utility-first CSS framework for rapidly building custom user interfaces. Documentation For full documentation, visit tailwindcss.com. Community For

Tailwind Labs 63.5k Jan 1, 2023
selectivizr is a JavaScript utility that emulates CSS3 pseudo-classes and attribute selectors in Internet Explorer 6-8.

Selectivizr CSS3 selectors for IE 6-8 selectivizr is a JavaScript utility that emulates CSS3 pseudo-classes and attribute selectors in Internet Explor

Keith Clark 1.7k Dec 18, 2022
GraphQL query utility for serverside apps

Write GraphQL queries as objects instead of strings This is a better implementation of the GraphQL query API via NodeJS, created as a wrapper of Got.

Lucas Santos 391 Dec 6, 2022