Array with event listeners in JavaScript

Extend the arrays in javascript such that an event gets dispatched whenever an item is added or removed.

Example

Input:
const arr = [];
arr.addListener('add', (eventName, items, array) => {
  console.log('items were added', items);
});

arr.addListener('remove', (eventName, item, array) => {
  console.log(item, ' was removed');
});

arr.pushWithEvent('add', [4, 5]);
arr.popWithEvent('remove');


Output:
"items were added" // [object Array] (2)
[4,5]

5 " was removed"

The solution for this is quite straightforward, we will extend the array prototype and a few methods to it like,

  • listeners : This will store the list of event listeners associated with the event name.
  • addListener(eventName, callback) : This will add a callback to the event.
  • pushWithEvent(eventName, items) : Adds all the items in the array and triggers the event with the given name.
  • popWithEvent(eventName) : Removes the last items from the array and triggers the event with the given name.
  • triggerEvent(eventName, args) : A helper function that triggers all the callbacks associated with the given event name.
  • removeListener(eventName, callback) : Removes the callback attached to the eventName. Note: It won’t work for anonymous functions.
// to track the events and their callbacks
Array.prototype.listeners = {};

// to add/assign a new event with listener
Array.prototype.addListener = function(name, callback){
  // if there are no listener present
  // create a new one
  // we will invoke all the callbacks when event is triggered
  if (!this.listeners[name]) {
    this.listeners[name] = [];
  }
  this.listeners[name].push(callback);
}

// add a new method that triggers an event on push
// Calls trigger event
Array.prototype.pushWithEvent = function(event, args) {
  // push the new values
  this.push(...args);
  
  // trigger add event
  this.triggerEvent(event, args);
};

// add a new method that triggers an event on pop
// Calls trigger event
Array.prototype.popWithEvent = function(event, args) {
  // push the new values
  const element = this.pop();
  
  // trigger add event
  this.triggerEvent(event, element);
};

Array.prototype.triggerEvent = function(eventName, elements) {
  // if the event is present
  // trigger all the callbacks with the value
  if (this.listeners[eventName]) {
    this.listeners[eventName].forEach(callback =>
      callback(eventName, elements, this)
    );
  }
};

Array.prototype.removeListener = function(eventName, callback){
  // if event exists
  if(this.listeners[eventName]){
    // filter out the listener
    // note: this won't work for anonymous function.
    this.listeners[eventName] = this.listeners[eventName].filter((e) => e !== callback);
  }
}
Input:
const arr = [];

const onAdd = (eventName, items, array) => {
  console.log('items were added', items);
}

const onAddAgain = (eventName, items, array) => {
  console.log('items were added again', items);
}

arr.addListener('add', onAdd);

arr.addListener('add', onAddAgain);

arr.addListener('remove', (eventName, item, array) => {
  console.log(item, ' was removed');
});

arr.pushWithEvent('add', [1, 2, 3, 'a', 'b']);

arr.removeListener('add', onAddAgain); // removes the second listener

arr.pushWithEvent('add', [4, 5]);
arr.popWithEvent('remove');

console.log(arr);

Output:
"items were added" // [object Array] (5)
[1,2,3,"a","b"]

"items were added again" // [object Array] (5)
[1,2,3,"a","b"]

"items were added" // [object Array] (2)
[4,5]

5 " was removed"

// [object Array] (6)
[1,2,3,"a","b",4]

You can also add a remove listener at the end for memory cleanup.