Javascript arrays remaining identical after one is changed - javascript

I am making an app in which an algorithm is run on an array. Because the contents of the array will change during the execution of the algorithm, I'm storing the array contents to another array beforehand - performing the 'if' statements on the source array but updating the temporary array, then equating them afterwards.
The problem is that after running the algorithm, the two arrays are still identical. It seems that updating the temporary array automatically updates the source array.
I've created this jsfiddle to demonstrate:
var a = new Array( 0 , 1 , 2 );
var b = a;
b[1]=3;
document.write( (a[1]==b[1]) );
//Should show 'false' as this will not be correct
The code above returns "True". Is this normal behaviour? How can I overcome this?

That is normal behaviour, when copying array you are actually making a reference, so when you do var b = a that means instead of copying value you are just copying the reference
if you want to shallow copy an array (array with only one depth level) then you can use simple method like this:
var b = a.slice(0);
But if you want to deep copy an array (array with 2 or more depth level) then you can use below method for that:
var objClone = function(srcObj) {
var destObj = (this instanceof Array) ? [] : {};
for (var i in srcObj)
{
if (srcObj[i] && typeof srcObj[i] == "object") {
destObj[i] = objClone(srcObj[i]);
} else {
destObj[i] = srcObj[i];
}
}
return destObj;
};
For use of both of these methods check this jsFiddle

b = a doesn't copy the array, but a reference. Use b = a.slice() to make a copy.
See also ...

As others have mentioned a = b does not copy the array. However, if you do end up using the .slice() method to copy arrays please note that objects within an array will be copied by reference.
Take the following example
var obj = { greeting: "hello world" };
var a = [ obj ];
var b = a;
a[0].greeting = "foo bar";
b[0].greeting // => "foo bar";
Other than that you are good to go :)

Related

Why do i get just a number from console.log(Array.push()); in Javascript? [duplicate]

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?
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.
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, {}));
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
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
)
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>" ]
var arr = [];
var element = Math.random();
assert(element === arr[arr.push(element)-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)

For a deep copy of a JavaScript multidimensional array, going one level deep seems sufficient. Is this reliably true?

Note: I'm only a novice coder, so there might be a glaring error or misconception at the heart of this question.
Essentially, I need to deep copy multidimensional arrays 'by value' in JavaScript to an unknown depth. I thought this would require some complex recursion, but it seems that in JavaScript you only need to copy one level deep in order to copy the whole array by value.
As an example, here is my test code, using a deliberately convoluted array.
function test() {
var arr = [ ['ok1'],[],[ [],[],[ [], [ [ ['ok2'], [] ] ] ] ] ];
var cloned = cloneArray(arr);
arr = ''; // Delete the original
alert ( cloned );
}
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
In my running of this test (latest stable Chrome and Firefox on Ubuntu), even the deepest parts of the array seem to be successfully copied by value in the clone, even after the original is deleted, despite the fact that the slice() "copying" only went one layer deep. Is this the standard behaviour in JavaScript? Can I depend on this to work for older browsers?
Your test is flawed for whether a true copy is being made which makes your conclusion incorrect that you are getting a full copy of all the data in the nested arrays. You are only doing a two level copy, not an N level copy.
Javascript is a garbage collected language so you don't actually delete variables or objects and, even if you tried that doesn't affect the same variable if it's being referenced somewhere else in your code. To see if you truly have a completely independent copy, try nesting an object two levels deep and then change a property on the object in the original array. You will find that the same object changes in the cloned array because you aren't doing a deep clone. Both arrays have a reference to the exact same object.
Here's an example.
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
var x = [[{foo: 1}]];
var y = cloneArray(x);
x[0][0].foo = 2;
// now see what the value is in `y`
// if it's 2, then it's been changed and is not a true copy
// both arrays have a reference to the same object
console.log(y[0][0].foo); // logs 2
The same result would happen if the third level was another array too. You will have to recursively traverse every element that is an object type and then clone that object itself to get a complete clone of everything in the nested arrays.
If you want code that will do a deep copy (to an arbitrary level) and work for all data types, see here.
FYI, your cloneArray() function assumes that all first level members of your array are arrays themselves and thus doesn't work if it contains any other type of value.
Array.prototype.slice is not suitable for cloning arrays
This should work well for you
function deepClone(arr) {
var len = arr.length;
var newArr = new Array(len);
for (var i=0; i<len; i++) {
if (Array.isArray(arr[i])) {
newArr[i] = deepClone(arr[i]);
}
else {
newArr[i] = arr[i];
}
}
return newArr;
}
If you need to support older browser, make sure to use this polyfill (via MDN)
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Your code does not work:
If your array contains other variables such as numbers or strings (not only arrays), it will fail, because it will call arr[i].slice(), but since that arr[i] is a number, it hasn't got any .slice property and this will throw an error.
Your function will, in any case, leave all the references to objects and other stuff inside the array alive. So you will not actually obtain a copy of your array.
Example:
var a = [1,2, [11,13], ['ok']];
var b = cloneArray(a);
> TypeError: undefined is not a function // because numbers have no .slice method
Solution:
To copy an array you will need to make a deep copy of it. Since that creating a deep copy will need a function that uses recursion to deep copy any object or array inside of the main one, the easiest way to do it is by using jQuery and its .extend method, which performs a deep copy of the array, see here for more info.
var a =[[1], [2], [3]];
var b = $.extend(true, [], a);
b[0][0] = 99;
a[0][0] // still 1
Here's my recursive approach to "cloning" a multidimensional array. It runs all the way down to the deepest levels:
if ( !Array.clone )
{
Array.prototype.clone = function()
{
var _arr = ( arguments[0] == null ) ? [] : arguments[0] ;
for( var _p = 0 ; _p < this.length ; _p++ )
{
if ( this[_p] instanceof Array )
{
var _sub = [] ;
this[_p].clone( _sub ) ;
_arr.push( _sub.slice() );
}
else _arr.push( this[_p] );
}
return _arr ;
}
}
Now try this code:
var _a = [ "a", "b", [ "c", "d", [ "e", "f" ] ] ];
var _b = _a.clone();
console.log( _b );
Vars _a and _b are two distinct objects: if you remove an element from var _b, then var _a would not be affected.

Strange behavior in jQuery loop with arrays

I am having an issue with the following code:
var samount = [{value:100, name:'USD'},
{value:200, name:'USD'},
{value:100, name:'USD'}];
var object1 = new Array;
objects1 = samount;
var categories1 = new Array();
var groupedObjects1 = [];
var output1 = '';
var i = 0;
console.log(samount);
_.each(objects1,function(obj){
var existingObj;
if($.inArray(obj.currency,categories1) >= 0) {
existingObj = _.find(objects1,function(o){return o.currency === obj.currency;});
existingObj.value += obj.value;
} else {
groupedObjects1[i] = obj;
categories1[i] = obj.currency;
i++;
}
});
console.log(samount);
console.log(groupedObjects1);
The problem is that I do not want that samount variable to change after looping, so I have done this:
var object1 = new Array;
objects1 = samount;
The goal of this script is to sum up all values from the same currencies, but still to not mess with the initial array.
But it still changes the initial Array. Could anyone help me with this error?
Copy the array with slice
var objects1 = samount.slice(0);
Arrays and object are passed by "reference" (not really, but doesn't matter here), so when assigning an array to a new variable, all you get is a reference to the same array, not a new array.
You need to deep copy the initial array instead of affecting it.
var samount = [{value:100, name:'USD'},
{value:200, name:'USD'},
{value:100, name:'USD'}];
var object1 = $.extend(true, [], samount);
You were doing an affectation (i.e. 2 variables pointing to the same object) where you needed a copy (2 variables pointing to 2 different objects)
The problem is you're not creating a copy with
objects1 = samount
In most OO languages, you can only copy primitive types so int, strings characters etc, but not objects.
This is somewhat similar in javascript in the sense that {} is an object, same as Array or [].
So if you want to copy an object you'd have to iterate over every element of that object till you find an primitive type. This can be tedious and quite hard even (http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/) this is called a deep copy.
But in this case a shallow copy (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) is enough
var objects1 = samount.slice(0);

How do I pass the value instead of the reference of an array?

I have this structure:
var a = [];
a.push({"level": 1, "column": 4, "parent": "none", "title": "Node 0", "content": "Parintele suprem", "show": "1"});
var b = a;
a.push({"level": 1, "column": 5, "parent": "none", "title": "Node 1", "content": "Parintele suprem", "show": "1"});
console.log(b);
Now the problem is that b has the exact content as a (the content after the second push). This suggest (correct me if I'm wrong) that when I said b = a I actually gave b same reference as a, so whatever I do in a I have in b. The thing is that I need to pass the value. So I have the previews a, value in b.
Edit to make the question more clear: How do I pass the value instead of the reference?
I think you can use this to copy the value instead of the reference:
var b = a.slice(0);
EDIT
As the comments have mentioned and it's also mentioned here: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/slice
slice does not alter the original array, but returns a new "one level deep" copy that contains copies of the elements sliced from the original array. Elements of the original array are copied into the new array as follows:
For object references (and not the actual object), slice copies
object references into the new array. Both the original and new array
refer to the same object. If a referenced object changes, the changes
are visible to both the new and original arrays.
For strings and numbers (not String and Number objects), slice copies
strings and numbers into the new array. Changes to the string or
number in one array does not affect the other array.
If a new element is added to either array, the other array is not affected.
you can implement a clone method as follow:
function clone(source) {
var result = source, i, len;
if (!source
|| source instanceof Number
|| source instanceof String
|| source instanceof Boolean) {
return result;
} else if (Object.prototype.toString.call(source).slice(8,-1) === 'Array') {
result = [];
var resultLen = 0;
for (i = 0, len = source.length; i < len; i++) {
result[resultLen++] = clone(source[i]);
}
} else if (typeof source == 'object') {
result = {};
for (i in source) {
if (source.hasOwnProperty(i)) {
result[i] = clone(source[i]);
}
}
}
return result;
};
then:
var b = clone(a);
if you are sure that a is Array, only use Niklas's:
var b = a.slice();
ps: my english is poor:)
Yes, that's the way reference assignment works in javascript. You want to clone the object to make a copy, which is unfortunately more involved than it should be. Frameworks like MooTools provide the simplest solution, or you can roll your own clone function.
The easiest way i could find is to just make a json string and then parse the data.
var assiging_variable = JSON.parse(JSON.stringify(source_array));
This also works if the source array is a multidimensional array, the array.slice(0) method does not work for multidimensional array.
I found a nicer way by using "Object.assign({}, source_object )":
let source_obj = {val1:"A",val2:"B"};
let copy_obj = Object.assign({}, source_obj);

Difference between Array.length = 0 and Array =[]?

Can some one explain the conceptual difference between both of them. Read somewhere that the second one creates a new array by destroying all references to the existing array and the .length=0 just empties the array. But it didn't work in my case
//Declaration
var arr = new Array();
The below one is the looping code that executes again and again.
$("#dummy").load("something.php",function(){
arr.length =0;// expected to empty the array
$("div").each(function(){
arr = arr + $(this).html();
});
});
But if I replace the code with arr =[] in place of arr.length=0 it works fine. Can anyone explain what's happening here.
foo = [] creates a new array and assigns a reference to it to a variable. Any other references are unaffected and still point to the original array.
foo.length = 0 modifies the array itself. If you access it via a different variable, then you still get the modified array.
Read somewhere that the second one creates a new array by destroying all references to the existing array
That is backwards. It creates a new array and doesn't destroy other references.
var foo = [1,2,3];
var bar = [1,2,3];
var foo2 = foo;
var bar2 = bar;
foo = [];
bar.length = 0;
console.log(foo, bar, foo2, bar2);
gives:
[] [] [1, 2, 3] []
arr.length =0;// expected to empty the array
and it does empty the array, at least the first time. After the first time you do this:
arr = arr + $(this).html();
… which overwrites the array with a string.
The length property of a string is read-only, so assigning 0 to it has no effect.
The difference here is best demonstrated in the following example:
var arrayA = [1,2,3,4,5];
function clearUsingLength (ar) {
ar.length = 0;
}
function clearByOverwriting(ar) {
ar = [];
}
alert("Original Length: " + arrayA.length);
clearByOverwriting(arrayA);
alert("After Overwriting: " + arrayA.length);
clearUsingLength(arrayA);
alert("After Using Length: " + arrayA.length);
Of which a live demo can be seen here: http://www.jsfiddle.net/8Yn7e/
When you set a variable that points to an existing array to point to a new array, all you are doing is breaking the link the variable has to that original array.
When you use array.length = 0 (and other methods like array.splice(0, array.length) for instance), you are actually emptying the original array.
Are you sure it really works?
I did a little experiment here, and trying to "add" an Array with a String resulted in a string.
function xyz(){
var a = [];
alert(typeof(a+$("#first").html()));
// shows "string"
}
http://www.jsfiddle.net/4nKCF/
(tested in Opera 11)

Categories