Overview of Underscore.js
Introduction to Underscore.js
Underscore.js is a lightweight JavaScript utility library that provides a wide range of functional programming helper methods for handling arrays, objects, functions, and other common data structures. Its core goal is to offer a concise, consistent API, enabling developers to write more elegant and maintainable code in a functional programming style.
Key Features:
- Lightweight: Approximately 16KB when minified.
- No dependencies: Works independently of other libraries.
- High compatibility: Supports JavaScript environments from ES3 and above.
- Functional programming style: Provides methods like
map,reduce,filter, etc. - Utility toolkit: Includes a variety of practical utility functions.
Development History:
- Created in 2009 by Jeremy Ashkenas (also the author of CoffeeScript and Backbone.js).
- Gained prominence as a dependency of Backbone.js.
- Evolved into a standalone utility library.
- Updates slowed after 2017, but stable versions are still maintained.
Relationship Between Underscore.js and Lodash
Underscore.js and Lodash (another popular JavaScript utility library) are closely related:
| Feature | Underscore.js | Lodash |
|---|---|---|
| Creator | Jeremy Ashkenas | John-David Dalton |
| Origin | 2009 | 2012 (originally a fork of Underscore) |
| Design Philosophy | Functional programming toolkit | Comprehensive utility library |
| Performance | Good | Typically better (especially v4+) |
| Community Support | Stable but less frequent updates | Active with frequent updates |
| Functionality | Core functional methods | Broader range of utility functions |
| Modularity | Supported | More fine-grained modularity |
Key Differences:
- Lodash has more performance optimizations.
- Lodash offers better support for modern JavaScript features.
- Lodash provides finer-grained modularization.
- Underscore focuses on core functional programming concepts.
Core Value of Underscore.js
- Functional Programming Paradigm: Offers
map,reduce,filter, and other functional methods for declarative coding. - Data Operation Abstraction: Simplifies operations on arrays, objects, and collections.
- Code Conciseness: Reduces boilerplate code, improving development efficiency.
- Consistency: Provides a unified API style, lowering the learning curve.
- Compatibility: Supports older JavaScript environments.
Detailed Explanation of Underscore.js Core Functions
Collection Processing Functions
Underscore.js provides a rich set of functions for handling collections, including arrays, objects, and array-like structures.
map/collect
_.map(list, iteratee, [context]): Executes the iteratee function on each element of the collection, returning an array of results.
const numbers = [1, 2, 3, 4];
const doubled = _.map(numbers, n => n * 2);
// doubled: [2, 4, 6, 8]
// Equivalent to
const doubled2 = numbers.map(n => n * 2);Differences:
- Underscore’s
mapcan handle objects and array-like structures. - Provides a consistent API across different collection types.
reduce/foldl/inject
_.reduce(list, iteratee, [memo], [context]): Reduces a collection to a single value.
const sum = _.reduce([1, 2, 3, 4], (sum, n) => sum + n, 0);
// sum: 10
// Equivalent to
const sum2 = [1, 2, 3, 4].reduce((sum, n) => sum + n, 0);Features:
- Supports setting an initial value (
memo). - Can handle sparse arrays.
filter/select
_.filter(list, predicate, [context]): Returns an array of elements that satisfy the predicate condition.
const evens = _.filter([1, 2, 3, 4], n => n % 2 === 0);
// evens: [2, 4]reject
_.reject(list, predicate, [context]): Returns an array of elements that do not satisfy the predicate condition.
const odds = _.reject([1, 2, 3, 4], n => n % 2 === 0);
// odds: [1, 3]every/all
_.every(list, predicate, [context]): Checks if all elements satisfy the predicate condition.
const allPositive = _.every([1, 2, 3], n => n > 0);
// allPositive: truesome/any
_.some(list, predicate, [context]): Checks if at least one element satisfies the predicate condition.
const hasNegative = _.some([-1, 2, 3], n => n < 0);
// hasNegative: truecontains/include
_.contains(list, value, [fromIndex]): Checks if the list contains the specified value.
const hasTwo = _.contains([1, 2, 3], 2);
// hasTwo: trueArray Functions
Underscore.js provides numerous utility functions specifically for array operations.
first/head/take
_.first(array, [n]): Returns the first element or the first n elements of an array.
const first = _.first([1, 2, 3]);
// first: 1
const firstTwo = _.first([1, 2, 3], 2);
// firstTwo: [1, 2]last
_.last(array, [n]): Returns the last element or the last n elements of an array.
const last = _.last([1, 2, 3]);
// last: 3
const lastTwo = _.last([1, 2, 3], 2);
// lastTwo: [2, 3]rest/initial
_.rest(array, [index]): Returns all elements after the specified index (defaults to 1).
const rest = _.rest([1, 2, 3, 4]);
// rest: [2, 3, 4]
const restFrom2 = _.rest([1, 2, 3, 4], 2);
// restFrom2: [3, 4]_.initial(array, [n]): Returns all elements except the last n elements.
const initial = _.initial([1, 2, 3, 4]);
// initial: [1, 2, 3]
const initialLast2 = _.initial([1, 2, 3, 4], 2);
// initialLast2: [1, 2]flatten
_.flatten(array, [shallow]): Flattens a nested array.
const flat = _.flatten([1, [2], [3, [[4]]]]);
// flat: [1, 2, 3, 4, [4]] (fully flattened)
const shallowFlat = _.flatten([1, [2], [3, [[4]]]], true);
// shallowFlat: [1, 2, 3, [4]] (flattened one level)without
_.without(array, *values): Returns a new array with specified values removed.
const withoutTwo = _.without([1, 2, 3, 2], 2);
// withoutTwo: [1, 3]union
_.union(*arrays): Returns the union of multiple arrays (deduplicated).
const union = _.union([1, 2, 3], [2, 3, 4]);
// union: [1, 2, 3, 4]intersection
_.intersection(*arrays): Returns the intersection of multiple arrays.
const intersection = _.intersection([1, 2, 3], [2, 3, 4]);
// intersection: [2, 3]difference
_.difference(array, *others): Returns elements in array that are not in other arrays.
const diff = _.difference([1, 2, 3, 4], [2, 3]);
// diff: [1, 4]uniq/unique
_.uniq(array, [isSorted], [iteratee]): Returns a deduplicated array.
const unique = _.uniq([1, 2, 1, 3, 2]);
// unique: [1, 2, 3]
// Using iteratee
const uniqueByAbs = _.uniq([-1, 2, -1, 3, 2], false, Math.abs);
// uniqueByAbs: [-1, 2, 3]Object Functions
Underscore.js provides many utility functions for operating on JavaScript objects.
keys
_.keys(object): Returns an array of the object’s enumerable property names.
const keys = _.keys({a: 1, b: 2, c: 3});
// keys: ['a', 'b', 'c']values
_.values(object): Returns an array of the object’s enumerable property values.
const values = _.values({a: 1, b: 2, c: 3});
// values: [1, 2, 3]pairs
_.pairs(object): Converts an object into an array of [key, value] pairs.
const pairs = _.pairs({a: 1, b: 2, c: 3});
// pairs: [['a', 1], ['b', 2], ['c', 3]]invert
_.invert(object): Inverts the object’s key-value pairs.
const inverted = _.invert({a: 1, b: 2, c: 3});
// inverted: {1: 'a', 2: 'b', 3: 'c'}functions/methods
_.functions(object): Returns an array of the object’s method names.
const obj = {
a: 1,
b: function() {},
c: function() {}
};
const funcs = _.functions(obj);
// funcs: ['b', 'c']extend
_.extend(destination, *sources): Copies properties from one or more source objects to a target object.
const obj = _.extend({a: 1}, {b: 2}, {c: 3});
// obj: {a: 1, b: 2, c: 3}defaults
_.defaults(object, *defaults): Copies properties from source objects to the target object, but only for properties that don’t exist in the target.
const obj = _.defaults({a: 1}, {a: 2, b: 2}, {b: 3, c: 3});
// obj: {a: 1, b: 2, c: 3}clone
_.clone(object): Performs a shallow clone of an object.
const obj = {a: 1, b: {c: 2}};
const cloned = _.clone(obj);
// cloned: {a: 1, b: {c: 2}} (b is a reference)tap
_.tap(object, interceptor): Calls the interceptor function with the object and returns the object, often used for debugging.
const result = _.tap({a: 1}, obj => {
console.log(obj); // {a: 1}
});
// result: {a: 1}Function Utilities
Underscore.js provides many tools for manipulating and enhancing functions.
bind
_.bind(function, object, *arguments): Binds a function to an object, optionally pre-setting some arguments.
const obj = {
a: 1,
add: function(b, c) { return this.a + b + c; }
};
const boundAdd = _.bind(obj.add, obj, 2);
const result = boundAdd(3); // 1 + 2 + 3 = 6partial
_.partial(function, *partialArgs): Pre-sets some arguments of a function, creating a new function.
const add = (a, b, c) => a + b + c;
const addPartial = _.partial(add, 1, 2);
const result = addPartial(3); // 1 + 2 + 3 = 6memoize
_.memoize(function, [hashFunction]): Caches function results to avoid redundant computations.
const fibonacci = _.memoize(n => {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
fibonacci(10); // Computes and caches result
fibonacci(10); // Reads directly from cachedelay
_.delay(function, wait, *arguments): Delays the execution of a function.
_.delay(() => console.log('Delayed'), 1000); // Executes after 1 secondthrottle
_.throttle(function, wait, [options]): Creates a throttled function that executes at most once every wait milliseconds.
const throttledScroll = _.throttle(() => {
console.log('Scroll event');
}, 200);
window.addEventListener('scroll', throttledScroll);debounce
_.debounce(function, wait, [immediate]): Creates a debounced function that executes after wait milliseconds of no further triggers.
const debouncedResize = _.debounce(() => {
console.log('Window resized');
}, 300);
window.addEventListener('resize', debouncedResize);once
_.once(function): Creates a function that executes only once.
const init = _.once(() => {
console.log('Initialized');
});
init(); // 'Initialized'
init(); // No outputafter
_.after(count, function): Creates a function that executes only after being called count times.
const saveAfter3 = _.after(3, () => {
console.log('Saved!');
});
saveAfter3(); // No output
saveAfter3(); // No output
saveAfter3(); // 'Saved!'wrap
_.wrap(function, wrapper): Wraps a function with a wrapper function.
const greet = name => `Hello, ${name}!`;
const wrappedGreet = _.wrap(greet, (func, name) => {
return `Before: ${func(name)} After`;
});
wrappedGreet('John'); // 'Before: Hello, John! After'Utility Functions
Underscore.js also provides a variety of general-purpose utility functions.
identity
_.identity(value): Returns the input value as-is.
const value = _.identity(42);
// value: 42constant
_.constant(value): Returns a function that always returns the specified value.
const alwaysFour = _.constant(4);
alwaysFour(); // 4noop
_.noop(): A no-operation function.
_.noop(); // No operationtimes
_.times(n, iteratee, [context]): Calls the iteratee function n times.
const results = _.times(3, i => i * 2);
// results: [0, 2, 4]random
_.random(min, max): Returns a random integer between min and max.
const rand = _.random(1, 10);
// rand: Random integer between 1 and 10mixin
_.mixin(object): Adds methods from object to the Underscore namespace.
_.mixin({
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_.capitalize('hello'); // 'Hello'uniqueId
_.uniqueId([prefix]): Generates a unique ID.
const id1 = _.uniqueId(); // '1'
const id2 = _.uniqueId(); // '2'
const id3 = _.uniqueId('item_'); // 'item_3'escape
_.escape(string): Escapes HTML special characters.
const escaped = _.escape('<script>alert("xss")</script>');
// escaped: '<script>alert("xss")</script>'result
_.result(object, property, [defaultValue]): Retrieves a property value from an object, executing it if it’s a function.
const obj = {
a: 1,
b: function() { return 2; }
};
const val1 = _.result(obj, 'a'); // 1
const val2 = _.result(obj, 'b'); // 2
const val3 = _.result(obj, 'c', 3); // 3 (default value)



