Search code examples
javascriptnode.jsobject-literalproto

Is it ever a good idea to append functions to a JavaScript Object Literal's __proto__ property?


I have an object literal:

var tasks = {};

That I basically add things to like so:

function addTask(task) {
  tasks[task.id] = task
}

I want to modify that so that I can call a start function on each task. So:

var tasks = {};
tasks.__proto__.start = function(key) {
  // do stuff with this[key]
}

function addTask(task) {
  tasks[task.id] = task
  tasks.start(task.id)
}

I've heard that it's best to avoid the proto object and that it slows down execution. However I'm not re-assigning it, I'm appending to it.

Is there an alternative that would be considered better?


Solution

  • There isn't really any need to use a prototype for this. You're not creating many instances where you need a common functionality abstracted at a higher level, you can just add a method on the tasks object.

    const tasks = {
      start(key) {
        const task = this[key]
        // do stuff with task
      }
    }
    
    // example call
    tasks.start('123'); 
    

    If you want to make sure there's no clash with an existing key, you can use a Symbol instead.

    const startSymbol = Symbol('start');
    const tasks = {
      [startSymbol](key) {
        const task = this[key]
        // do stuff with task
      }
    }
    
    // example call
    tasks[startSymbol]('123'); 
    

    You could also just have a standalone function that does this, similarly to your addTask function:

    function start(tasks, key) {
      const task = tasks[key]
      // do stuff with task
    }
    
    // example call
    start(tasks, '123')
    

    Having this standalone function is probably better because you won't have to worry about clashes between your task keys and method names.

    You could also create a wrapper object that does this separation:

    const taskManager = {
    
      tasks: {} // map of key to task
    
      // methods
      add(task) {
        this.tasks[task.id] = task;
        this.start(task.id);
      }
      start(key) {
        const task = this.tasks[key];
        // do stuff with task
      }
    }
    
    // example usage
    taskManager.start('123')
    

    The advantage of this approach is that your tasks are encapsulated within a container that manipulates on them, constricting the scope in which tasks should be used and making it more clear (suggesting to the programmer) which functions are meant to be used on the tasks.

    If you plan on having multiple task managers, then using prototypes might make sense here:

    class TaskManager {
      constructor() {
        this.tasks = {}  // map of key to task
      }
    
      // methods
      add(task) {
        this.tasks[task.id] = task;
        this.start(task.id);
      }
      start(key) {
        const task = this.tasks[key];
        // do stuff with task
      }
    }
    
    // example usage
    new TaskManager().start('123')