3

I am looking for the best way to replace or add to elements of an array without deleting the original reference. Here is the set up:

var a = [], b = [], c, i, obj;
for ( i = 0; i < 100000; i++ ) { a[ i ] = i; b[ i ] = 10000 - i; }
obj.data_list = a;

Now we want to concatenate b INTO a without changing the reference to a, since it is used in obj.data_list. Here is one method:

for ( i = 0; i < b.length; i++ ) { a.push( b[ i ] ); }

This seems to be a somewhat terser and 8x (on V8) faster method:

a.splice.apply( a, [ a.length, 0 ].concat( b ) );

I have found this useful when iterating over an "in-place" array and don't want to touch the elements as I go (a good practice). I start a new array (let's call it keep_list) with the initial arguments and then add the elements I wish to retain. Finally I use this apply method to quickly replace the truncated array:

var keep_list = [ 0, 0 ];
for ( i = 0; i < a.length; i++ ){ 
  if ( some_condition ){ keep_list.push( a[ i ] );
}

// truncate array
a.length = 0;

// And replace contents
a.splice.apply( a, keep_list );

There are a few problems with this solution:

  • there is a max call stack size limit of around 50k on V8
  • I have not tested on other JS engines yet.
  • This solution is a bit cryptic

Has anyone found a better way?

11
  • Setting arr.length = 0 will keep the instance itself alive whilst removing all items in the Array. Commented Oct 30, 2013 at 2:50
  • Let's say you have an array that, then what ? Commented Oct 30, 2013 at 2:55
  • There's a great website for performance testing: jsperf.com Commented Oct 30, 2013 at 2:59
  • Ok, the question is complete. This is a method I dug up to concatenate arrays or replace 'in-place'. While it's not really posted as a question, I would certainly appreciate feedback. For instance, it may work horribly on IE or Firefox, or there may be a better way. In any event, I thought it clever enough to share. Commented Oct 30, 2013 at 2:59
  • @MichaelMikowski StackOverflow is designed for question-answer style items, and you can answer your own questions. If you think the community would benefit from this information, why not structure your data such? As for feedback, don't forget that push can take multiple args too, and that you can have RangeError: Maximum call stack size exceeded by passing too many args into functions (as you're talking about large numbers). Commented Oct 30, 2013 at 3:20

1 Answer 1

1

Probably that the most comprehensive way yet still efficient would be to use push.

var source = [],
    newItems = [1, 2, 3];

source.push.apply(source, newItems);

If you ever reach the maximum call stack, you could seperate the operation into multiple batches.

var source = [],
    newItems = new Array(500000),
    i = 0,
    len = newItems.length,
    batch;

for (; i < len; i++) newItems[i] = i;

//You need to find a real cross-browser stack size, here I just used 50k
while((batch = newItems.splice(0, 50000)).length) {
    source.push.apply(source, batch);
}

console.log(source[499999]);

Also keep in mind that expensive operations might hang the browser, especially in old browsers that have slow JS engines. To circumvent the issue, you could further split the process into smaller batches and let the browser breath by using setTimeout.

Finally another approach I thought of would be to use a wrapper object around your array which would allow you to replace the array directly since your references would be retained through the object.

var arrWrapper = { list: [] },
    obj1 = { items: arrWrapper },
    obj2 = { items: arrWrapper };

//update the array
obj2.items.list = [1, 2, 3, 4];

//access the array
obj1.items.list;

The only restriction would be to avoid keeping a reference to arrWrapper.list directly.

Note: If you are targetting modern browsers only, you could probably make use of WebWorkers. However as far as I know, you can only pass serialized data which means that the worker wouldn't be able to modify the source array directly.

Sign up to request clarification or add additional context in comments.

5 Comments

Works well +1. The way the question was posed was as if OP didn't want to actually refactor code anywhere else, meaning I'm not sure if this will suit the "situation". If it was a modern browser, surely a getter and setter would be cleaner than a webworker?
@PaulS. Could you explain how custom getters/setters would help? I am probably missing something, but I tried to think about it and haven't found a better solution involving them.
Object.defineProperty(obj, 'data_list', {get: function () {return a;});, then assuming you're modifying a = [] in the same closure later, the getter updates with the change.
To avoid call-stack overflow and avoid per-browser-limit-hell, one might just start with a reasonable batch size (say 50k), and then use try-catch block with interpolation to determine max-per-browser stack size. The value may be calculated once and stored in a static variable. The wrapper approach tries to avoid the problem altogether, but the array still gets clobbered, which is simply unacceptable in many situations. Example: obj = { list : [ 1,2,3] }; a = obj.list; obj.list = [3,4,5]; b = obj.list; Notice a !== b. Only the key remains the same.
@MichaelMikowski Well, it's because you should treat the array wrapper as if it was the array reference. If you never store the array directly, you will never have that issue. That's a design decision that needs to be enforced in code. However, to avoid multiple property lookups, there's nothing preventing you from storing the real array in a local reference for quick lookups.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.