Scoped variable is creating a reference instead of new instance - javascript

Experiencing a weird Javascript bug it'd be great to get some insight to. I've managed to debug it to know what the issue is, I just haven't been able to figure out the why yet and a non-hackey way to resolve it.
I'm trying to create a new instance of an array as a variable inside a function, so I can manipulate it without affecting the main array. Instead I'm getting some kind of reference, so doing operations on my new variable ids also affects the array that ids equals.
I've simplified my code to demonstrate the issue more clearly:
var defence = {
selectedIDs: new Array(),
toggleTerm: function(term, id) {
// add to the main array
this.selectedIDs.push(5);
// adds to this.selectedIDs
this.showTweet();
},
showTweet: function() {
// copy the array as a new variable in the scope of the function
var ids = this.selectedIDs;
if (ids.length == 1) {
ids.push(10); // this pushes to 'ids' AND 'this.selectedIDs'
}
console.log(this.selectedIDs); // outputs [5, 10]
}
}
Also in JSFiddle
It's always been my understand that var ids = this.selectedIDs would effectively copy the contents of this.selectedIDs into that new instance - however this doesn't seem to be the case.
Any ideas on why this is happening, and the best way to get around it (beyond manually recreating it through something like a for loop)?

It's always been my understand that var ids = this.selectedIDs would
effectively copy the contents
OK, but your understanding is wrong. It's an assignment.
Consider this instead:
var ids = this.selectedIDs.slice()

It's always been my understand that var ids = this.selectedIDs would
effectively copy the contents of this.selectedIDs into that new
instance - however this doesn't seem to be the case.
I don’t know how to describe this except “no, that doesn’t happen at all”. JavaScript isn’t C++; when you assign something to a variable, it is never copied. You always get a reference, except in the case of primitives, which are immutable, making the point moot. (Heh.)
You could use concat, but if this isn’t the actual situation, there might be something more appropriate.
showTweet: function() {
var ids = this.selectedIDs;
if (ids.length === 1) {
ids = ids.concat(10); // Array.prototype.concat returns a new array
}
console.log(this.selectedIDs); // outputs [5]
}

Related

Immutables and collections in JavaScript

I'm trying to get my head around how to use Immutables in JavaScript/TypeScript without taking all day about it. I'm not quite ready to take the dive into Immutable.js, because it seems to leave you high and dry as far as type safety.
So let's take an example where I have an Array where the elements are all of Type MyType. In my Class, I have a method that searches the Array and returns a copy of a matching element so we don't edit the original. Say now that at a later time, I need to look and see if the object is in the Array, but what I have is the copy, not the original.
What is the standard method of handling this? Any method I can think of to determine whether I already have this item is going to take some form of looping through the collection and visiting each element and then doing a clunky equality match, whether that's turning both of them to strings or using a third-party library.
I'd like to use Immutables, but I keep running into situations like this that make them look pretty unattractive. What am I missing?
I suspect that my solution is not "...the standard method of handling this." However, I think it at least is a way of doing what I think you're asking.
You write that you have a method that "...returns a copy of a matching element so we don't edit the original". Could you change that method so that it instead returns both the original and a copy?
As an example, the strategy below involves retrieving both an original element from the array (which can later be used to search by reference) as well as a clone (which can be manipulated as needed without affecting the original). There is still the cost of cloning the original during retrieval, but at least you don't have to do such conversions for every element in the array when you later search the array. Moreover, it even allows you to differentiate between array elements that are identical-by-value, something that would be impossible if you only originally retrieved a copy of an element. The code below demonstrates this by making every array element identical-by-value (but, by definition of what objects are, different-by-reference).
I don't know if this violates other immutability best practices by, e.g., keeping copies of references to elements (which, I suppose, leaves the code open to future violations of immutability even if they are not currently being violated...though you could deep-freeze the original to prevent future mutations). However it at least allows you to keep everything technically immutable while still being able to search by reference. Thus you can mutate your clone as much as you want but still always hold onto an associated copy-by-reference of the original.
const retrieveDerivative = (array, elmtNum) => {
const orig = array[elmtNum];
const clone = JSON.parse(JSON.stringify(orig));
return {orig, clone};
};
const getIndexOfElmt = (array, derivativeOfElement) => {
return array.indexOf(derivativeOfElement.orig);
};
const obj1 = {a: {b: 1}}; // Object #s are irrelevant.
const obj3 = {a: {b: 1}}; // Note that all objects are identical
const obj5 = {a: {b: 1}}; // by value and thus can only be
const obj8 = {a: {b: 1}}; // differentiated by reference.
const myArr = [obj3, obj5, obj1, obj8];
const derivedFromSomeElmt = retrieveDerivative(myArr, 2);
const indexOfSomeElmt = getIndexOfElmt(myArr, derivedFromSomeElmt);
console.log(indexOfSomeElmt);
The situation you've described is one where a mutable datastructure has obvious advantages, but if you otherwise benefit from using immutables there are better approaches.
While keeping it immutable means that your new updated object is completely new, that cuts both ways: you may have a new object, but you also still have access to the original object! You can do a lot of neat things with this, e.g. chain your objects so you have an undo-history, and can go back in time to roll back changes.
So don't use some hacky looking-up-the-properties in the array. The problem with your example is because you're building a new object at the wrong time: don't have a function return a copy of the object. Have the function return the original object, and call your update using the original object as an index.
let myThings = [new MyType(), new MyType(), new MyType()];
// We update by taking the thing, and replacing with a new one.
// I'll keep the array immutable too
function replaceThing(oldThing, newThing) {
const oldIndex = myThings.indexOf(oldThing);
myThings = myThings.slice();
myThings[oldIndex] = newThing;
return myThings;
}
// then when I want to update it
// Keep immutable by spreading
const redThing = myThings.find(({ red }) => red);
if (redThing) {
// In this example, there is a 'clone' method
replaceThing(redThing, Object.assign(redThing.clone(), {
newProperty: 'a new value in my immutable!',
});
}
All that said, classes make this a whole lot more complex too. It's much easier to keep simple objects immutable, since you could simple spread the old object into the new one, e.g. { ...redThing, newProperty: 'a new value' }. Once you get a higher than 1-height object, you may find immutable.js far more useful, since you can mergeDeep.

get dynamic property defined in prototype during JSON.stringify

I've defined an enumerable property in the prototype object and would like it to appear when I convert a prototyped object to JSON.
My first idea was to set it in toJSON but because I don't really want to keep it in the object afterwards I'll have to more or less clone the whole object in the function and set the necessary property.
Redefining the property in the target object and just proxying with the context of the current object doesn't seem to be an option as well, since I can't really use apply or call when getting dynamic properties.
Working solutions I could come up with so far seem to require quite an amount of code and aren't flexible and concise enough, so I'm wondering if there are any best practices of solving this task.
Here is an example which could seem a bit synthetic but still, I believe, conveys the idea:
function ProjectFolder() {
this.files = [];
Object.defineProperty(this, 'size', {enumerable: true, get: function() {
return this.files.length;
}});
}
function GithubProjectFolder() {
this.files = ['.gitignore', 'README.md'];
}
GithubProjectFolder.prototype = new ProjectFolder();
var project1 = new ProjectFolder();
JSON.stringify(project1);
// output: {"files":[],"size":0}
// size is present
var project = new GithubProjectFolder();
JSON.stringify(project);
// output: {"files":[".gitignore","README.md"]}
// size is absent
I'll have to more or less clone the whole object in the function and set the necessary property.
Yes, and there's nothing wrong with that. That's how .toJSON is supposed to work:
ProjectFolder.prototype.toJSON = function toJSON() {
var obj = {};
for (var p in this) // all enumerable properties, including inherited ones
obj[p] = this[p];
return obj;
};
However, there are two other points I'd like to make:
The size of a folder doesn't really need to be stored separately in the JSON when it already is encoded in the length of the files array. This redundant data seems to be superfluous, and can confuse deserialisation. Unless something requires this property to be present, I'd recommend to simply omit it.
In ProjectFolders, the .size is an own property of each instance - in GithubProjectFolders it is not. This suggest that you're doing inheritance wrong. Better:
function GithubProjectFolder() {
ProjectFolder.call(this);
this.files.puhs('.gitignore', 'README.md');
}
GithubProjectFolder.prototype = Object.create(ProjectFolder.prototype);
If you'd fix that alone, the size will appear in the serialisation of your project.

Javascript Object inside of Object

I'm working with phylogentic trees and I want an object for the tree itself and then an object for each species, 4 species total. I'm trying to have the tree contain the species objects under tree.leaf and then assign an array of attributes to each species but through the tree object, because I'm randomizing the order of the species so I can't depend on species names but I can use leaf placement(Hope that makes sense). I'm having trouble updating the html, a div inside a table though.
Simplified Version:
var tree = new Object();
var speciesA = new Object();
tree.leaf1 = speciesA;
//Not sure if this next line assigns to speciesA or what exactly happens
tree.leaf1.attributes = new Array("Attr1","Attr2",etc);
var count = 1;
for(attr in speciesA.attributes)
{
//There are 4 divs per speices to display attributes
document.getElementById("A"+String(count)).innerhtml = speciesA.attributes[attr];
count++;// used to specify divs ie A1 = attribute1, A2 = attribute2 etc
}
So I guess my main question is will this work/do what I think it does?
If needed I can pastebin my html and full js files.
What you have should work, but it can be written a bit cleaner. I would suggest this:
var tree = {
leaf1: {attributes: ["Attr1", "Attr2"]}
};
var attributes = tree.leaf1.attributes;
for (var i = 0; i < attributes.length; i++) {
document.getElementById("A"+(i+1)).innerHTML = attributes[i];
}
Things I changed:
Used a javascript literal to make the definition a lot more compact
Used {} and [] for defining arrays and objects rather than new Object() and new Array().
Used for (var i = 0; i < xxx.length; i++) syntax to iterate array elements only, not all properties. This is the "safe" way to iterate elements of an array.
Remove the String(count) as it is not needed. Javascript will auto-convert a number to a string when adding to another string.
Cached the value of the attributes array to save having to deep reference it each time.
Removed separate count variable as the for index can be used
To answer one of your other questions, when you do this:
tree.leaf1 = speciesA;
you have assigned a "reference" to speciesA to tree.left1. A reference is like a pointer. It is not a copy. So, the both refer to exactly the same object. Any change you make to speciesA or to tree.leaf1 is make a change to the exact same object.
So, when you then do this:
//Not sure if this next line assigns to speciesA or what exactly happens
tree.leaf1.attributes = new Array("Attr1","Attr2",etc);
you are indeed modifying the speciesA object since speciesA and tree.leaf1 point to the same object.
In javascript, arrays, objects and strings are assigned by reference. That means that when you assign one to a variable, it just points to the original object. A copy is not made. So, change the object via either either one will change the other (since they both point to the same object). Strings are immutable (a string is never actually changed). Things that feel like modifications to a string always just return a new string so this aspect of javascript doesn't affect strings so much. But, it is very important to know that arrays and objects are assigned by reference.

Why is a function within an object constructor altering all objects previously created by that constructor?

I have a function within an object constructor that is altering all objects created by that constructor. I'm not sure why. Could someone please take a look at my code and tell me what I'm missing?
A quick description of what is going on:
Warning! It might be easier to just read through the code than try to make sense of my description
I have created two new Arrays. The first one called foos, which will be an array of foo objects each one containing an array of bar objects. The second is called bars which is an array of all bar objects that are available to be added to the foos.foo.bars arrays.
When a new foo object is created using the foo object constructor it is given two arguments(aBars,bBars). aBars is an array of all bar objects to be included in the foo object. bBars is an array of all included bar objects that are considered 'special' in some way. Within the constructor there is a function that runs through every object in the bars array and if it's name value matches that of a string in the aBars argument then it is added to foo.bars array. If it's name value matches a string in the bBars argument it then has it's property bBar set to true, otherwise it's set to false.
The issue I'm having is that on the second foo object constructor when a bar object has bBar set to true or false it also changes that value in that object in all other foo.bars objects.
I realize that this is probably hard to follow. Sorry about that, it's the end of the day.
Found my own answer!
I just realized what the issue is. foos[0].bars[4] and foos[1].bars[3] are not separate objects, they are simply two different variables pointing to the same object. So when one is changed the change shows up on both. Wow, I can't believe I just spent so much time working on this when the answer was a basic fact about how javascript works that I learned back when I first started.
Ok, the new question:
How can I change this code to create duplicates of the objects instead of just pointing at the originals? This is not something I've ever had to do before.
Thanks
jsfiddle
JS:
var foos = new Array();
var bars = new Array();
function foo(aBars,bBars) {
var $this = this;
this.aBars = aBars;
this.bars = new Array();
bars.forEach(function(e,i) {
if ($this.aBars.lastIndexOf(e.barName) > -1) {
$this.bars.push(e);
if (bBars.lastIndexOf(e.barName) > -1) {
$this.bars[$this.bars.length-1].bBar = true;
} else {
$this.bars[$this.bars.length-1].bBar = false;
}
}
});
}
function bar(name) {
this.barName = name;
}
bars.push(new bar('l'));
bars.push(new bar('m'));
bars.push(new bar('n'));
bars.push(new bar('o'));
bars.push(new bar('p'));
foos.push(new foo(['l','m','n','o','p'],['n','p']));
foos.push(new foo(['l','n','o'],['n','o']));
console.log(foos);
The only way to achieve that would be to replace this line
$this.bars.push(e);
in your 'foo'-constructor with this one:
$this.bars.push(new bar(e.barName));
Cloning objects in javascript is only possible by copying their properties.

Javascript array becomes an object structure

I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify

Categories