function orderBy(arr) {
  var _comparator = null;
  var sortKeys;
  arr = src_util.convertArray(arr); // determine order (last argument)

  var args = src_util.toArray(arguments, 1);
  var order = args[args.length - 1];

  if (typeof order === 'number') {
    order = order < 0 ? -1 : 1;
    args = args.length > 1 ? args.slice(0, -1) : args;
  } else {
    order = 1;
  } // determine sortKeys & comparator


  var firstArg = args[0];

  if (!firstArg) {
    return arr;
  } else if (typeof firstArg === 'function') {
    // custom comparator
    _comparator = function comparator(a, b) {
      return firstArg(a, b) * order;
    };
  } else {
    // string keys. flatten first
    sortKeys = Array.prototype.concat.apply([], args);

    _comparator = function comparator(a, b, i) {
      i = i || 0;
      return i >= sortKeys.length - 1 ? baseCompare(a, b, i) : baseCompare(a, b, i) || _comparator(a, b, i + 1);
    };
  }

  function baseCompare(a, b, sortKeyIndex) {
    var sortKey = sortKeys[sortKeyIndex];

    if (sortKey) {
      if (sortKey !== '$key') {
        if (src_util.isObject(a) && '$value' in a) a = a.$value;
        if (src_util.isObject(b) && '$value' in b) b = b.$value;
      }

      a = src_util.isObject(a) ? src_util.getPath(a, sortKey) : a;
      b = src_util.isObject(b) ? src_util.getPath(b, sortKey) : b;
      a = typeof a === 'string' ? a.toLowerCase() : a;
      b = typeof b === 'string' ? b.toLowerCase() : b;
    }

    return a === b ? 0 : a > b ? order : -order;
  } // sort on a copy to avoid mutating original array


  return arr.slice().sort(_comparator);
}
Filter filter for arrays

@param {String|Array<String>|Function} ...sortKeys
@param {Number} [order]