5

I feel like there's probably something incredibly simple I missed in the MDN docs or something, but I've been digging for a while, and I have no clue.

Is there a way to call a function in a similar way to a method? This is basically what I'm trying to do:

function addItem(itemName, quality, quantity /*, arr*/) {
  arr.push([itemName, quality, quantity]);
}

var someArr = [['item', 1, 1]];

someArr.addItem('someOtherItem', 2, 3);
// someArr === [['item', 1, 1], ['someOtherItem', 2, 3]]

Now, mind you, I'm not trying to make a constructor or define a variable as a function. Furthermore, I am fully aware that I could simply add the array as an argument and call the function as normal. What I'm trying to accomplish is running a function in a way that, when notated as an array method, will affect that array in the specified way. How can I do this? Can I do this at all?

Pineda
  • 7,435
  • 3
  • 30
  • 45
JessLovely
  • 142
  • 1
  • 12

2 Answers2

3

DANGEROUS APPROACH (disclaimer :P):
I do not advocate extending native objects, so the following solution serves only as a reference one way to achieve it but is highly discouraged in practice. See: THE DANGERS OF EXTENDING NATIVE OBJECTS

Array.prototype.addItem = function addItem(itemName, quality, quantity) {
  this.push([itemName, quality, quantity]);
}

var someArr = [['item', 1, 1]];

someArr.addItem('someOtherItem', 2, 3);

console.log(someArr); 
// logs out: [["item", 1, 1], ["someOtherItem", 2, 3]]

The use of this inside the new method with be set to the object that the method is called upon. In the case of this example, this will be the array someArr.


A BETTER APPROACH: extend the array instance of someArr with the addItem function (and NOT the native Array object):

var someArr = [['item', 1, 1]];

// set property 'addItem' on someArr to equal named function expression
someArr.addItem = function addItem(itemName, quality, quantity) {
  this.push([itemName, quality, quantity]);
};

someArr.addItem('someOtherItem', 2, 3);

console.log(someArr); 
// logs out: [["item", 1, 1], ["someOtherItem", 2, 3]]
Community
  • 1
  • 1
Pineda
  • 7,435
  • 3
  • 30
  • 45
  • 1
    I... wow. Um... wow. I had figured I could MacGyver it by adding a key or something called `'addItem'` or whatever else to every single array, but I never thought of just routing it through `Array.prototype`. Thanks! – JessLovely Mar 26 '17 at 03:25
  • Even with the alert, you shouldn't advocate changing native objects to a newcomer. It's teaching the wrong and dangerous way of doing things. @Papayaman1000 don't do that. Ever. Read the alert of the dangers of this practice and you'll see why. – Nelson Teixeira Mar 26 '17 at 04:00
  • @NelsonTeixeira: I thought that the link would have been enough. Is it such bad practice to inform OPs of the solution to what they have asked IF the necessary caveats/reading materials of why it isn't a good idea are provided? Either way, I've attempted to address your concerns by putting the warning at the beginning of the answer. – Pineda Mar 26 '17 at 04:10
  • Well in my view it's a bad practice to point a newcomer of a very dangerous path if there are much better alternatives as @AlexeiLevenkov answer pointed out. Some people tend to look for the easy paths and don't read long texts. But anyway, thanks for at least changing the waring to the beginning. Nice of you. :) – Nelson Teixeira Mar 26 '17 at 04:16
  • If you're going to extend `Array.prototype`, at least use `Object.defineProperty` to do so! – Alnitak Mar 26 '17 at 04:24
  • @Alnitak: I have not seen an approach using `Object.defineProperty` before. Got any direct links to hand? – Pineda Mar 26 '17 at 04:47
  • `Object.defineProperty(Array.prototype, 'addItem', { get: yourfunc } )` – Alnitak Mar 26 '17 at 14:54
  • The particular benefit is that the newly added property is _non-enumerable_, meaning it won't appear in a `for .. in` enumeration of the array – Alnitak Mar 26 '17 at 14:55
  • @Alnitak: Ahah. I see, thanks for taking the time to explain. – Pineda Mar 26 '17 at 14:58
  • 1
    @Alnitak Hmm. I've no plans to expand a native prototype, but any prototypes I _do_ expand with a function (such as a subtype for the arrays I'm working with to circumvent native alteration), this will definitely stick in the back of my head. – JessLovely Mar 28 '17 at 18:41
2

Alternatively to modifying Array prototype as shown in other answer you can add method to a particular array (or object) if that works for your case:

function addItem(itemName, quality, quantity /*, arr*/) {
  this.push([itemName, quality, quantity]);
}

var someArr = [['item', 1, 1]];
someArr.addItem = addItem;

someArr.addItem('someOtherItem', 2, 3);

var otherArray = [42];
otherArray.addItem('someOtherItem', 2, 3); // fails as otherArray does not have addItem.

Note that this will not add method to all arrays (or objects), but only to one particular array you've modified. This actually may be beneficial for cases when you add very specific method (like in this case) rather than very general once like sum or max that make sense on many types of arrays (or objects).

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • This is a much better approach that changing the prototype of Array. Augementing native prototypes should be avoided at all costs. – Nelson Teixeira Mar 26 '17 at 03:58
  • Nice one @AlexeiLevenkov. Don't know why I didn't suggest this myself. – Pineda Mar 26 '17 at 04:48
  • I personally prefer other answers provided simply so that, as I learn JSON and the likes, it's easier to generalize to various other arrays without first having to initialize them all with the property (especially since any arrays I'm targeting with this are buried in [sub-*]dictionaries), as well as avoiding having to expand every `.length` test to account for a sometimes-existing item. – JessLovely Mar 28 '17 at 18:27