Prototyping a sub-classes to already defined classes in Javascript - javascript

Sorry if it is trivial but I am a very beginner. In Javascript what is the best way to extend an already defined object class with propreties which are classes themselves?
For example suppose I have a class Sprite with some built in properties and methods. I want to have an extra property Sprite.position where the position part is a class with (say) two properties position.x and position.y. And I want it applied all the instances of the class Sprite (past and future), not to one single instance.
If position would be a simple input data (number or string) then I could just write Sprite.prototype.position='there'. Also if position was an array it would have been easy: just write Sprite.prototype.position="there", or even an array of array would work like this... But what if position is a class? Usually to build "classes" one would use the object constructor:
function position(x,y) {
this.x = x;
this.y = y;
}
but then I would need to instantiate the position objects with var myPosition = new position(0,0); but I want the position object to exist every time I instantiate a new Sprite object. If I write Sprite.prototype.position=new position(0,0);than there is a problem: if I define mySprite1.position.x=2 the value 2 is also given to the property position.x of every istance of the class Sprite. This doesn't happen with the command Sprite.prototype.position='there', in this case every istance will have and keep his own separate values. I want every istance with separate independent "position" objects.
Is it possible to do what I want in an easy linear way?

And I want it applied all the instances of the class Sprite (past and future), not to one single instance.
You're quite right that if you were dealing with a primitive (like your "testing" string), you could just add it to Sprite.prototype and it would appear on all Sprite objects without any crosstalk between them.
Also if position was an array it would have been easy: just write Sprite.prototype.position=[0,0]
While it's true that the array would show up on all Sprite objects, they would have the potential for cross-talk, because they'd all share the same array:
var sprite1 = new Sprite();
var sprite2 = new Sprite();
Sprite.prototype.position = [0,0];
sprite1.position[0] = 42;
console.log(sprite2.position[0]); // "42"
If you don't mind them all sharing the same array/object, you can do exactly the same thing with your class:
Sprite.prototype.position = new Position(/*...*/);
Again, they will all share one Position object.
If you want each sprite instance to have its own Position object (or array), independent of the one on other instances, you have only two choices:
Modify the Sprite constructor to assign the object/array to the instances as they're created.
Spin through all Sprite objects ever created and add the object/array.
Obviously you can't do #2 unless you have references to those objects.
It may well be that you don't have to do anything to Sprite.prototype at all, depending on how you want to handle a sprite not having a position. For example:
function doSomethingWithASprite(sprite) {
if (!sprite.position) {
// Doesn't have a position yet, give it one
sprite.position = new Position(/*...relevant args...*/);
}
}
Similarly, any time you want to get a sprite's position:
var x = sprite.position && sprite.position.x;
That will give you undefined if the sprite doesn't have a position yet.
A final option for you: Make position a function:
Sprite.prototype.position = function(x, y) {
// Make sure we have a position
if (!this.position) {
this.position = new Position(/*...relevant args...*/);
}
if (typeof x === "undefined") {
// Getter, return the current position
return this.position;
}
else {
// Setter, set the current position
this.position.x = x;
this.position.y = y;
}
};

Related

Eloquent JavaScript: Persistent Group

In the book, Eloquent JavaScript, it asks to make a class PGroup which is similar to a class made in a previous exercise. It basically is like a simplified Set class that has add, delete, and has methods. The specific part I don't understand at all is at the end. It says:
"The constructor shouldn't be part of the class's interface (though
you'll definitely want to use it internally). Instead, there is an
empty instance, PGroup.empty, that can be used as a starting value.
Why do you need only one Pgroup.empty value, rather than having a
function that creates a new, empty map every time?"
This is the given answer to the problem:
class PGroup {
constructor(members) {
this.members = members;
}
add(value) {
if (this.has(value)) return this;
return new PGroup(this.members.concat([value]));
}
delete(value) {
if (!this.has(value)) return this;
return new PGroup(this.members.filter(m => m !== value));
}
has(value) {
return this.members.includes(value);
}
}
PGroup.empty = new PGroup([]);
let a = PGroup.empty.add("a");
let ab = a.add("b");
let b = ab.delete("a");
tldr: What is PGroup.empty?
edit: To clear up confusion, what I meant is that I don't understand the purpose of PGroup.empty or what it even is in relation to the class PGroup. Like, for example, is it a property of the constructor?
PGroup.empty represents an empty set. You can use PGroup.empty as the starting point to create more sets.
What makes this particular implementation of PGroup interesting is that the add and delete methods do not modify the existing PGroup instance that you're operating on. Instead, add and delete return entirely new PGroup instances. This means that every time you add or remove an element from a PGroup that you already have, you create an entirely new PGroup instance rather than modify your existing PGroup instance.
Using this pattern means that given an empty set (in our case PGroup.empty), we can create a whole bunch of other PGroups without ever explicitly using the new keyword. In particular, if we wanted a set of ['a', 'b', 'c'], we could do the following:
let abc = PGroup.empty.add('a').add('b').add('c');
Moreover, because the PGroup.empty instance itself does not change when you call the add method on it, you can use reuse the same PGroup.empty instance however many times you want.
let xyz = PGroup.empty.add('x').add('y').add('z');
let efg = PGroup.empty.add('e').add('f').add('g');
This aspect of immutability allows us to satisfy the following requirement:
The constructor shouldn't be part of the class's interface
Instead, we can use add and delete to create more instances of PGroup.
The technical term of having add and delete create a new instance of a PGroup rather than modify the existing instance of a PGroup is known as immutibility.

Make a variable inherit the methods of an existing prototype?

So let's say i have a tile on a map.
function TileData (image, z) {
this.image = image;
this.z = z;
}
var tile = new TileData (tileimage, 0);
Now i can call tile.image and tile.z to reference the existing data.
I also have an array called map, which i can loop through to draw the map.
var map = [];
What i want to do is attach the methods of the existing tile to the 0th entry in the array so it will be drawn when the function is called.
So i tried doing something like this.
map[0] = tile;
So i can call map[0].image to get the image that represents that tile on the map.
Now i want to change the image of this tile, so i try doing this.
map[0].image = differentTileImage;
However what Javascript actually does is this:
tile.image = differentTileImage;
Which means any time i try to use that tile for another part of the map, it will have a different value.
map[1] = tile;
//map[1].image will return differentTileImage instead of tileimage.
Is there a way for a variable to inherit the properties and values of an existing prototype, rather than merely referencing it?
(Sorry if this is a dupe).
Yes, you can use Object.assign(), that function will create a copy of the Tile.
map[1] = Object.assign({}, tile);
You can find more useful information at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The problem is that objects in JavaScript are always passed by reference. When you change the original, changes all values in the chain, where you use this reference.
In your case tile and map[0] are the same think. If you change tile map[0] is changed because the reference in the memory is the same as tile.
Which means any time i try to use that tile for another part of the map, it will have a different value.
Well not exactly. It means that when you use a single tile for every part of the map, it will have the exactly same value everywhere. And if you change this single tile, the whole map will change.
What you want is to create multiple tile instances:
map[0] = new TileData (tileimage, 0);
map[1] = new TileData (tileimage, 0);
…
(you'd use a loop of course)
They still will all inherit from the same prototype object (TileData.prototype), but are distinct instances and every image can be changed separately.

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.

What is the weird behavior of native objects caused by?

Recently I started learning about object-oriented programming in JavaScript. What I understood, is that when referencing to variables, we in fact reference not to their actual values, but locations in the memory. That's why all those "return this" methods that are supposed to copy instances don't work.
So, example code:
//An example object with a simple property and
//failing "copy" function.
function MyObject()
{
this.myProperty = 123;
this.copy = function() { return this; };
}
var iOne = new MyObject();
var iTwo = iOne.copy();
iTwo.myProperty = 321;
Now "myProperty" property of both iOne and iTwo equals 321, because "copy" method returned a reference, instead of a value. This behavior is expected, and everything is okay.
Now, I tried doing the same with a native object type, Number. Let's create an instance of it, in a more object-oriented programmer-friendly way:
var iOne = new Number(123);
var iTwo = iOne; //Equals "iTwo = iOne.copy()", except there isn't a copy method
iOne = 321;
And now, something terrible happened. iOne equals 321, but iTwo kept its value and is still equal to 123.
I have no idea what is this behavior caused by. Maybe Number is some kind of "special"? Maybe the decimal number associated with it is something more than a property? Or maybe it's just supposed to make life of inexperienced programmers easier? The last option is related to operators. If anyone know something about it, please don't let my way of understanding JavaScript fall apart.
Objects, Arrays and Strings are assigned by reference (not by copy). All other types are effectively copies when assigned (e.g. they make a new variable that has nothing to do with the old one).
Strings are a special case because they are immutable so when you change a string, it always creates a new string so it behaves more like it makes a copy even though the previous assignment was a reference.
Assigning:
iOne = 321;
Is replacing the value of iOne with a simple primitive numeric type so it will have no effect on any other variable.
var iOne = new Number(123);
var iTwo = iOne; //Equals "iTwo = iOne.copy()", except there isn't a copy method
iOne = 321;
You're overwriting the object reference held by the iOne variable, with a distinct primitive number.
The objects are held as references, but they are not pointers that can be directly dereferenced, so you can't replace the data held in that memory location. You can only mutate it (if the object is mutable).
Specifically, the Number object wrapper is not mutable, or at least the primitive value it holds can't be replaced. You can only replace the entire object.
iOne = 321;
This code did what is expected, you assigned 321 to the variable iOne, overwriting what it was referencing to originally.
There's no real difference in behaviour between "native types" and objects in Javascript (except that native types are immutable).
In your second example you're simply changing what variable iOne is pointing to, why should it change what another independent iTwo variable is pointing to?
In the first case instead you have two variables pointing to the same object and if you use one variable to mutate the object and you can observe the change also using the other variable (obvious... it's pointing to the same object).
In Javascript you can imagine that everything is always by reference and never by value (copy). If you want to copy something you need to do it explicitly... for arrays you can use x.slice() to make a shallow copy of x; for objects there's no primitive function for doing the same so you must call the constructor.
A common OOP pattern is to have a member function .clone() that returns a copy so who needs the copy doesn't need to know how to make a copy of every class.
function P2d(x, y) {
this.x = x;
this.y = y;
}
P2d.prototype.clone = function() {
return new P2d(this.x, this.y);
}
Another possibility specific to the protoype model of Javascript and that can be useful in some cases is to create a separate object that will appear like a shallow copy that can be mutated without affecting the original but that is instead referencing the original object when reading:
function fakeCopy(x) {
function f() { }
f.prototype = x;
return new f;
}
p = new P2d(10, 20);
q = fakeCopy(p);
console.log(q.x); // Displays 10
q.y = 30;
console.log(q.y); // Displays 30
console.log(p.y); // Displays 20 -- original not changed
p.x = 99;
console.log(q.x); // Displays 99 (!)
This happens because Javascript objects have a "prototype chain" that is searched when accessing a member for reading. q is created as an empty object that with p as its prototype so when looking for an attribute (for reading) it will go searching inside p if something is not found inside q. When writing however an attribute will be set inside q, not affecting p and from that point on the value present in q will be returned instead of having to go up in the prototype chain.

reinitializing javascript object's properties

In my Javascript drag and drop build app, a variety of buildings can be built. The specific characteristics of these are all saved in one object, like
var buildings = {
house: ['#07DA21',12,12,0,20],
bank: ['#E7DFF2',16,16,0,3],
stadium: ['#000000',12,12,0,1],
townhall: ['#2082A8',20,8,0,1],
etcetera
}
So every building has a number of characteristics, like color, size, look which can be called as buildings[townhall][0] (referring to the color). The object changes as the user changes things. When clicking 'reset' however, the whole object should be reset to its initial settings again to start over, but I have no idea how to do that. For normal objects it is something like.
function building() {}
var building = new building();
delete building;
var building2 = new building();
You can easily delete and remake it, so the properties are reset. But my object is automatically initialized. Is there a way to turn my object into something that can be deleted and newly created, without making it very complicating, or a better way to store this information?
You can keep initial state as a prototype on an object.
var Base = function(){};
Base.prototype={a:1,b:2};
var c = new Base();
Now, you can change value of a or b to whatever you want.
To restore, just use
delete c.a or delete c.b
You will get your initial value back.
Hope this help.
Just use a copy/clone method to restore the original state
var defaults = {
foo: "bar"
};
var building;
function reset(){
building = {};
for (var key in defaults) {
if (defaults.hasOwnProperty(key){
building[key] = defaults[key];
}
}
}
Now you can just call reset() whenever you need to have building reset.

Categories