In a previous post on extending the JavaScript Array prototype, I showed how methods could be added to the Array prototype to make it (mostly) consistent with the Ecma 5 standard across all browsers using a technique called polyfilling. In this article, I will further extend the Array prototype with methods borrowed from Ruby, which in my opinion has a very useful Array toolkit.

In a previous post on extending the JavaScript Array prototype, I showed how methods could be added to the Array prototype to make it (mostly) consistent with the Ecma 5 standard across all browsers using a technique called polyfilling. In this article, I will further extend the Array prototype with methods borrowed from Ruby, which in my opinion has a very useful Array toolkit.

JavaScript allows native objects like Array to be extended by extending their prototypes. If you would like to add a method to the Array prototype, you would do this:

Array.prototype.hello = function (str) {
  console.log ("Hello " + str);
}

After this code is executed, any other code can now call the hello method on any array. There is no need to instantiate a special array class, or to make any modifications to the code. This is the magic of prototypes:

var a = [1,2,3];
a.hello("world");

In this article, we present a set of methods to add to the Array prototype that implement part of the Ruby array methods. All methods are defined within an immediately invoked function expression (IFFE) so as not to create namespace conflicts with other code:

(function () {
  "use strict";
  if (!Array.myMethod) {
    Array.myMethod= function (arg) {
      // method code
    };
  }
}());

at

Returns the element at index. A negative index counts from the end of self, where -1 is the last element.

Array.prototype.at = function (index) {
  if (index < 0) {
    index = this.length + index;
  }
  if (index < 0 || index >= this.length) {
    return undefined;
  }
  return this[index];
};

clear

Removes all elements from self and returns self for chaining purposes.

Array.prototype.clear = function () {
  this.length = 0;
  return this;
};

compact

Returns a copy of self with all undefined elements removed, turning a sparse array into a dense array. Returns self for chaining purposes.

Array.prototype.compact = function () {
  // Filters skips missing elements in sparse arrays.
  // By simply returning true, we close all gaps.
  return this.filter(function (x) { return x !== undefined &&  x !== null; });
};

$compact

Removes null and undefined elements from the array, turning it into a dense array. Returns self for chaining purposes.

	
Array.prototype.$compact = function () {
  var i, changes = false;
  for (i = 0; i < this.length; i = i + 1) {
    // If element is non-existent, undefined or null, remove it.
    if (!this[i]) { this.splice(i, 1); i = i - 1; changes = true; }
  }
  if (!changes) {
    return undefined;
  }
  return this;
};

deleteAt

Deletes the element at the specified index, returning that element, or undefined if the index is out of range. A negative index is counted from the end of the array, where -1 corresponds to the last element. Returns self for chaining purposes.

Array.prototype.deleteAt = function (index) {
  if (index < 0) {
    index = this.length + index;
  }
  // If element is non-existent, return undefined:
  if (!this.hasOwnProperty(index)) {
    return undefined;
  }
  var elem = this[index];
  this.splice(index, 1);
  return elem;
};

Examples

var a = ["ant", "bat", "cat", "dog"]
a.deleteAt(2)    // => "cat"
a                // => ["ant", "bat", "dog"]
a.deleteAt(99)   // => undefined

deleteIf

Deletes every element of self for which function evaluates to true. Sparse arrays are converted to dense arrays. The array is changed instantly every time the function is called, not after the iteration is over.

Array.prototype.deleteIf = function (f) {
  var i;
  for (i = 0; i < this.length; i = i + 1) {
    if (!this[i] || f(this[i])) {
      this.splice(i, 1);
      i = i - 1;
    }
  }
  return this;
};

Example

var scores = [ 97, 42, 75 ]
scores.deleteIf (function(score) { return score < 80; });  // => [97]

drop

Drops first n elements from array and returns the rest of the elements in a new array.

Array.prototype.drop = function (n) {
  if (n < 0) {
    throw new RangeError();
  }
  return this.slice(n);
};

Example

var a = [1, 2, 3, 4, 5, 0]
a.drop(3)             // => [4, 5, 0]

dropWhile

Drops elements up to, but not including, the first element for which the function returns false and returns an array containing the remaining elements.

Array.prototype.dropWhile = function (f) {
  var pos = 0;
  while (pos < this.length) {
    if (!f(this[pos])) {
      break;
    }
    pos = pos + 1;
  }
  return this.slice(pos);
};

Example

var a = [1, 2, 3, 4, 5, 0]
a.dropWhile (function(i) { return i < 3; })   // => [3, 4, 5, 0]

fetch

Tries to return the element at position index, but throws a RangeError exception if the referenced index lies outside of the array bounds. This error can be prevented by supplying a second argument, which will act as a default value. Alternatively, if a function is given it will only be executed when an invalid index is referenced. Negative values of index count from the end of the array.

Array.prototype.fetch = function (index, f) {
  if (index < 0) {
    index = this.length + index;
  }
 
  // No range error:
  if (index < this.length) {
    return this[index];
  }
 
  // Range error:
  if (!f) {
    throw new RangeError();
  } else if (typeof (f) === "function") {
    return f(index);
  } else {
    return f;
  }
};

Example:

var a = [ 11, 22, 33, 44 ]
a.fetch(1)               // => 22
a.fetch(-1)              // => 44
a.fetch(4, 'cat')        // => "cat"
a.fetch(100, function(i) { return i + " is out of bounds"; })
                         // => "100 is out of bounds"

fill

fill (obj)-> array
fill (obj, start [, length]) -> array
fill (function(index)) -> array
fill (function(index), start, [, length]) -> array

The first two forms set the selected elements of self (which may be the entire array) to obj.

A start of null / undefined is equivalent to zero. A length of null / undefined is equivalent to the length of the array.

The last two forms fill the array with the value of the given function, which is passed the absolute index of each element to be filled.

Negative values of start count from the end of the array, where -1 is the last element.

Array.prototype.fill = function (f, start, length) {
  var i, end;
 
  if (!start) {
    start = 0;
  }
  if (start < 0) {
    start = this.length + start;
  }
 
  end = this.length - 1;
  if (length) {
    end = start + length - 1;
  }
  if (end >= this.length) {
    end = this.length - 1;
  }
 
  for (i = start;  i <= end; i = i + 1) {
    if (typeof (f) === "function") {
      this[i] = f(i);
    } else {
      this[i] = f;
    }
  }
 
  return this;
};

Example

var a = [ "a", "b", "c", "d" ]
a.fill("x")              // => ["x", "x", "x", "x"]
a.fill("z", 2, 2)        // => ["x", "x", "z", "z"]
a.fill("y", 0, 1)        // => ["y", "y", "z", "z"]
a.fill (function(i) { return i*i; })      // => [0, 1, 4, 9]
a.fill(-2) (function(i) { return i*i; })  // => [0, 1, 8, 27]

find

Returns the index of the first object for which the function returns true . Returns undefined if no match is found.

Array.prototype.find = function (f) {
  var i, len = this.length;
  for (i = 0; i < len; i = i + 1) {
    if (f(this[i])) {
      return i;
    }
  }
  return undefined;
};

first

Returns the first element, or the first n elements, of the array. If the array is empty, requesting one element returns undefined, and requesting multiple elements returns an empty array.

Array.prototype.first = function (n) {
  if (!n) {
    if (this.length === 0) {
      return undefined;
    }
    return this[0];
  } else {
    if (this.length === 0) {
      return [];
    }
    return this.slice(0, n);
  }
};

Example

var a = [ "q", "r", "s", "t" ]
a.first     // => "q"
a.first(2)  // => ["q", "r"]

flatten

Returns a new array that is a one-dimensional flattening of self (recursively). That is, for every element that is an array, extract its elements into the new array. The optional level argument determines the level of recursion to flatten.

Array.prototype.flatten = function (level) {
  if (!level) {
    level = -1;
  }
 
  function flatten_recursive(arr, level) {
    var result = [];
    arr.forEach(function (elem) {
      if (Array.isArray(elem)) {
        if (level !== 0) {
          result = result.concat(flatten_recursive(elem, level - 1));
        } else {
          result.push(elem);
        }
      } else {
        result.push(elem);
      }
    });
    return result;
  }
 
  return flatten_recursive(this, level);
};

Example

var s = [ 1, 2, 3 ]           // => [1, 2, 3]
var t = [ 4, 5, 6, [7, 8] ]   // => [4, 5, 6, [7, 8]]
var a = [ s, t, 9, 10 ]       // => [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
a.flatten()                   // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = [ 1, 2, [3, [4, 5] ] ]
a.flatten(1)                  // => [1, 2, 3, [4, 5]]

includes

Returns true if the given object is present in self (that is, if any element === object), otherwise returns false.

Array.prototype.includes = function (obj) {
  var i, len = this.length;
  for (i = 0; i < len; i = i + 1) {
    if (this[i] === obj) {
      return true;
    }
  }
  return false;
};

Example

var a = [ "a", "b", "c" ]
a.includes("b")   // => true
a.includes("z")   // => false

insert

Inserts the given values before the element with the given index.

Negative indices count backwards from the end of the array, where -1 is the last element. If a negative index is used, the given values will be inserted after that element, so using an index of -1 will insert the values at the end of the array.

Array.prototype.insert = function (index) {
  var i;
 
  if (index < 0) {
    index = this.length + index + 1;
  }
  if (index > this.length) {
    index = this.length;
  }
 
  for (i = arguments.length - 1; i > 0; i = i - 1) {
    this.splice(index, 0, arguments[i]);
  }
 
  return this;
};

Example

var a = ["a", "b", "c", "d"]
a.insert(2, 99)         // => ["a", "b", 99, "c", "d"]
a.insert(-2, 1, 2, 3)   // => ["a", "b", 99, "c", 1, 2, 3, "d"]

isEmpty

Returns true if self contains no elements.

Array.prototype.isEmpty = function () {
  return this.length === 0;
};

keepIf

Deletes every element of self for which the given function evaluates to false.

Array.prototype.keepIf = function (f) {
  return this.deleteIf(function (elem) { return !f(elem); });
};

last

Returns the last element(s) of self. If the array is empty, returns undefined if only one element requested.

Array.prototype.last = function (n) {
  var start;
  if (!n) {
    if (this.length === 0) {
      return undefined;
    }
    return this[this.length - 1];
  } else {
    start = this.length - n;
    if (start < 0) { start = 0; }
    return this.slice(start, this.length);
  }
};

Example

var a = [ "w", "x", "y", "z" ]
a.last()     // => "z"
a.last(2)    // => ["y", "z"]

reject

Returns a new array containing the items in self for which the given block is not true.

Array.prototype.reject = function (f) {
  return this.filter(function (x) { return !f(x); });
};

$reject

Same as reject, works on self.

Array.prototype.$reject = function (f) {
  return Array.prototype.delete_if(f);
};

$reverse

Reverses self in place.

Array.prototype.$reverse = function () {
  var i, temp, len = this.length / 2;
  for (i = 0; i < len; i = i + 1) {
    temp = this[this.length - i - 1];
    this[this.length - i - 1] = this[i];
    this[i] = temp;
  }
  return this;
};

rfind

Returns the index of the last object for which the function returns true. Returns undefined if no match is found.

Array.prototype.rfind = function (f) {
  var i, len = this.length;
  for (i = len - 1; i >= 0; i = i - 1) {
    if (f(this[i])) {
      return i;
    }
  }
  return undefined;
};

shuffle

Returns a new array with elements of self shuffled.

Array.prototype.shuffle = function () {
  var i, len = this.length, to, temp, result = [];
 
  function get_random_int(max) {
    return Math.floor(Math.random() * max);
  }
 
  for (i = 0; i < len; i = i + 1) {
    result.push(this[i]);
  }
 
  for (i = 0; i < len; i = i + 1) {
    to = Math.floor(Math.random() * (len - 1));
    temp = result[i];
    result[i] = result[to];
    result[to] = temp;
  }
 
  return result;
};

take

Returns first n elements from the array.

Array.prototype.take = function (n) {
  if (n < 0) {
    throw new RangeError();
  }
  return this.slice(0, n);
};

takeWhile

Passes elements to the function until the function returns false, then stops iterating and returns an array of all prior elements.

Array.prototype.takeWhile = function (f) {
  var pos = 0;
  while (pos < this.length) {
    if (!f(this[pos])) {
      break;
    }
    pos = pos + 1;
  }
  return this.slice(0, pos);
};

uniq

Returns a new array by removing duplicate values in self. Comparison is done by default using the === operator.

If a function is given, it will use the return value of the function for comparison.

Array.prototype.uniq = function (f) {
  var i, j, len = this.length, result = [], found;
  for (i = 0; i < len; i = i + 1) {
    found = false;
    for (j = 0; j < result.length; j = j + 1) {
      if (!f) {
        if (result[j] === this[i]) {
          found = true;
        }
      } else {
        if (f(result[j]) === f(this[i])) {
          found = true;
        }
      }
    }
    if (!found) {
      result.push(this[i]);
    }
  }
  return result;
};

Example

var a = [ "a", "a", "b", "b", "c" ]
a.uniq  // => ["a", "b", "c"]
var b = [["student","sam"], ["student","george"], ["teacher","matz"]]
b.uniq (function(s) { return s[0]; }) // => [["student", "sam"], ["teacher", "matz"]]