Array filter changes the main array - javascript

I've noticed some strange behavior in the array filters on node.js.
There is a simple array and a loop:
var array = [
{
name:"bob",
planet:"earth"
},
{
name:"mike",
planet:"mars"
},
{
name:"vlad",
planet:"jupiter"
}];
var filtered = array.filter(function(x){
return x.name !== "mike";
});
console.log(array); //lets print how normal array looks like
console.log("---");
console.log(filtered); //lets print how filtered one looks like
for(var i = 0; i < filtered.length; i++)
{
delete filtered[i].planet; //remove planet
filtered[i].name = filtered[i].name + "[NEW]"; //add NEW to the name
}
console.log("After replacement:");
console.log(array);//lets print how normal array looks like now
console.log("-----------");
console.log(filtered);//lets print how filtered array looks like now
In theory, array array should not be changed, since I did not manipulate it in any way. Hovewer, this is what I get in console:
[ { name: 'bob', planet: 'earth' },
{ name: 'mike', planet: 'mars' },
{ name: 'vlad', planet: 'jupiter' } ] //this array is normal
---
[ { name: 'bob', planet: 'earth' },
{ name: 'vlad', planet: 'jupiter' } ] //this is good behavior, since I don't need "mike"
After replacement:
[ { name: 'bob[NEW]' },
{ name: 'mike', planet: 'mars' },
{ name: 'vlad[NEW]' } ] //this should not be changed in any way
-----------
[ { name: 'bob[NEW]' }, { name: 'vlad[NEW]' } ] //this is correct
Why does this happen? I need array to stay the same as in the beginning.
Thanks.

Your code here:
for(var i = 0; i < filtered.length; i++)
{
delete filtered[i].planet; //remove planet
filtered[i].name = filtered[i].name + "[NEW]"; //add NEW to the name
}
...isn't changing either array. It's changing the state of the objects that both arrays refer to.
Simpler example:
var a = [{answer:null}];
var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis
a[0].answer = 42;
console.log(b[0].answer); // 42
This line:
a[0].answer = 42;
doesn't change a or b, it changes the state of what a[0] refers to, which b[0] also refers to.
Let's throw some ASCII-art Unicode-art at it:
After this line:
var a = [{answer:null}];
this is what we have in memory (ignoring some irrelevant details);
+−−−−−−−−−−−−−−+
| variable "a" |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| Ref11542 |−−−−>| array |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| 0: Ref88464 |−−−−>| object |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| answer: null |
+−−−−−−−−−−−−−−+
a refers to an array object, which has a 0 property, which refers to the object with the answer property. I'm using "Ref11542" and "Ref88494" to represent the object references that a and a[0] contain, but of course we never actually see the value of those references; they're private to the JavaScript engine.
Then we do this:
var b = a.filter(function() { return true; }); // Just a copy, but using `filter` for emphasis
Now we have:
+−−−−−−−−−−−−−−+
| variable "a" |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| Ref11542 |−−−−>| array |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| 0: Ref88464 |−−+
+−−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−>| object |
| variable "b" | | +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ | | answer: null |
| Ref66854 |−−−−>| array | | +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ |
| 0: Ref88464 |−−+
+−−−−−−−−−−−−−−+
Note that both arrays contain the same object reference (shown here as "Ref88464"); they point to the same object.
Now we do this:
a[0].answer = 42;
All that does is change the state of the object; it has no effect on a or b or the arrays they refer to:
+−−−−−−−−−−−−−−+
| variable "a" |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| Ref11542 |−−−−>| array |
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+
| 0: Ref88464 |−−+
+−−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−>| object |
| variable "b" | | +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ | | answer: 42 |
| Ref66854 |−−−−>| array | | +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−+ | ^
| 0: Ref88464 |−−+ +−−−−−−− only change is here
+−−−−−−−−−−−−−−+
So naturally
console.log(b[0].answer);
...outputs 42.
In a comment you've asked:
But then how do I set the state of filtered object and not the main one?
Remember, the objects are the same in both arrays. You haven't copied the objects, you've just created a new array that only has some of them in it.
If you want to be able to change those objects' properties without affecting the objects in the first array, you need to make copies of the objects.
If the objects contain only simple primitive values, that's easy; the general case is quite hard. :-)
In your case, though, since your objets just have name and planet properties, and the very next thing you do is remove the planet property and change the name, we can easily create objects as we go; see comments:
var array = [
{
name:"bob",
planet:"earth"
},
{
name:"mike",
planet:"mars"
},
{
name:"vlad",
planet:"jupiter"
}];
var filtered = array.filter(function(x){
return x.name !== "mike";
}).map(function(x) {
// Create a *new* object, setting its `name` to the `name`
// of the original object plus [NEW], and ignoring its
// `planet` property entirely
return {name: x.name + "[NEW]"};
});
console.log(array);
console.log("---");
console.log(filtered);
Alternately, you might want to make just one pass through the array:
var filtered = [];
array.forEach(function(x){
if (x.name !== "mike") {
filtered.push({name: x.name + "[NEW]"});
}
});

Related

How to freeze var value in array app script? [duplicate]

var flagArray = [];
var flagTarget = document.getElementById("content");
var flag = {
init: function(country) {
if (country) {
this.country = country;
flagArray.push(Object.create(flag));
}
},
draw: function() {
var pos = flagArray.indexOf(this.country);
if (this.country === "norway") {
flagArray[pos] = '<div class="flag ' + this.country + '"><div class="part1"></div><div class="part2"></div><div class="part3"></div><div class="part4"></div></div>';
} else {
flagArray[pos] = '<div class="flag ' + this.country + '"><div class="part1"></div><div class="part2"></div></div>';
}
flagTarget.innerHTML += flagArray[pos];
},
};
flag.init("ivorycoast");
flag.init("sweden");
flag.init("denmark");
flag.init("finland");
flag.init("norway");
flagArray.forEach(function(flag) {
flag.draw();
});
I've been struggling with this school assignment for too many hours now. Why does the last flag.init value get written 5 times to the array?
It doesn't, but it seems like it does.
What's happening is that you're updating country on flag five times, and each time you're creating a new object that inherits from flag and pushing that on the array. So the end result is five different objects in the array, all of which inherit from flag, and none of which have their own country value. So the country you see on them is the one inherited from flag, which is the last value you assigned to this.country.
If you want them to inherit from flag but each have their own country value, you need to remember the object created with Object.create and set country on it:
init: function(country) {
if (country) {
var newFlag = Object.create(flag);
flagArray.push(newFlag);
newFlag.country = country;
}
},
(There's a more advanced way I show after the following diagrams.)
Side note: There's a separate problem in draw, this line:
var pos = flagArray.indexOf(this.country);
should just be
var pos = flagArray.indexOf(this);
Because the array contains objects, not country strings. That said, you don't need to use flagArray in draw at all.
Let's throw some ASCII-Art (actually, Unicode-art) at your current code to understand what you're seeing:
You start out with this in memory:
+−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−>| (object) |
+−−−−−−−−−−−−−−−−−−−−−−−+
| init: (function) |
| draw: (function) |
+−−−−−−−−−−−−−−−−−−−−−−−+
+−−−−−−−−−−−+
flagArray−−−>| (array) |
+−−−−−−−−−−−+
| length: 0 |
+−−−−−−−−−−−+
Then after the first init call, you have:
+−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−−−−−>| (object) |
| +−−−−−−−−−−−−−−−−−−−−−−−+
| | init: (function) |
| | draw: (function) |
| | country: "ivorycoast" |
| +−−−−−−−−−−−−−−−−−−−−−−−+
|
+−−−−−−−−−−−+ |
flagArray−−−>| (array) | |
+−−−−−−−−−−−+ |
| length: 1 | +−−−−−−−−−−−−−−−+ |
| 0 |−−−>| (object) | |
+−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+ |
| [[Prototype]] |−−+
+−−−−−−−−−−−−−−−+
You've created a new object with flag as its prototype, and set flag's country to "ivorycoast".
After the second call to init, you have:
+−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−+−−−−>| (object) |
| | +−−−−−−−−−−−−−−−−−−−−−−−+
| | | init: (function) |
| | | draw: (function) |
| | | country: "sweden" |
| | +−−−−−−−−−−−−−−−−−−−−−−−+
| |
+−−−−−−−−−−−+ | |
flagArray−−−>| (array) | | |
+−−−−−−−−−−−+ | |
| length: 2 | +−−−−−−−−−−−−−−−+ | |
| 0 |−−−>| (object) | | |
| 1 |−+ +−−−−−−−−−−−−−−−+ | |
+−−−−−−−−−−−+ | | [[Prototype]] |−−+ |
| +−−−−−−−−−−−−−−−+ |
| |
| +−−−−−−−−−−−−−−−+ |
+−−>| (object) | |
+−−−−−−−−−−−−−−−+ |
| [[Prototype]] |−−−+
+−−−−−−−−−−−−−−−+
...and so on, until flagArray is filled with five separate objects, all using flag as their prototype, and flag has "norway" for its country property.
Here's that more advanced way to add country that I mentioned. If I were writing production code, I wouldn't use it for this (too verbose), but just for completeness: There's a second argument you can give Object.create that can have a list of properties to add to the new object. You specify the properties by giving their name and an object the specifies the various aspects of the property, like whether it's enumerable, whether it's read-only or writable, its initial value (or a getter and/or setter), etc. The writable, enumerable, and configurable features of a property are all defaulted false when you do this, but they default true when you create a property by just assigning to it. So to do the same thing we're doing with newFlag.country = country, we have to specify all those things and say true to make the property the same.
So, this does the same thing the version earlier using newFlag did, just without the temporary variable:
init: function(country) {
if (country) {
flagArray.push(Object.create(flag, {
country: {
enumerable: true,
configurable: true,
writable: true,
value: country
}
}));
}
},
Using Object.create(flag) you set the flag as prototype of each element in the array. The prototype is shared between all objects, so this.country gets updated on the prototype object in each init call and inherited by every element in the array.
To create a separate object you should try Object.assign, for example:
flagArray.push(Object.assign(Object.create(flag), { country : country }));
https://jsfiddle.net/p58189f1/

Why does changing object in copied array also affects the object from original one?

In this code, I copied obj using spread operator into copiedObj. Then modified the checked property value to false. However when I console.log(obj.checked[0]), it returns false not true. And it seems like the checked value in copiedObj also changed original property value. Why is that?
const obj = [{id: 1, checked: true}];
const copiedObj = [...obj];
copiedObj.checked[0] = false;
console.log(obj.checked[0]);
obj and copiedObj are arrays, not plain objects. Changing the state of copiedObj (by adding a checked property to it) does not change the state of obj because they're separate arrays.
But, both of the arrays contain a reference to the same object (the one with cheecked on it). So if you did:
checkedObj[0].checked = true;
that would change the state of that one object, which you would see whether you looked up that object on obj[0] or checkedObj[0].
If you want to make a deep copy so that you have separate arrays and separate objects, see this question's answers.
Since I'm 99% sure you meant checkedObj[0].checked = true, I'll explain what's happening in this code:
// Creates an array containing an object
const obj = [{id: 1, checked: true}];
// Creates a new array that contains the *same* object (NOT a *copy* of it)
const copiedObj = [...obj];
// Sets `checked` on that one object that is in both arrays
copiedObj[0].checked = false;
// Looks at the `checked` property on that one object that is in both arrays
console.log(obj[0].checked);
Step by step:
After
// Creates an array containing an object
const obj = [{id: 1, checked: true}];
in memory you have something like
+−−−−−−−−−−−−−+
obj:Ref44329−−−−−>| (array) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+
| 0: Ref82445 |−−−>| (object) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−+
| id: 1 |
| checked: true |
+−−−−−−−−−−−−−−−+
Then when you do
// Creates a new array that contains the *same* object (NOT a *copy* of it)
const copiedObj = [...obj];
you have something like:
+−−−−−−−−−−−−−+
obj:Ref44329−−−−−−−−−−>| (array) |
+−−−−−−−−−−−−−+
| 0: Ref82445 |−−+
+−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−−−+
+−−>| (object) |
+−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+
checkedObj:Ref12987−−−>| (array) | | | id: 1 |
+−−−−−−−−−−−−−+ | | checked: true |
| 0: Ref82445 |−−+ +−−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−+
Then when you do
// Sets `checked` on that one object that is in both arrays
copiedObj[0].checked = false;
it changes the object that both arrays point to
+−−−−−−−−−−−−−+
obj:Ref44329−−−−−−−−−−>| (array) |
+−−−−−−−−−−−−−+
| 0: Ref82445 |−−+
+−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−−−+
+−−>| (object) |
+−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−+
checkedObj:Ref12987−−−>| (array) | | | id: 1 |
+−−−−−−−−−−−−−+ | | checked: false |
| 0: Ref82445 |−−+ +−−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−+
...so looking it up will give you false regardless of which array you look at it through.
obj is an array of objects, when you do:
const copiedObj = [...obj];
You make a new array, but inside that array you will still reference the object from obj
This happens because you have two levels: array -> object, but the spread operator only applies to the first level (the array)
this is because when you created the copiedObj by the spread operator, you are still referencing the object which you initially created in memory.
In order to fix this so that you would get the expected behavior is to create another shallow copy of all the properties inside the object also.
p.s. I modified copiedObj[0].checked = false; console.log(obj[0].checked) because I think you were trying to select the first element in the array you created.
const obj = [{id: 1, checked: true}];
const copiedObj = [Object.assign({}, obj[0])];
copiedObj[0].checked = false;
console.log(obj[0].checked)
//returns true

Array.fill gives same object repeated. why? [duplicate]

This question already has answers here:
Array.prototype.fill() with object passes reference and not new instance
(7 answers)
Closed 3 years ago.
var arr = new Array(4).fill({});
arr[2].status = true;
console.log(arr[0].status);
why is array fill filling same object in all indexes?
.fill will insert the same exact object (same instance) on each segment of the array.
That's why doing .fill(Math.random) will return an array filled with always the same number.
You can do this to obtain what you want:
new Array(4).fill().map(() => ({}));
To explain what's happening under the hood in your question's code, let's convert this to ES5 code:
var arr = new Array(4); // new Array(4)
var obj = new Object(); // *1: with this object instance
for(i = 0; i < 4; i++) { // .fill( *1 )
arr[i] = obj;
}
As you can see, we are assigning the same object instance (obj) to each cell of the array instance.
This is the same principle of why:
const obj = {};
const a = obj;
const b = obj;
a.foo = true;
Makes a.foo === b.foo resolve to true.
fill repeats the value you pass it. The value in this case is an object reference. Every copy of that reference refers to the same object, so what you're getting is:
+−−−−−−−−−+
arr−−−>| (array) |
+−−−−−−−−−+ +−−−−−−−−−−−−−−+
| 0 |−−+−+−+−>| (object) |
| 1 |−/ / / +−−−−−−−−−−−−−−+
| 2 |−−/ / | status: true |
| 3 |−−−/ +−−−−−−−−−−−−−−+
+−−−−−−−−−+
If you want separate objects, you'll need to create multiple objects. The simplest way is, of course:
var arr = [{}, {}, {}, {}];
Example:
var arr = [{}, {}, {}, {}];
arr[2].status = true;
console.log("arr[0].status", arr[0].status);
console.log("arr[2].status", arr[2].status);
If you want to do it with a variable length:
Since you're using Array.fill, I'm assuming you're using ES2015 (aka "ES6") features (but see below for an ES5-compatible solution without polyfills). You can do that via Array.from with a callback:
const arr = Array.from({length:4}, () => ({}));
arr[2].status = true;
console.log("arr[0].status", arr[0].status);
console.log("arr[2].status", arr[2].status);
That gives you:
+−−−−−−−−−+
arr−−−>| (array) |
+−−−−−−−−−+ +−−−−−−−−−−−−−−+
| 0 |−−−−−−−−>| (object) |
| 1 |−−−−−+ +−−−−−−−−−−−−−−+
| 2 |−−−+ |
| 3 |−+ | | +−−−−−−−−−−−−−−+
+−−−−−−−−−+ | | +−−>| (object) |
| | +−−−−−−−−−−−−−−+
| |
| | +−−−−−−−−−−−−−−+
| +−−−−>| (object) |
| +−−−−−−−−−−−−−−+
| | status: true |
| +−−−−−−−−−−−−−−+
|
| +−−−−−−−−−−−−−−+
+−−−−−−>| (object) |
+−−−−−−−−−−−−−−+
(You can do that with an Array.from polyfill on ES5 if you like, just use function() { return {}; } instead of () => ({}).)
In ES5, if you need a variable length, the simplest thing is probably just a loop:
var arr = [];
for (var i = 0; i < 4; ++i) {
arr[i] = {};
}
arr[2].status = true;
console.log("arr[0].status", arr[0].status);
console.log("arr[2].status", arr[2].status);
You could put that in a helper function, of course.

Objects contain same values but shouldn't

I try to achieve an object inheritance, where each object shares the same functionality but contains different values/containers. Doing so I discovered a strange behavior; each individual object works like a charm but they all share the same options. Do clarify what I mean, I added a sample code.
What I want to do is read the data-value from each element and bind it to the options. Those are required inside some functions to work correctly!
<div class="first" data-value="first div"></div>
<div class="second" data-value="second div"></div>
<script>
var original = {
options: {
value: null,
factor: 13
},
init: function (container) {
this.options.value = container.getAttribute('data-value');
}
};
var variation = Object.create(original);
variation.options.factor = 37;
var firstDiv = Object.create(variation);
var secondDiv = Object.create(variation);
firstDiv.init(document.getElementsByTagName('div')[0]);
secondDiv.init(document.getElementsByTagName('div')[1]);
alert('firstDiv.options === secondDiv.options: ' + (firstDiv.options === secondDiv.options)); // but should be false!
</script>
Please note, that this just shows a little part of the actual objects. All the other parts are, in my opinion, irrelevant.
I hope it's clear what the problem is. Also that I use Object.create() on purpose.
objects contain same values but shouldn't
They should, because you haven't changed the options property on variation, so it still points at the same object that the original.options points to.
When you do this:
var original = {
options: {
value: null,
factor: 13
},
init: function (container) {
this.options.value = container.getAttribute('data-value');
}
};
Here's what you get in memory (some details omitted):
+−−−−−−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−+ +−−−>| (Object.prototype) |<−+
original−−−>| (object) | | +−−−−−−−−−−−−−−−−−−−−+ |
+−−−−−−−−−−−−+ | |
| __proto__ |−−−+ +−−−−−−−−−−−−−+ |
| options |−−−−−−−>| (object) | |
| init |−−−+ +−−−−−−−−−−−−−+ |
+−−−−−−−−−−−−+ | | __proto__ |−−−−−−−−−+
| | value: null |
| | factor: 13 |
| +−−−−−−−−−−−−−+
|
| +−−−−−−−−−−−−+
+−−−−| (function) |
+−−−−−−−−−−−−+
...where __proto__ is a pseudo-property showing what the object's prototype is (specifically, the value of what the spec calls the object's internal [[Proto]] property). (On browsers, in ES2015, there is actually a __proto__ accessor, but it shouldn't be used.)
Then when you do this:
var variation = Object.create(original);
You have:
+−−−−−−−−−−−−+
variation−−>| (object) |
+−−−−−−−−−−−−+
| __proto__ |−−+
+−−−−−−−−−−−−+ |
|
+−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+
| +−>| (Object.prototype) |<−+
\ +−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−−−−−+ |
original−−−>| (object) | | |
+−−−−−−−−−−−−+ | |
| __proto__ |−−−−−+ +−−−−−−−−−−−−−+ |
| options |−−−−−−−>| (object) | |
| init |−−−+ +−−−−−−−−−−−−−+ |
+−−−−−−−−−−−−+ | | __proto__ |−−−−−−−−−+
| | value: null |
| | factor: 13 |
| +−−−−−−−−−−−−−+
|
| +−−−−−−−−−−−−+
+−−−−| (function) |
+−−−−−−−−−−−−+
You can see how both original and variation are still pointing at the same options object.
If you want separate options objects, you have to create a new object. You could make variation.options use original.options as its prototype:
var variation = Object.create(original);
variation.options = Object.create(original.options);
...but then you'd have to do it again for firstDiv and secondDiv:
var firstDiv = Object.create(variation);
firstDiv.options = Object.create(variation.options);
var secondDiv = Object.create(variation);
secondDiv.options = Object.create(variation.options);
...which suggests we need to do something different.
You could have a function you use to do it:
function createVariation(source) {
var v = Object.create(source);
v.options = Object.create(source.options);
return v;
}
var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);
...but you might look at constructor functions or maker functions, and a fairly typical pattern called the "extend" function (such as jQuery's or Underscore's).
Neither firstDiv nor secondDiv has its own options property. Both inherit options from original. You can test this by observing that all of the expressions below are false:
variation.hasOwnProperty("options") // false
firstDiv.hasOwnProperty("options") // false
secondDiv.hasOwnProperty("options") // false
Your init function does this.options.value = ..., but consider for a moment only the expression this.options. You never create another options object, so when you ask for firstDiv or secondDiv for its options object, that escalates the this.options lookup to the original prototype object.
Thus, when you access the options property of either firstDiv or secondDiv, you are causing the property lookup to escalate to original (the first object in the prototype chain that really has an options property). This means that firstDiv.options and secondDiv.options are the exact same object (namely, original.options). Therefore, it follows that when you set secondDiv.options.value, you are also setting firstDiv.options.value.
The solution here is to ensure that firstDiv and secondDiv explicitly have their own options property. However, you also want that options value to itself inherit from variation.options, and you want variation to have its own options value as well, which inherits from original.options. T.J. Crowder's answer addresses this in detail with his createVariation method:
function createVariation(source) {
var v = Object.create(source);
v.options = Object.create(source.options);
return v;
}
var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);

Unexpected behavior with Javascript array methods

Take the following code
var a = b = [];
a.push('value');
if (a === b) {
console.log('a === b'); // this will execute
}
console.log(a, b); // ["value"] ["value"]
What gives? Why are both a and b modified? Is the var declaration making b a live copy of a? If so, then how come the same cannot apply for regular variable assignment such as:
var a = b = '';
a = 'value';
if (a === b) {
console.log('a === b'); // this will never run
}
console.log(a, b); // value
Of course, using the following declaration solves the problem in the initial example:
var a = [], b = [];
But the behavior in the initial example struck me as odd, especially that it only applies to array operations.
If it helps, I'm using Google Chrome 10.0.648.82 beta
The concept you need to grok is referencing. When you assign a variable to another one which points to an object ( [], new Array, {}, new Object, functions, and so forth ) in ECMAScript, the reference is passed. b will refer to a unless you create a new object and assign it to b.
To reiterate, var a = [], b = [] creates two distinct arrays. var a = b = [] assigns b to [] and then b to a which picks up the same exact object stored in memory. Any mutator methods will mutate that object and any variables assigned to that object will refer to that object.
When you store "an object" (including an array) in a variable, what you're really storing is a reference (or "pointer") to the object, not a copy of the object itself. That is, the object exists somewhere in memory, and what's stored in the variable is a piece of information that tells the interpreter where to find that object.
In contrast, primitives like numbers or booleans are actually stored in the variable.
So in my best ASCII art, when you have the code
var y = [];
...you end up with this:
+−−−−−−−−−−+
| y |
+−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−+
| Ref55467 |−−−−−−−−−−−>| The actual array |
+−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−+
| [] |
+−−−−−−−−−−−−−−−−−−+
(Obviously, the Ref55467 is just an example, and you can never actually see the object reference's underlying value in code.)
...whereas with:
var x = 32;
...you end up with:
+−−−−−−−−−−+
| x |
+−−−−−−−−−−+
| 32 |
+−−−−−−−−−−+
Now let's look at your
var a = b = [];
That looks like this:
+−−−−−−−−−−+
| a |
+−−−−−−−−−−+
| Ref55467 |−−−−−−+
+−−−−−−−−−−+ |
| +−−−−−−−−−−−−−−−−−−+
+−−−−>| The actual array |
| +−−−−−−−−−−−−−−−−−−+
+−−−−−−−−−−+ | | [] |
| b | | +−−−−−−−−−−−−−−−−−−+
+−−−−−−−−−−+ |
| Ref55467 |−−−−−−+
+−−−−−−−−−−+
Both a and b reference (point to) the same array in memory, and so operations that change the array, change the array, regardless of which reference you invoke them on.
In contrast with:
var a = b = 32;
....which looks like:
+−−−−−−−−−−+
| a |
+−−−−−−−−−−+
| 32 |
+−−−−−−−−−−+
+−−−−−−−−−−+
| b |
+−−−−−−−−−−+
| 32 |
+−−−−−−−−−−+
The number 32 is actually stored in each place, rather than being in a central place both variables point to.
This is also true when you pass values into functions. With an object reference, the value you're passing into the function is an object reference. You're not actually passing the object into the function, just a reference to it. And that means if the function uses the reference to operate on the object, you see the result in the calling code. Example:
function foo(theArray) {
theArray[0] = 1;
}
var a = ["one", "two", "three"];
foo(a);
alert(a[0]); // alerts "1"
When we called foo, we passed in a reference to the array object, and so when foo used that to modify the array, we see the result.
It's not a live copy, it's an object referencing to the same instance.
When you run [] you're doing new Array(), so, you're assigning the new Array instance to a and b.
Good luck!

Categories

Resources