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

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/

Related

How to make a linked list from an array in Javascript

Here's the basic code
function ListNode(x) {
this.value = x;
this.next = null;
}
function linkedList(arr){
let list = new ListNode(arr[0]);
let selectedNode = list;
for(let i = 1; i < arr.length; i++){
selectedNode.next = new ListNode(arr[i]);
selectedNode = selectedNode.next
}
return list
}
l = [3, 1, 2, 3, 4, 5];
console.log(linkedList(l));
For some reason, it just works. I don't get how the 'list' variable is changing/updating in linkedList(arr) function. I see selectedNode = list, but list never changes. list initializes with the constructor new ListNode(arr[0]) but after that, the only variable that's changing is selectedNode. There isn't even code for list.next to change to something.
So how is it that return list returns the full linked list?
function linkedList(arr){
return arr.reduceRight((next, value) => ({value, next}), null);
}
l = [3, 1, 2, 3, 4, 5];
console.log(linkedList(l));
you don't need the class ListNode for such a simple object.
When you do this let selectedNode = list; two variables are pointing to the same value in memory, in this case, list and selectedNode. So, the chain of modifications starts with list and downstream adding new linked nodes.
The author has initialized the selectedNode with list as a starting point (root node) of the linked list.
This how looks like the memory with this let selectedNode = list;
+-----------------+------------------+
| list | selectedNode |
+-----------------+------------------+
| | | |
| {value: 3, | | |
| next: null } <---------+ |
| | |
+-----------------+------------------+
So, every iteration will modify the same list object because the next node is bound/linked with the previous nodes:
selectedNode.next = new ListNode(arr[i]); i = 1
+-------------------+------------------+
| list | selectedNode |
+-------------------+------------------+
| | | |
| { | | |
| value: 3, | Points to | |
| next: { | next | |
| value: 1 <------------+ |
| next: null | |
| } | |
| } | |
| | |
+-------------------+------------------+
selectedNode.next = new ListNode(arr[i]); i = 2
+-----------------------+------------------+
| list | selectedNode |
+-----------------------+------------------+
| | | |
| { | | |
| value: 3, | | |
| next: { | | |
| value: 1 | Points to | |
| next: { | next | |
| value: 2, <------------+ |
| next: null | |
| } | |
| } | |
| } | |
| | |
+-----------------------+------------------+
And so on.
When you assign an object type to a variable, the variable's value isn't the actual object but rather a reference to it. Therefore, when you do selectedNode = list both variables reference the same object and any changes to this object, using any of the references to it, will be visible from any of its references.
In short: the list variable doesn't change but the object it's referencing does.

javascript - prototype chain

Please consider such a code:
class a {
constructor() {
this.test = "test from a";
this.testA = "X";
}
mimi() {
console.log('aaaaaaaa', this.test, this.testA)
}
}
class b extends a {
constructor() {
super();
this.test = "test from b"
}
mimi() {
console.log('bbbbbbbb', this.test, this.testA)
}
}
class c extends b {
constructor() {
super();
this.test = "test from c"
}
mimi() {
console.log('cccccccc', this.test, this.testA)
}
meme() {
var test = kalreg.__proto__.__proto__;
test.mimi();
var test2 = kalreg.__proto__.__proto__.__proto__;
test2.mimi();
}
}
var kalreg = new c(); kalreg.mimi(); kalreg.meme();
The output I get is:
cccccccc test from c X
bbbbbbbb undefined undefined
aaaaaaaa undefined undefined
My object logic makes me use "a" as the most generic class, "b" that is a child to it, and "c" that is the child to "b". I want "c" to have all methods and properties of "b" and "a" BUT part of functionalities of "a" is overwritten by "b" so the only way "c" may have access to overwritten functionalities is to use prototype chain.
Unfortunately, my way doesn't work so the questions are:
In meme() function, how to avoid kalreg.proto - I've been told that such way of accessing prototype is bad and dangerous for the code.
In my opinion, there should be no "undefined" in output, however, there is.
The expected output is:
cccccccc test from c X
bbbbbbbb test from b X
aaaaaaaa test from a X
How to achieve it?
Thank you!
in meme() function, how to avoid kalreg.proto
Use super for the use cases where you really do need to access a property from the superclass (but note that that won't help with test and testA, because those aren't on the superclass or its prototype object, they're on the instance created with new c; more on that below).
Separately: Avoid __proto__. In those rare cases where you need to access an object's prototype, use Object.getPrototypeOf.
in my opinion there should be no "undefined" in output however there is. expected output is:
cccccccc test from c X
bbbbbbbb test from b X
aaaaaaaa test from a X
The output is correct, because the prototype objects on which you're calling mimi don't have a test or testA property. Only the object created with new c has those properties. And there is only one such object, so test will be "test from c" no matter which mimi you call, and testA will always be "X".
In a comment you've asked how there can only be one test when each constructor has this.test = ... in it. Let's look at what's in memory once you've done var kalreg = new c();:
+−−−−−−−−−−−−+
a−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−>| (function) | +−−−>(Function.prototype)
| +−−−−−−−−−−−−+ |
| | __proto__ |−−+ +−−−−−−−−−−−−−+
| | prototype |−−−−−−−−−−+−>| (object) |
| +−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | | __proto__ |−−>...
+−−−−−−−−−−−−+ | | | constructor |−−>a
b−−−−−−−−−−−−−−−−−−−+−>| (function) | | | | mimi |−−>...
| +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+
| | __proto__ |−+ +−−−−−−−−−−−−−+ |
| | prototype |−−−−−−−−+−>| (object) | |
| +−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ |
| | | __proto__ |−−+
+−−−−−−−−−−−−+ | | | constructor |−−>b
c−−−>| (function) | | | | mimi |−−>...
+−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+
| __proto__ |−+ +−−−−−−−−−−−−−+ |
| prototype |−−−−−−−+−>| (object) | |
+−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+ |
| | __proto__ |−+
| | constructor |−−>c
+−−−−−−−−−−−−+ | | mimi |−−>...
kalreg−−−>| (object) | | | meme |−−>...
+−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| __proto__ |−−+
| test |
| testA |
+−−−−−−−−−−−−+
As you can see, only the object kalreg points to has test or testA. Why? Because during the call to each constructor, this refers to that object; that's how new and super() work. So since this refers to that object, each constructor does its this.test = ... line, and since the one in c is the last one to run, it "wins."
You can access superclass versions of the mimi method, but since they all show the test property, they'll all show "test from c". To show different things, they'd have to have different properties to show. Additionally, with super you can only go one level up, so if you wanted to go two levels up, you'd either use a.prototype.mimi (or this.__proto__.__proto__.mimi explicitly, or put some facility in b to call a's mimi.
Example with different properties for each level, and with b providing superMimi so c can use a's mimi:
class a {
constructor() {
this.testA = "test from a (this.testA)";
}
mimi() {
console.log('aaaaaaaa', this.testA);
}
}
class b extends a {
constructor() {
super();
this.testB = "test from b (this.testB)";
}
mimi() {
console.log('bbbbbbbb', this.testB)
}
superMimi() {
return super.mimi();
}
}
class c extends b {
constructor() {
super();
this.testC = "test from c (this.testC)";
}
mimi() {
console.log('cccccccc', this.testC);
}
meme() {
super.mimi(); // Uses b's version
super.superMimi(); // Uses a's version
this.__proto__.__proto__.__proto__.mimi.call(this); // Also uses a's version
var p = Object.getPrototypeOf(this);
var p2 = Object.getPrototypeOf(p);
var p3 = Object.getPrototypeOf(p2);
p3.mimi.call(this); // Also uses a's version
}
}
var kalreg = new c();
kalreg.mimi();
kalreg.meme();

Array filter changes the main array

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]"});
}
});

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);

Javascript object references

I'm trying to animate Objects in a Canvas with a function, so I wrote this object - which I'm trying to animate:
var chartElements = {
'bottomLine': {
'width': 0,
setWidth: function (x) {
this.width = x;
},
getWidth: function () {
return this.width;
},
},
};
...and created it with: var cE = Object.create(chartElements);
So my, first thought was, to just change the var with cE.chartElements.bottomLine.width = 10, but this didn't work so I wrote a function where I can reference to the getters and setters:
function changeIt(val, getter, setter) {
console.log(getter());
setter(val);
console.log(getter());
}
And now it's getting very, very weird because first, the getter-Function returnes an undefined, but after it's set, it always returnes the new variable but without changing the actual property of the cE-Object. So it seems like I'm creating a new Object somewhere.
So, whats wrong, why can't I change the actual property of cE?
There are two problems here. The first problem is that you're trying to use a property on your object (chartElements) that doesn't exist. Instead of
cE.chartElements.bottomLine.width = 10
it would be
cE.bottomLine.width = 10;
or, of course, using the setter you created:
cE.bottomLine.setWidth(10);
but, that brings us to the second problem:
By doing
var cE = Object.create(chartElements);
...you're creating an object, referred to by cE, that uses chartElements as its underlying prototype. In memory, you get something that looks like this (love me some ASCII-art):
+-------------+
chartElements--------+-->| (object) |
| +-------------+ +----------+
| | bottomLine |----->| (object) |
| +-------------+ +----------+
| | width: 0 |
+-----------+ | | setWidth |---->(function)
cE--->| (object) | | | getWidth |---->(function)
+-----------+ | +----------+
| __proto__ |--+
+-----------+
That means that cE.bottomLine refers to the prototype's bottomLine property. If you change it, you'll be changing the bottomLine object of the prototype, which would mean if you have other objects you've created via Object.create(chartElements), if they use bottomLine, they'll see the change (because they're all using the same bottomLine).
Instead, you might have a prototype for bottomLine:
var bottomLinePrototype = {
width: 0,
setWidth: function (x) {
this.width = x;
},
getWidth: function () {
return this.width;
}
};
and a constructor function or builder function for your chart element creation:
Here's a constructor function:
// Constructor function
function ChartElement() {
this.bottomLine = Object.create(bottomLinePrototype);
}
// use it via `new`
var cE = new ChartElement();
Here's a builder function:
// Builder function
function buildChartElement() {
return {
bottomLine: Object.create(bottomLinePrototype)
};
}
// Use it *without* `new`
var cE = buildChartElement();
Now, your objects have their own bottomLine object:
+-------------+
cE--->| (object) |
+-------------+ +-----------+
| bottomLine |----->| (object) |
+-------------+ +-----------+ +----------+
| __proto__ |---->| (object) |
+-----------+ +----------+
| width: 0 |
| setWidth |---->(function)
| getWidth |---->(function)
+----------+
Initially that looks pretty similar, but the difference is that since the object has its own bottomLine object, you can safely use:
cE.bottomLine.width = 10;
or
cE.bottomLine.setWidth(10);
...to set that instance's bottomLine's width, which will shadow the one on its prototype, giving us:
+-------------+
cE--->| (object) |
+-------------+ +-----------+
| bottomLine |----->| (object) |
+-------------+ +-----------+ +----------+
| __proto__ |---->| (object) |
| width: 10 | +----------+
+-----------+ | width: 0 |
| setWidth |---->(function)
| getWidth |---->(function)
+----------+
This works because prototype properties work differently when you're doing a set vs. get operation:
When you're getting, if the object itself doesn't have the property, the JavaScript engine looks to the object's prototype to see if it has the property. If so, it uses the value of the prototype's property. If not, it looks to the prototype's prototype, and so on.
When you're setting, though, it sets the value on the object itself, creating a new property on the object if necessary or upating the value of the property if it's already there. The prototype's property is unaffected.

Categories

Resources