24

Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?

I don't know if this has already been proposed or asked before; Google searches returned only a myriad number of questions related to the current functionality of Array.push().

Here's an example implementation of this functionality, feel free to correct it:

;(function() {
    var _push = Array.prototype.push;
    Array.prototype.push = function() {
        return this[_push.apply(this, arguments) - 1];
    }
}());

You would then be able to do something like this:

var someArray = [],
    value = "hello world";

function someFunction(value, obj) {
    obj["someKey"] = value;
}

someFunction(value, someArray.push({}));

Where someFunction modifies the object passed in as the second parameter, for example. Now the contents of someArray are [{"someKey": "hello world"}].

Are there any drawbacks to this approach?

Elliot Bonneville
  • 51,872
  • 23
  • 96
  • 123
  • 6
    First, bad idea to change the behavior of a built-in object as it can break existing code. Second, you would change `Array.prototype.push`, not `Array.push`. Besides, you can always get the element you just pushed onto the array if you need it with `array[array.length - 1]` or you could just store it in an intermediate local variable before using `.push()`. Bad, bad idea. – jfriend00 Apr 29 '14 at 03:29
  • 2
    FYI, You also broke the semantics of `.push()` which can take multiple arguments. – jfriend00 Apr 29 '14 at 03:34
  • Thanks for the catch. And yes, I am aware that I can get the most recent object; in fact, I can do it with a one liner: `array[array.push(val) - 1]`. I guess this 'idea' is unnecessary, but I couldn't really see why without getting an outside perspective. Rubber ducking and all that, I suppose. Oh, and fixed semantics. – Elliot Bonneville Apr 29 '14 at 03:35
  • Also, if you want to post your comment as an answer, I'll accept that, since you responded before Bergi. Otherwise, I will accept his answer. – Elliot Bonneville Apr 29 '14 at 03:36
  • Starting an immediately invoked function expression with an empty statement seems pointless. Do you have a reason to do that? – RobG Apr 29 '14 at 03:38
  • 1
    @RobG - if the previous statement doesn't properly end with a semi-colon, what is intended to be an IIFE can look like the start of a calling a function and cause an unintended error or even worse, wrong execution. This has actually happened to me when working on someone else's code who seem to have a broken semi-colon key on their keyboard. – jfriend00 Apr 29 '14 at 03:49
  • @jfriend00—there are many cases where a missing semi colon at the end of a line might cause an issue with following code. The chance that it will occur as the very last line of a script and that other code will be concatenated is possible (and your case actual) I suppose. I think I'd rather find and fix it than defend against it. I don't think it's necessary to start every script with a semi-colon just in case—and in a post here? :-O – RobG Apr 29 '14 at 12:18

7 Answers7

23

See my detailed answer here

TLDR;
You can get the return value of the mutated array, when you instead add an element using array.concat[].

concat is a way of "adding" or "joining" two arrays together. The awesome thing about this method, is that it has a return value of the resultant array, so it can be chained.

newArray = oldArray.concat[newItem];

This also allows you to chain functions together

updatedArray = oldArray.filter((item) => { item.id !== updatedItem.id).concat[updatedItem]};

Where item = {id: someID, value: someUpdatedValue}

The main thing to notice is, that you need to pass an array to concat.
So make sure that you put your value to be "pushed" inside a couple of square brackets, and you're good to go.
This will give you the functionality you expected from push()

You can use the + operator to "add" two arrays together, or by passing the arrays to join as parameters to concat().

let arrayAB = arrayA + arrayB;
let arrayCD = concat(arrayC, arrayD);

Note that by using the concat method, you can take advantage of "chaining" commands before and after concat.

SherylHohman
  • 16,580
  • 17
  • 88
  • 94
  • 4
    I came searching for answers as to why I couldn't chain `Array.prototype.push()` and this is by far the (almost) best answer. The best answer would be `targetArray.concat([newValue]).thenChainMoreStuff...` – Matt Scheurich Nov 22 '18 at 14:11
  • 2
    this is a good answer but why does it say that you can add arrays using the `+`, AFAIK that's not true – CervEd Mar 09 '21 at 21:44
  • 1
    You cannot "add" arrays with `+`, nor is `concat` a "standalone" function. – Dave Newton Dec 22 '22 at 17:06
  • (And: some syntax errors in the `concat` examples, and you don't need to pass an array to `concat`.) – Dave Newton Dec 22 '22 at 17:12
  • @DaveNewton You need to wrap the value you want to push in an array, because if that value is itself an array `concat` will 'unpack' its contents before adding them, which is not what you want. – Malcolm Aug 08 '23 at 17:53
  • @Malcolm It is when it's what you want; this answer tried to discuss "adding arrays", with the implication that `arrA + arrB` would do something like `[1, 2, 3] + [4, 5, 6] = [1, 2, 3, 4, 5, 6]` with the equivalent (and actually working) `concat` solution. – Dave Newton Aug 08 '23 at 18:46
  • @Malcolm But what I said was that you don't have to pass an array to `concat`, e.g., `a1.concat([1, 2, 3])` is the same as `a1.concat(1, 2, 3)` and `a1.concat(4)` . These "unpack" the params, as it should; concat is not the same as push in multiple ways. – Dave Newton Aug 08 '23 at 18:47
13

Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?

Of course there is one: Other code will expect Array::push to behave as defined in the specification, i.e. to return the new length. And other developers will find your code incomprehensible if you did redefine builtin functions to behave unexpectedly.

At least choose a different name for the method.

You would then be able to do something like this: someFunction(value, someArray.push({}));

Uh, what? Yeah, my second point already strikes :-)

However, even if you didn't use push this does not get across what you want to do. The composition that you should express is "add an object which consist of a key and a value to an array". With a more functional style, let someFunction return this object, and you can write

var someArray = [],
    value = "hello world";

function someFunction(value, obj) {
    obj["someKey"] = value;
    return obj;
}

someArray.push(someFunction(value, {}));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I've already thought of this. Does other internal code rely on the returned value being the length of the new array? – Elliot Bonneville Apr 29 '14 at 03:30
  • 1
    @ElliotBonneville You can safely assume plenty of things both assume and use it. – Etheryte Apr 29 '14 at 03:31
  • 1
    @ElliotBonneville: What do you mean by "internal code"? No, no built-in functionality relies on the value of `Array.prototype.push`. Even if anything did use it, since ES5.1 such things are specified to refer to the builtin `_push` function directly and not reference overwritten properties. – Bergi Apr 29 '14 at 03:41
  • @ElliotBonneville—not according to ECMA-262. I doubt very much that internal methods of built–in objects would use public interfaces anyway, their behaviour is expressed purely in terms of internal methods (`[[Get]]`,`[[Put]]`,`[[Delete]]`, etc). – RobG Apr 29 '14 at 03:44
  • @RobG: Yeah, I suppose my question would be a bit silly, if silly questions existed. Thanks. :) – Elliot Bonneville Apr 29 '14 at 03:47
  • @RobG: The `Array` and `Object` constructors are oftentimes referenced, yet always explicitly "*where … is the standard built-in constructor with that name.*" - instead of being vulnerable to [json hijacking](http://stackoverflow.com/a/13040748/1048572) – Bergi Apr 29 '14 at 03:47
  • @Bergi—yes, I mean it doesn't say "execute `new Object()`, but things like "*Return a new object created **as if** by the expression new Object() where Object is the standard built-in constructor with that name*…". Where methods are called, it uses phrases like "*Call the* `[[DefineOwnProperty]]` *internal method*…". – RobG Apr 29 '14 at 11:52
10

Just as a historical note -- There was an older version of JavaScript -- JavaScript version 1.2 -- that handled a number of array functions quite differently.

In particular to this question, Array.push did return the item, not the length of the array.

That said, 1.2 has been not been used for decades now -- but some very old references might still refer to this behavior.

http://web.archive.org/web/20010408055419/developer.netscape.com/docs/manuals/communicator/jsguide/js1_2.htm

Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
5

By the coming of ES6, it is recommended to extend array class in the proper way , then , override push method :

class XArray extends Array {
   
   push() {
     super.push(...arguments);
     return (arguments.length === 1) ? arguments[0] : arguments;
   }
 
}
//---- Application
let list  = [1, 3, 7,5];

list = new XArray(...list);

console.log(
  'Push one item : ',list.push(4)
);

console.log(
  'Push multi-items :', list.push(-9, 2)
);

console.log(
   'Check length :' , list.length
)
Abdennour TOUMI
  • 87,526
  • 38
  • 249
  • 254
5

Method push() returns the last element added, which makes it very inconvenient when creating short functions/reducers. Also, push() - is a rather archaic stuff in JS. On ahother hand we have spread operator [...] which is faster and does what you needs: it exactly returns an array.

// to concat arrays
const a = [1,2,3];
const b = [...a, 4, 5];
console.log(b) // [1, 2, 3, 4, 5];

// to concat and get a length
const arrA = [1,2,3,4,5];
const arrB = [6,7,8];
console.log([0, ...arrA, ...arrB, 9].length); // 10

// to reduce
const arr = ["red", "green", "blue"];
const liArr = arr.reduce( (acc,cur) => [...acc, `<li style='color:${cur}'>${cur}</li>`],[]);
console.log(liArr);
  //[ "<li style='color:red'>red</li>", 
  //"<li style='color:green'>green</li>", 
  //"<li style='color:blue'>blue</li>" ]
Denys Rusov
  • 560
  • 6
  • 6
  • 1
    Can you elaborate on push being archaic? Or is just because the feature is old? (Just for curiosity). Btw, I was looking for a more elegant solution for the very reason of push being inconvenient on reducers, so thanks! – Emilio Grisolía Aug 23 '20 at 15:03
  • @EmilioGrisolía Today, in 99% of cases, we need not mutate an object, that is why now _push_ it is rarely used. – Denys Rusov Aug 25 '20 at 13:42
  • 1
    That is, if you don't care about efficiency. – loop Feb 20 '21 at 10:30
  • Why do we care if it mutates an object or not? Why create a new array when you can append to an existing one? – Jamie Marshall Jun 11 '23 at 21:39
3
var arr = [];
var element = Math.random();
assert(element === arr[arr.push(element)-1]);
brillout
  • 7,804
  • 11
  • 72
  • 84
1

How about doing someArray[someArray.length]={} instead of someArray.push({})? The value of an assignment is the value being assigned.

var someArray = [],
    value = "hello world";

function someFunction(value, obj) {
    obj["someKey"] = value;
}

someFunction(value, someArray[someArray.length]={});

console.log(someArray)
loop
  • 825
  • 6
  • 15