I read before that the only way to change the value a variable holds is using an assignment operator (=, +=, ..)
But in this example from mdn, nums variable returns two different values by changing one of the arrays used in the assignment without using an assignment operator.
var num1 = [[1]];
var num2 = [2, [3]];
var nums = num1.concat(num2);
console.log(nums);
num1[0].push(4);
console.log(nums);
However, when I remove the nested array and just push 4 to num2 array directly, the printed value doesn't change.
Check this example
The concat function returns a new array, comprised of the elements of both arrays.
In the first case, the num1 array and nums contains references to the same inner array, so when you change it, it is reflected in both.
In the second case, the arrays contain numbers, and there is nothing shared between the arrays. That's why when you push to one, the other doesn't change.
As for the assignment confusion, nums always points to the same array until you assign a new value to nums. That doesn't mean you can't change the contents of the array itself.
Related
var arrN = [1, 2, 3];
function init(arr){
arr = [];
console.log(arrN) //output [1, 2, 3], expect []
}
init(arrN);
When using splice or push methods the array passed to a function is being modified. So I am trying to understand what is happening when using assignment operator, why it's not changing the array? is it creating the local var of the passed array?
Any help will be appreciated!
You need to distinguish between the variable and the actual object (the array). splice and push are changing the object.
arr = [] is just changing the variable, the old object just stays as it is.
There is a difference in assigning a different object to a variable or mutating the object currently referenced by a variable.
(Re)assigning
When you do an assignment like:
arr = []; // or any other value
... then the value that arr previously had is not altered. It is just that arr detaches from that previous value and references a new value instead. The original value (if it is an object) lives on, but arr no longer has access to it.
Side note: if no other variable references the previous value any more, the garbage collector will at some point in time free the memory used by it. But this is not your case, since the global variable arrN still references the original value.
Mutating
It is another thing if you don't assign a value to arr, but apply instead a mutation to it, for instance with splice, push, pop, or an assignment to one of its properties, like arr[0] = 1 or arr[1]++. In those cases, arr keeps referencing the same object, and the changes are made to the object it references, which is visible to any other variable that references the same object, like arrN.
Clearing an array
Now if you want to clear the array that is passed to your function, you must avoid to make an assignment like arr = .... Instead, use methods that mutate the array in place:
arr.splice(0)
Or, alternatively:
arr.length = 0;
Now you have actually mutated the array, which is also the array referenced by arrN.
In JavaScript, particularly when working with objects the assignment operator (=) has three jobs.
Assignment
Creating a new reference if the right side is an object.
Breaking any previous referencing and creating multiple assignments if both sides are objects.
Such as;
var a = [1,2,3], // a is assigned an array
b = a; // b is assigned just a reference to a
a = ["a","b","c"]; // b to a referral is now broken
// a is assigned with ["a","b","c"]
// b is assigned with [1,2,3]
same would apply if it was done in reverse order;
var a = [1,2,3], // a is assigned an array
b = a; // b is assigned just a reference to a
b = ["a","b","c"]; // b to a referral is now broken
// b is assigned with ["a","b","c"]
// a keeps it's existing assignment as [1,2,3]
Your are passing arrN to the console instead of passing arr. Also, you just call a function by its name, not by the keyword function. Here is the corrected code:
var arrN = [1, 2, 3];
function init(arr){
arr = [];
console.log(arr)
}
init(arr);
You have also declared init() with an argument arr, which is not needed in this case. What ever the value you pass to init(), the value of arr will be [] because you are reassigning it in the function.
I just want to understand how Javascript arrays work but I have a complicated problem here.
First I created my array:
var arr = [];
And set some elements in it:
arr[5] = "a thing";
arr[2] = undefined;
I thought that I should have an array of size 2, because I only have two objects at 2 specific indexes. So I tested it with the .length property of arrays:
document.write(arr.length + "<br>");
The result, interestingly, is 6. But it must contain two items. How can its size be 6? It is probably related with the latest index that I used, here arr[5] = "a thing";
I then tried to loop over it:
var size = 0;
for(var x in arr){
size++;
}
And the size variable is now 2. So, what I learned from this: if I use a for in loop, I will calculate how many properties are in it, not its last index.
But if I try to document.write(arr[4]) (which is not set yet), it writes undefined.
So why is arr[2] counted in the for..in loop, but not arr[4]?
Let me answer my question: what I was thinking about typeof undefined == undefined which is amazingly true. But this is JavaScript, we need to play with it using his own rules :)
jsFiddle and snippet below.
var arr = [];
arr[5] = "a thing";
arr[2] = undefined;
document.write(arr.length + "<br>");
var size = 0;
for(var x in arr){
size++;
}
document.write(size + "<br>");
document.write(arr[4] + "<br>");
Note: Array indexes are nothing but properties of Array objects.
Quoting MDN's Relationship between length and numerical properties section,
When setting a property on a JavaScript array when the property is a valid array index and that index is outside the current bounds of the array, the engine will update the array's length property accordingly.
Quoting ECMA Script 5 Specification of Array Objects,
whenever a property is added whose name is an array index, the length property is changed, if necessary, to be one more than the numeric value of that array index; and whenever the length property is changed, every property whose name is an array index whose value is not smaller than the new length is automatically deleted
So, when you set a value at index 5, JavaScript engine adjusts the length of the Array to 6.
Quoting ECMA Script 5 Specification of Array Objects,
A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232−1.
So, in your case 2 and 4 are valid indexes but only 2 is defined in the array. You can confirm that like this
arr.hasOwnProperty(2)
The other indexes are not defined in the array yet. So, your array object is called a sparse array object.
So why arr[2] is counted in for..in loop and not arr[4] is not counted?
The for..in enumerates all the valid enumerable properties of the object. In your case, since only 2 is a valid property in the array, it will be counted.
But, when you print arr[4], it prints undefined, because JavaScript will return undefined, if you try to access a property which is not defined in an object. For example,
console.log({}['name']);
// undefined
Similarly, since 4 is not yet defined in the arr, undefined is returned.
While we are on this subject, you might want to read these answers as well,
Why doesn't the length of the array change when I add a new property?
JavaScript 'in' operator for undefined elements in Arrays
There’s a difference between a property that has the value undefined and a property that doesn’t exist, illustrated here using the in operator:
var obj = {
one: undefined
};
console.log(obj.one === undefined); // true
console.log(obj.two === undefined); // true
console.log('one' in obj); // true
console.log('two' in obj); // false
When you try to get the value of a property that doesn’t exist, you still get undefined, but that doesn’t make it exist.
Finally, to explain the behaviour you see: a for in loop will only loop over keys where that key is in the object (and is enumerable).
length, meanwhile, is just adjusted to be one more than whatever index you assign if that index is greater than or equal to the current length.
To remove undefined values from array , try utilizing .filter()
var arr = [];
arr[5] = "a thing";
arr[2] = undefined;
arr = arr.filter(Boolean);
document.write(arr.length);
It all comes down the idea of how space is handled by machines. Let's start with the simplest idea of:
var arr =[];
This in turn creates a location where you can now store information. As #Mike 'Pomax' Kamermans pointed out: This location is a special javascript object that in turn functions as a collection of keys and values, like so:
arr[key] = value;
Now moving on through your code:
arr[5] = "a thing";
The machine now is understanding that you are creating something in the (giving value to the) 6th position/5th key (as array's first position is 0). So you wind up with something that looks like this:
arr[,,,,,"a thing"];
Those commas represent empty positions (elisions as #RobG pointed out) in your array.
Same thing happens when you declare:
arr[2] = undefined;
arr[,,undefined,,,"a thing"];
So when you're iterating inside an array using "for var in" you're checking for each one of the spaces in this array that are populated, so in turn 2.
As a difference, when you check for the length of the array, you're looking to see how many spaces to store information exist inside the array, which in turn is 6.
Finally, javascript interprets empty room in an array as unidentified values, which is the reason arr[4] is being outputted as such.
Hope that answered your question.
JavaScript arrays, at least in the first version, were plain object with a length property. Most of the weird behaviour you experienced is a consequence of this.
Result interesting, it is 6. But it must contain two data, how its
size can be 6? It is probably related with the latest index that I
used here arr[5] = "a thing";
It results in 6 because the length is always 1 higher than the highest index, even if there are actually fewer items in the array.
o why arr[2] is counted in for..in loop and not arr[4] is not counted?
because when you are doing:
arr[2] = undefined;
You are actually adding a key called 2 to the array object. As result, the value arr[2] is counted in the for in loop, while the a[4] is ignored.
The assignment sets the property of the array, so that when you do the for var i in style for loop, you only see properties that have been set (even if you set them to be undefined). When you assign a new integery property such as arr[6] the array modifies the length of the array to be 7. The memory underlying the array may or may not be reallocated accordingly, but it will be there for you when you go to use it - unless your system is out of memory.
Edited according to RobG's comment about what ECMA-262 says.
Suppose you have the following code:
var array = [1];
var array2 = array;
array2.push(2);
alert(array);
This will alert "1,2", even though I clearly declared array as [1], and then did no changes to it. It's like array and array2 gets connected, and every change I do to array2 affects array. I simply want to declare array2 with the same value as array, not connect them.
Why does this happen and how to prevent it?
Thanks in advance!
jsFiddle
The problem is, that array2 is pointing on array.
what you want is:
var array2 = array.slice(0);
This really creates a copy of "array" and does not let the variable array2 point on "array".
It's because arrays and objects in JS are "passed by reference" (actually a copy of reference), not value (as it happens with primitives). There are many ways to do it, one of them being concatting an empty array:
var array = [1];
var array2 = array.concat([]);
array2.push(2);
alert(array); // 1
alert(array2); // 1,2
See some nice answers here: Does Javascript pass by reference?
Befor all , in Javascript an array is an Object
so the array variable is pointing to an in array (that you created [1]) that has an adresse in the memory (for example #1234abc). So the variable array it self is a pointer that points to an array.
You did var array2 = array
so array2 will point to the same adress in the memory (to the same object).
In other words, array2 is array they are pointing to the same object.
To get what you want, you have to create a new array and assign the same values of array1, this is called cloning.
You can do it with this trick var array2 = array.slice(0);
array2 is a reference to the original array, so if you modify it, the original will change too.
You´ll have to set array2's value to array.slice(), which clones the original and gives you a new reference.
It is because of concept Pass by reference. Internally both the variables are pointing to same memory location. So, when you change one variable that will effect other variables as well. Try slice() method if you intend to copy only values but not reference as it is. :)
In JavaScript, how do I mutate the value of an array inside of a function? I know that happens when using certain array methods, but it doesn't seem to work for normal assignment.
var arr = [4]
function changeArray(arr) {
arr = [1,2,3];
}
// arr is still equal to [4]. I want it to equal [1,2,3].
It's my understanding that this code doesn't change the value of "arr" outside of the function. How can I do it in a way that mutates the array that was passed as an argument?
You can use .splice:
arr.splice(0, arr.length, 1, 2, 3);
Or you can empty the array and .push the values:
arr.length = 0;
arr.push(1, 2, 3);
// or given an array of values
arr.push.apply(arr, newValues);
However, make sure it is clear that the function changes the array in place. While there are native functions that does this (like Array#sort), I'd argue that this is not as common as simply returning a new array.
It's my understanding that this code doesn't change the value of "arr" outside of the function.
Correct. Assigning a new value to a variable or property never changes the value of another variable or property (exceptions: global scope and with statements).
You basically tried to change the value of a variable, not mutate the value itself.
JavaScript is call/pass/assign by value (Wikipedia also mentions "by sharing"), not by reference.
Any programming language which provides arrays (lists, vectors, tuples etc.) must decide whether they have reference or value semantics, with the usual/obvious choice being reference semantics for mutable arrays and value semantics for immutable ones.
JavaScript which provides mutable arrays appears to have chosen reference semantics e.g. given
var a = [1, 2, 3]
var b = [1, 2, 3]
then a != b, as expected because though they have the same contents, they are different arrays.
However when you use them as keys in an object, the picture changes; if you set obj[a] to a value, then obj[b] gets the same value. Furthermore, this remains true if you change the contents of the arrays; at least when I tested it in Rhino, it behaves as though the interpreter were recursively comparing the full contents of the supplied and stored key arrays on every lookup, complete with a check for the infinite loop that would occur if one of the arrays were made to point to itself.
Is this the intended/specified behavior in all implementations?
Does it also apply to objects used as keys?
Is there any way to get the other behavior, i.e. to look up values using arrays as keys with reference semantics?
When arrays are used as property names they are cast to a string:
[1,2,3].toString() == '1,2,3'
Once turned into a string value, arrays with the same contents would map to the same property.
To answer your last question, you can't use objects to reference property names (keys) whereby only the same object maps to the same property (1:1 mapping).
obj[a] and obj[b] will run the toString function on the arrays and produce the same result for both. It doesn't try to use the arrays as keys.
var a = [1,2,3];
var x = {};
x[a] = "test";
var i;
for(i in x)
{
alert(i); //"1,2,3"
}
jsFiddle example