Skip to content

Instantly share code, notes, and snippets.

@thisisui
Last active August 29, 2015 14:01
Show Gist options
  • Save thisisui/564a6cb22a4909b8b051 to your computer and use it in GitHub Desktop.
Save thisisui/564a6cb22a4909b8b051 to your computer and use it in GitHub Desktop.

So you are a UI guy and cannot live without Underscore or Lodash when you write your JavaScript? Well, it is okay but have you ever look into the source of those libraries or you are using them just because everyone does. In this short series I will take methods from lodash and look through most of them to show you what is happening in these methods and why you are using them.

Arrays

compact

So let's get started. First method is compact. This method is very simple and just remove falsy methods from array.

function compact(array) {
  var index = -1,
      length = array ? array.length : 0,
      result = [];

  while (++index < length) {
    var value = array[index];
    if (value) {
        result.push(value);
    }
  }

  return result;
}

So what you just saw. Array is being send to function as parameter, then we have a loop on that array and with simple if (value) all values which are falsy are ignored and that's all simple and clear.

difference

Now this one is a little bit tricker. Let's copy some of the code

function difference(array) { return baseDifference(array, baseFlatten(arguments, true, true, 1)); }

to use this method we need to look into two more function baseDifference and baseFlatten.

function baseFlatten(array, isShallow, isStrict, fromIndex) {
  var index = (fromIndex || 0) - 1,
    length = array ? array.length : 0,
    result = [];

 while (++index < length) {
  var value = array[index];

  if (value && typeof value == 'object' && typeof value.length == 'number'
      && (isArray(value) || isArguments(value))) {
    // recursively flatten arrays (susceptible to call stack limits)
      if (!isShallow) {
        value = baseFlatten(value, isShallow, isStrict);
      }
      var valIndex = -1,
        valLength = value.length,
        resIndex = result.length;

      result.length += valLength;
      while (++valIndex < valLength) {
      result[resIndex++] = value[valIndex];
      }
    } else if (!isStrict) {
    result.push(value);
    }
  }
return result;
}

function baseDifference(array, values) {
  var index = -1,
      indexOf = getIndexOf(),
      length = array ? array.length : 0,
      isLarge = length >= largeArraySize && indexOf === baseIndexOf,
      result = [];

  if (isLarge) {
    var cache = createCache(values);
    if (cache) {
      indexOf = cacheIndexOf;
      values = cache;
    } else {
      isLarge = false;
    }
  }
  while (++index < length) {
    var value = array[index];
    if (indexOf(values, value) < 0) {
      result.push(value);
    }
  }
  if (isLarge) {
    releaseObject(values);
  }
  return result;
}

This is loooong function and uses next two helpful functions - isArray and isArguments, and then isNative and reNative. All of them will be covered in next section. BaseDifference just need to check which values are different in arrays. One of the arrays is baseFlatten which I will explain in few seconds. check what this function really does

findIndex

function findIndex(array, callback, thisArg) {
  var index = -1,
      length = array ? array.length : 0;

  callback = lodash.createCallback(callback, thisArg, 3);
  while (++index < length) {
    if (callback(array[index], index, array)) {
      return index;
    }
  }
  return -1;
}

We have here a createCallback function with number 3 as one of the arguments. - interesting

function createCallback(func, thisArg, argCount) {
  var type = typeof func;
  if (func == null || type == 'function') {
    return baseCreateCallback(func, thisArg, argCount);
  }
  // handle "_.pluck" style callback shorthands
  if (type != 'object') {
    return property(func);
  }
  var props = keys(func),
      key = props[0],
      a = func[key];

  // handle "_.where" style callback shorthands
  if (props.length == 1 && a === a && !isObject(a)) {
    // fast path the common case of providing an object with a single
    // property containing a primitive value
    return function(object) {
      var b = object[key];
      return a === b && (a !== 0 || (1 / a == 1 / b));
    };
  }
  return function(object) {
    var length = props.length,
        result = false;

    while (length--) {
      if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
        break;
      }
    }
    return result;
  };
}

Quite a loooong function once again. At the beginning we don't know what argument we will recieve: a function, a simple key value object or just a property which will be true. If there is no callback function passed as argument or there is a function lodash uses baseCreateCallback function. Then we have object check and property scenario. a === a this is very interesting part. - interesting

function baseCreateCallback(func, thisArg, argCount) {
  if (typeof func != 'function') {
    return identity;
  }
  // exit early for no `thisArg` or already bound by `Function#bind`
  if (typeof thisArg == 'undefined' || !('prototype' in func)) {
    return func;
  }
  var bindData = func.__bindData__;
  if (typeof bindData == 'undefined') {
    if (support.funcNames) {
      bindData = !func.name;
    }
    bindData = bindData || !support.funcDecomp;
    if (!bindData) {
      var source = fnToString.call(func);
      if (!support.funcNames) {
        bindData = !reFuncName.test(source);
      }
      if (!bindData) {
        // checks if `func` references the `this` keyword and stores the result
        bindData = reThis.test(source);
        setBindData(func, bindData);
      }
    }
  }
  // exit early if there are no `this` references or `func` is bound
  if (bindData === false || (bindData !== true && bindData[1] & 1)) {
    return func;
  }
  switch (argCount) {
    case 1: return function(value) {
      return func.call(thisArg, value);
    };
    case 2: return function(a, b) {
      return func.call(thisArg, a, b);
    };
    case 3: return function(value, index, collection) {
      return func.call(thisArg, value, index, collection);
    };
    case 4: return function(accumulator, value, index, collection) {
      return func.call(thisArg, accumulator, value, index, collection);
    };
  }
  return bind(func, thisArg);
}

A lot of checking here for 'this' and for number of arguments. and we have misterious number 3 solved and this is number of arguments which determines how many arguments will be passed to function with specific order.

findLastIndex

function findLastIndex(array, callback, thisArg) { var length = array ? array.length : 0; callback = lodash.createCallback(callback, thisArg, 3); while (length--) { if (callback(array[length], length, array)) { return length; } } return -1; }

Just the same as findIndex just array is being search from the end.

first

function first(array, callback, thisArg) {
  var n = 0,
      length = array ? array.length : 0;

  if (typeof callback != 'number' && callback != null) {
    var index = -1;
    callback = lodash.createCallback(callback, thisArg, 3);
    while (++index < length && callback(array[index], index, array)) {
      n++;
    }
  } else {
    n = callback;
    if (n == null || thisArg) {
      return array ? array[0] : undefined;
    }
  }
  return slice(array, 0, nativeMin(nativeMax(0, n), length));
}

Let's take a look at return value

function slice(array, start, end) { start || (start = 0); if (typeof end == 'undefined') { end = array ? array.length : 0; } var index = -1, length = end - start || 0, result = Array(length < 0 ? 0 : length);

while (++index < length) {
  result[index] = array[start + index];
}
return result;

}

I really like the argument default value note: start || (start = 0); Here we can as element to search put function, property and object (here we can filter to show different based on condition from object).

flatten

function flatten(array, isShallow, callback, thisArg) { // juggle arguments if (typeof isShallow != 'boolean' && isShallow != null) { thisArg = callback; callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow; isShallow = false; } if (callback != null) { array = map(array, callback, thisArg); } return baseFlatten(array, isShallow); }

Here we also land on baseFlatten.

indexOf

Here we have special argument fromIndex which can be also a boolean

function indexOf(array, value, fromIndex) {
  if (typeof fromIndex == 'number') {
    var length = array ? array.length : 0;
    fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
  } else if (fromIndex) {
    var index = sortedIndex(array, value);
    return array[index] === value ? index : -1;
  }
  return baseIndexOf(array, value, fromIndex);
}

sortedIndex - interesting

And here we have special function which will make a binary search and give back a position where element should be inserted to keep array sorted. We have a bitwise operator here to make a binary search.

function sortedIndex(array, value, callback, thisArg) {
  var low = 0,
      high = array ? array.length : low;

  // explicitly reference `identity` for better inlining in Firefox
  callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
  value = callback(value);

  while (low < high) {
    var mid = (low + high) >>> 1;
    (callback(array[mid]) < value)
      ? low = mid + 1
      : high = mid;
  }
  return low;
}

initial

function initial(array, callback, thisArg) {
  var n = 0,
      length = array ? array.length : 0;

  if (typeof callback != 'number' && callback != null) {
    var index = length;
    callback = lodash.createCallback(callback, thisArg, 3);
    while (index-- && callback(array[index], index, array)) {
      n++;
    }
  } else {
    n = (callback == null || thisArg) ? 1 : callback || n;
  }
  return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
}

Callback can be a number and it is doing something different than if is a function. It can set up how much from end must be spliced the array - interesting

intersection - clear all arrays only for === equality

function intersection() {
    var args = [],
    argsIndex = -1,
    argsLength = arguments.length,
    caches = getArray(),
    indexOf = getIndexOf(),
    trustIndexOf = indexOf === baseIndexOf,
    seen = getArray();

    while (++argsIndex < argsLength) {
        var value = arguments[argsIndex];
        if (isArray(value) || isArguments(value)) {
            args.push(value);
            caches.push(trustIndexOf && value.length >= largeArraySize &&
                createCache(argsIndex ? args[argsIndex] : seen));
        }
    }
    var array = args[0],
    index = -1,
    length = array ? array.length : 0,
    result = [];
    
    outer:
    while (++index < length) {
        var cache = caches[0];
        value = array[index];
    
        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
            argsIndex = argsLength;
            (cache || seen).push(value);
            while (--argsIndex) {
                cache = caches[argsIndex];
                if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
                    continue outer;
                }
            }
            result.push(value);
        }
    }
    while (argsLength--) {
        cache = caches[argsLength];
        if (cache) {
            releaseObject(cache);
        }
    }
    releaseArray(caches);
    releaseArray(seen);
    return result;
}

Here we have a usage of label in while loop.

function releaseObject(object) {
    var cache = object.cache;
    if (cache) {
        releaseObject(cache);
    }
    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
    if (objectPool.length < maxPoolSize) {
        objectPool.push(object);
    }
}

function releaseArray(array) {
    array.length = 0;
    if (arrayPool.length < maxPoolSize) {
        arrayPool.push(array);
    }
}

Just adds arrays and objects to pools.

last

Gets the last n values or goes from the end.

function last(array, callback, thisArg) {
  var n = 0,
      length = array ? array.length : 0;

  if (typeof callback != 'number' && callback != null) {
    var index = length;
    callback = lodash.createCallback(callback, thisArg, 3);
    while (index-- && callback(array[index], index, array)) {
      n++;
    }
  } else {
    n = callback;
    if (n == null || thisArg) {
      return array ? array[length - 1] : undefined;
    }
  }
  return slice(array, nativeMax(0, length - n));
} 

lastIndexOf

Gives an index of matching value from the end

function lastIndexOf(array, value, fromIndex) {
  var index = array ? array.length : 0;
  if (typeof fromIndex == 'number') {
    index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
  }
  while (index--) {
    if (array[index] === value) {
      return index;
    }
  }
  return -1;
} 

pull

removes all stricty matching values

function pull(array) {
  var args = arguments,
      argsIndex = 0,
      argsLength = args.length,
      length = array ? array.length : 0;

  while (++argsIndex < argsLength) {
    var index = -1,
        value = args[argsIndex];
    while (++index < length) {
      if (array[index] === value) {
        splice.call(array, index--, 1);
        length--;
      }
    }
  }
  return array;
} 

range

creates array with numbers

function range(start, end, step) {
  start = +start || 0;
  step = typeof step == 'number' ? step : (+step || 1);

  if (end == null) {
    end = start;
    start = 0;
  }
  // use `Array(length)` so engines like Chakra and V8 avoid slower modes - interesting
  // http://youtu.be/XAqIpGU8ZZk#t=17m25s
  var index = -1,
      length = nativeMax(0, ceil((end - start) / (step || 1))),
      result = Array(length);

  while (++index < length) {
    result[index] = start;
    start += step;
  }
  return result;
} 

remove

Just removes values from array and returns array of removed items

function remove(array, callback, thisArg) {
  var index = -1,
      length = array ? array.length : 0,
      result = [];

  callback = lodash.createCallback(callback, thisArg, 3);
  while (++index < length) {
    var value = array[index];
    if (callback(value, index, array)) {
      result.push(value);
      splice.call(array, index--, 1);
      length--;
    }
  }
  return result;
} 

rest

get rest of elements to the end

function rest(array, callback, thisArg) {
  if (typeof callback != 'number' && callback != null) {
    var n = 0,
        index = -1,
        length = array ? array.length : 0;

    callback = lodash.createCallback(callback, thisArg, 3);
    while (++index < length && callback(array[index], index, array)) {
      n++;
    }
  } else {
    n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
  }
  return slice(array, n);
}

sortedIndex is above

union

Uses baseUniq - TBD and unions many arrays to one using strict comparision ===

function union() {
  return baseUniq(baseFlatten(arguments, true, true));
} 

uniq

Removes duplicates from array.

function uniq(array, isSorted, callback, thisArg) {
      // juggle arguments
      if (typeof isSorted != 'boolean' && isSorted != null) {
        thisArg = callback;
        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
        isSorted = false;
      }
      if (callback != null) {
        callback = lodash.createCallback(callback, thisArg, 3);
      }
      return baseUniq(array, isSorted, callback);
    } 

without

And remove values which are arguments

function without(array) {
  return baseDifference(array, slice(arguments, 1));
} 

xor

symetrical difference http://en.wikipedia.org/wiki/Symmetric_difference

function xor() {
  var index = -1,
      length = arguments.length;

  while (++index < length) {
    var array = arguments[index];
    if (isArray(array) || isArguments(array)) {
      var result = result
        ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
        : array;
    }
  }
  return result || [];
} 

zip

create array of arrays from arrays

function zip() {
  var array = arguments.length > 1 ? arguments : arguments[0],
      index = -1,
      length = array ? max(pluck(array, 'length')) : 0,
      result = Array(length < 0 ? 0 : length);

  while (++index < length) {
    result[index] = pluck(array, index);
  }
  return result;
} 

zipObject

create object from arrays

function zipObject(keys, values) {
  var index = -1,
      length = keys ? keys.length : 0,
      result = {};

  if (!values && length && !isArray(keys[0])) {
    values = [];
  }
  while (++index < length) {
    var key = keys[index];
    if (values) {
      result[key] = values[index];
    } else if (key) {
      result[key[0]] = key[1];
    }
  }
  return result;
} 

Arrays done

Collections

_.at 

action: _.at({'a': 'b', 'c': 'd', 'e':'f'}, ['a','e']); result: ["b", "f"]

Creates an array of elements from the specified indexes, or keys, of the 'collection`. Indexes may be specified as individual arguments or as arrays of indexes

_.contains

_.contains({ 'name': 'fred', 'age': 40 }, 'fred', /*index*/1); result: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment