-1

I use certain algorithms so often that I am considering adding them to prototypes in my app. For example, the last value in an array. array.last() is so convenient compared to writing arr[arr.length -1] every time I want the last value in the array.

I would do this when my app first loads.

Object.defineProperty(Array.prototype, 'last', {
   value: function(){return this[this.length -1]} })

Are there functional reasons not to do this?

Jeremy Gottfried
  • 1,061
  • 13
  • 28
  • 1
    What happens when the next library you load *also* wants to add a helper function called "last" to Arrays and their function signature doesn't match your own? – zero298 Feb 13 '19 at 21:39
  • It is fine to add it if you do it in an application and not a library, also do it like polyfills, first check if `array.prototype.last` exists, then add it if it doesn't. If another library that you depend on is overriding core functionality, then you should probably not use it, unless the library literally focused on overriding certain parts, because it cam create side effects for other libraries as well as your application. – Pavlo Feb 13 '19 at 21:45
  • @Pavlo Until some future ES standard specifies a native `Array.prototype.last` and libraries that you want to use begin to depend on it. And no, you should *not* check if it exists, you should always install your own version as that's the functionality your code will expect. – Bergi Feb 13 '19 at 21:53

1 Answers1

0

Yes, there are reasons to not do this, one that immediately jumps out is that you will inevitably collide with another library that also thinks it is easier to adjust built-in prototypes.

Consider the following:

// my-cool-library.js

// Returns the last value in an array
Array.prototype.last = function() {
    return this[this.length - 1];
}

// some-other-cool-library.js

// Returns the last possible index of the array
Array.prototype.last = function() {
    return this.length - 1;
}

// some-consumer.js

import "my-cool-library.js";
import "some-other-cool-library.js";

const a = ["a", "b", "c"];

// You would probably expect it to print "c", but it prints 2
console.log(a.last());

You might think that this is unlikely, but what if you utilize really big frameworks? Let's say you use both Angular and lodash. It isn't all the unlikely that a huge framework like Angular would want to make life easier by adding some helper functions to certain Object prototypes. However, lodash is a very broad scoped library that also adds helper functions for just about every operation you might want to perform on a collection.

It is very likely that both of those libraries would want to use the same, terse helper function names but might not have the same function signature. Suddenly it becomes non-apparent how you should call and use Array.prototype.last.

Instead, it is more appreciated when you leverage dependency injection and write functions that take all the arguments needed to perform the calculation and don't pollute the prototype. In that way, you get to decide exactly which last function is used and when.

You can potentially also leverage the benefits of tree shaking.

Consider the non-polluting example:

// my-cool-library.js

// Returns the last value in an array
export function last(arr) {
    return arr[arr.length - 1];
}

// some-other-cool-library.js

// Returns the last possible index of the array
export function last(arr) {
    return arr.length - 1;
}

// some-consumer.js

import {last as myLast} from "my-cool-library.js";
import {last} from "some-other-cool-library.js";

const a = ["a", "b", "c"];

// You know that you're going to use myLast 
// and how it is going to act
console.log(myLast(a));
zero298
  • 25,467
  • 10
  • 75
  • 100
  • Not sure why anyone would downvote this. But since everyone knows you shouldn't mutate the prototype, "you will inevitably collide with another library" isn't necessarily true. Which means it's ok to do now... right? /s – Evan Trimboli Feb 13 '19 at 22:00