Javascript Accessor properties confusion - javascript

I am not sure why this code gives me an error. All I want to do is create an object that has an array as a property. I want to achieve this with a setter and getter but for some reason when I do this.array = [] inside the setArray function I get Maximum call stack size exceeded. What am I doing wrong ? What am I missing about Javascript's accessor properties.
var obj = {
set array(size) {
this.array = [];
for (var i = 0; i < size; i++){
this.array[i] = i;
}
}
};
var myObj = Object.create(obj);
myObj.array = 20;

You're assigning to a property with a setter from within the setter, which is why it recurses forever:
var obj = {
set array(size) { // <== The property is called `array`
this.array = []; // <== This calls the setter
for (var i = 0; i < size; i++){
this.array[i] = i; // <== This would fail because there's no getter
}
}
};
You have to store the value elsewhere, for instance here we create a private scope and close over a variable:
var obj = (function() {
var array = [];
return {
set array(size) {
array = []; // No `this` here
for (var i = 0; i < size; i++) {
array[i] = i; // No `this` here
}
},
showArray() {
console.log(array);
}
};
})();
var myObj = Object.create(obj);
myObj.array = 20;
myObj.showArray();
You asked about avoiding the function. You need the function to make the value of the property truly private. But if you don't want to use it for some reason, and you're not that bothered about it being truly private, you can do what Zoltán Tamási did in his answer: Use another property on the same object. His answer uses a _ prefix on the underlying property (e.g., _array), which is a very common convention in JavaScript for "leave this alone, it's 'private'" even though there are no private properties. The fact is that even in languages with truly private properties (like Java, where they're called instance fields), there's usually some form of powerful reflection mechanism you can use to get around it, so...
If you do that, one thing to consider is whether you want that private property included in JSON if you serialize. If not, just implement toJSON as well so you can control which properties are included during serialization:
var obj = {
_array: [],
foo: "a value to include in JSON",
set array(size) {
this._array = []; // Note the _
for (var i = 0; i < size; i++) {
this._array[i] = i;
}
},
showArray() {
console.log(this._array);
},
toJSON() {
return {
// Here we list only the properties we want to have
// included in the JSON
foo: this.foo
};
}
};
var myObj = Object.create(obj);
myObj.array = 20;
myObj.showArray();
console.log(JSON.stringify(myObj));

You are using the setter inside the setter itself when you write this.array = []. Introduce another member called for example _array and fill that in the setter of the array property.
var obj = {
_array: [],
set array(size) {
for (var i = 0; i < size; i++){
this._array[i] = i;
}
}
};
var myObj = Object.create(obj);
myObj.array = 20;

You had mentioned that you wanted to use setters and getters, this snippet just uses obj.array instead of showArray to view the object's array.
var obj = (function() {
var _array = [];
return {
get array(){
return _array;
},
set array(size) {
_array = [];
for (var i = 0; i < size; i++) {
_array[i] = i;
}
}
};
})();
obj = Object.create(obj);
obj.array = 20;
console.log(obj.array);

Related

Javascript foreach not executing for all objects

I currently have an object that adds itself to an array whenever a new one is created. Eventually, I want to remove all of the references in the array so I can add new ones.
I've created an object method (this.removeFromArray()) that looks for itself in the array and splices itself out. removeAll() runs a for loop that makes each object in the array run removeFromArray(), so I expect that when I try to read out the items in the array, I should get nothing.
Instead, depending on the amount of objects created, I get one or two left behind. How can I fix this and have all objects in the array cleared out?
var objArray = [];
function obj(name) {
objArray.push(this);
console.log("Created "+name);
this.name = name;
this.removeFromArray = function() {
objArray.splice(
objArray.findIndex(function(e) {
return e == this;
}),
1
);
}
}
function removeAll() {
for (var i = 0; i <= objArray.length - 1; i++) {
objArray[i].removeFromArray();
}
}
var foo = new obj("foo");
var bar = new obj("bar");
var cat = new obj("cat");
var dog = new obj("dog");
var bird = new obj("bird");
removeAll();
for (var i = 0; i <= objArray.length-1; i++) { //Check the values in the array for leftovers
console.log(objArray[i].name);
}
//Expected nothing in the console but the creation messages, got foo and bar instead
If you want to simply delete all the created object, edit removeAll() function like below:
Note that you have to create a variable for objArray.length, not directly put the objArray.length to for() loop.
function removeAll() {
var len = objArray.length;
for (var i = 0; i <= len - 1; i++) {
objArray.splice(0,1);
}
}
better way to achieve this would be to utilize inheritance through prototype. it is better than creating a function inside the constructor object.
var objArray = [];
function Obj(name) {
this.name = name;
objArray.push(this);
}
Obj.prototype.removeFromArray = function() {
var i = -1,
len = objArray.length,
removed = null;
while (++i < len) {
if (objArray[i] === this) {
removed = objArray.splice(i, 1);
removed = null; //nullify to free memory, though not that necessary
break;
}
}
};
Obj.prototype.removeAll = function() {
var len = objArray.length,
removed = null;
//note that i started from the last item to remove to avoid index out of range error
while (--len >= 0) {
removed = objArray.splice(len, 1);
removed = null; //nullify to free memory, though not that necessary
}
};

js rewrite previous elements in loop

I want to push elements to array in loop but when my method returns a value, it always rewrites every element of array(probably returned value refers to the same object). I'm stuck with this problem for one day and I can't understand where is the problem because I've always tried to create new objects and assign them to 'var' not to 'let' variables. Here is my code:
setSeason(competitions, unions) {
var categories = this.sortCategories(competitions);
var unionsByCategories = new Array();
let k = 0;
for (; k < categories.length; k++) {
unionsByCategories[k] = this.assignCompetitionsToUnions(unions[0], categories[k]);
}
this.setState({categories: unionsByCategories, refreshing: false})
}
and
assignCompetitionsToUnions(unions1, competitions) {
var unions2 = this.alignUnions(unions1);
let tempUnions = [];
for (var i = 0; i < unions2.length; i++) {
var tempUnionsCompetitions = new Array();
var tempSubsCompetitions = new Array();
if (Globals.checkNested(unions2[i], 'union')) {
tempUnionsCompetitions = unions2[i].union;
tempUnionsCompetitions['competitions'] = this.getCompetitionsById(unions2[i].union.id, competitions);
}
if (Globals.checkNested(unions2[i], 'subs')) {
for (var j = 0; j < unions2[i].subs.length; j++) {
if (Globals.checkNested(unions2[i].subs[j], 'union')) {
tempSubsCompetitions[tempSubsCompetitions.length] = {union: unions2[i].subs[j].union};
tempSubsCompetitions[tempSubsCompetitions.length - 1]['union']['competitions'] =
this.getCompetitionsById(unions2[i].subs[j].union.id, competitions)
}
}
}
tempUnions.push({union: tempUnionsCompetitions, subs: tempSubsCompetitions});
}
return tempUnions;
}
Many thanks for any help.
Answer updated by #Knipe request
alignUnions(unions3) {
let newUnions = unions3.subs;
newUnions = [{union: unions3.union}].concat(newUnions);
return newUnions.slice(0, newUnions.length - 1);
}
getCompetitionsById(id, competitions) {
let tempCompetitions = [];
for (let i = 0; i < competitions.length; i++) {
if (competitions[i].union.id === id) {
tempCompetitions.push(competitions[i]);
}
}
return tempCompetitions;
}
sortCategories(competitions) {
if (competitions.length === 0) return [];
let categories = [];
categories.push(competitions.filter((item) => {
return item.category === 'ADULTS' && item.sex === 'M'
}));
categories.push(competitions.filter((item) => {
return item.category === 'ADULTS' && item.sex === 'F'
}));
categories.push(competitions.filter((item) => {
return item.category !== 'ADULTS'
}));
return categories;
}
it always rewrites every element of array(probably returned value
refers to the same object).
You are probably unintended mutating the content of the source array. I would recommend creating a copy of the array.
This is example of array mutation.
let array1 = [1,2,3];
let array2 = array1;
array2[0] = 4; // oops, now the content of array1 is [4,2,3]
To avoid mutating the source array you can create a copy of it
let array1 = [1,2,3];
let array2 = array1.slice();
array2[0] = 4; // the content of array1 is still the same [1,2,3]
I've always tried to create new objects and assign them to 'var' not
to 'let' variables.
Using let/var will not prevent from rewrites. Creating new object with new Array() will not prevent rewrites.
It's hard to read where the bug is exactly from your code and description but you could try to avoid passing an array by reference and instead create a copy and pass the copy in function calls.
this.assignCompetitionsToUnions(unions[0].slice(), categories[k])
This is a shallow copy example, you might need to apply deep copy to make it work for your case.

Nested for-loop overwrites object attribute

I broke down my code to a simplified jsFiddle. The problem is that the attribute is is only set for one object but in the end every object gets the value of the last iteration (in this case it is false but id05 should be true). Why is it? Do I overlook something?
jsFiddle (see in the console)
var reminder = {
id0: {
id: 0,
medId: 0
}
};
var chart = {
id0: {
medId: 0,
values: [[5,1]]
}
}
var tmp = {};
for(var i = 0; i < 10; i++) {
for (id in reminder) {
tmp[id + i] = reminder[id];
tmp[id + i].is = false;
for(var j = 0; j < chart["id" + reminder[id].medId].values.length; j++) {
if (chart["id" + reminder[id].medId].values[j][0] === i) {
tmp[id + i].is = true;
}
}
}
}
tmp[id + i] = reminder[id]; will copy the reference to the object and not clone the object itself.
Consider this:
var a = { a: [] };
var b = a.a;
b.push(1);
console.log(a.a); // [1]
This means that all your objects are the same and they share the same properties (tmp.id05 === tmp.id06 etc...)
tmp.id00.__my_secret_value__ = 1234;
console.log(tmp.id09.__my_secret_value__); // 1234
To clone objects in JavaScript you can use Object.create but this will only make a shallow clone (only clone top level properties)

clear and undo clear the array

If I do have the following code then empty the arry:
var a1 = [1,2,3];
a1 = [];
//returns []
But I'm trying to make a function to clear and undo clear the array, it's not working as expected:
var foo = ['f','o','o'];
var storeArray;
function clearArray(a){
storeArray = a;
a = [];
}
function undoClearArray(a){
a = storeArray;
}
clearArray(foo);
foo; //still returns ['f','o','o']
//but expected result is: []
Here's the problem:
You assign an array to a variable foo.
Then you pass this object to your function which stores it in another variable a. Now you have one object that two variable are pointing at. In the function you then reassign a to a different object an empty array []. Now a points at the empty object and foo still points at the original object. You didn't change foo by reassigning a.
Here's a concise way to store you're array:
var storeArray = [];
function clearArray(a){
while (a.length>0){
storeArray.push(a.shift()) //now a is empty and storeArray has a copy
}
}
I tried something different. Maybe it's dirty, but the storage itself is on the object.
the fiddle
//define the object to hold the old data
Object.defineProperty(Array.prototype, "storage", {
enumerable: false,
configureable: true,
get: function () {
return bValue;
},
set: function (newValue) {
bValue = newValue;
}
});
//define the prototype function clear to clear the data
Object.defineProperty(Array.prototype, "clear", {
enumerable: false,
writable: false,
value: function () {
this.storage = this.slice(0); //copy the data to the storage
for (var p in this) {
if (this.hasOwnProperty(p)) {
delete this[p]; //delete the data
}
}
return this; //return the object if you want assign the return value
}
});
//define the prototype function restore to reload the data
Object.defineProperty(Array.prototype, "restore", {
enumerable: false,
writable: false,
value: function () {
var a = this.storage.slice(0); //copy the storage to a local var
for (var p in this.storage) {
if (this.storage.hasOwnProperty(p)) {
this[p] = a[p]; //assign the pointer to the new variable
delete this.storage[p]; //delete the storage
}
}
return this;
}
});
var a = ['f','o','o'];
console.log(a); //--> displays ['f','o','o']
a.clear();
console.log(a); //--> displays []
a.restore();
console.log(a); //--> displays ['f','o','o']
You can use splice() method to delete all elements of an array likes below
function clearArray(a){
storeArray = a;
a.splice(0,a.length);
}
var a = [1,2,3,4];
var tempArr ;
clearArray = function() {
tempArr = a.slice(0);
a.length = 0;
}
undoArray = function() {
a = tempArr.slice(0);
}
Here is a small jsfiddle: http://jsfiddle.net/66s2N/
Here's working way of what you want to achieve:
var foo = ['f','o','o'];
var storeArray;
function clearArray(a){
storeArray = a.slice(0);
for (var i=0; i<a.length; i++)
delete a[i];
a.length = 0;
}
function undoClearArray(a){
for (var i=0; i<storeArray.length; i++)
a.push(storeArray[i]);
}
console.log(foo);
clearArray(foo);
console.log(foo); //now foo is []
undoClearArray(foo);
console.log(foo); // now foo is ['f','o','o']
http://jsfiddle.net/44EF5/1/
When you do:
var a1 = [1,2,3];
a1 = [];
it's as if you've written:
var a1 = [1,2,3];
var a1 = [];
You're overwriting variables.
Now, why your approach doesn't work - in JS there's no passing by reference. MarkM response explains what's happening within the function.
Now, why does the above work - while you've got two variables pointing towards the same array, nothing prevents you from modifying that array. As such storeArray = a.slice(0) will create a copy of the array. Then by using delete we're removing all values of the array, and then as length isn't enumerable (so using for (var i in a) wouldn't help) we reassign the length of the array. This has removed the values of original array, while creating a new array assigned to storeArray.
function clearArray(a){
storeArray = a.slice(0);
return a.length = 0;
}
or set foo.length = 0;
Just update your both function with below ones
function clearArray(a){
storeArray = a.slice(0);
a.length = 0;
}
function undoClearArray(a){
a = storeArray;
return a;
}
in undoClearAray() we are returning the variable which have new reference(in your clearArray(), Both the original and new array refer to the same object. If a referenced object changes, the changes are visible to both the new and original arrays). so use it as foo=undoClearArray(foo); for old values.
try
var foo = ['f','o','o'];
var storeArray;
function clearArray(a){
storeArray = a;
a = [];
return a;
}
function undoClearArray(a){
a = storeArray;
}
foo = clearArray(foo);
foo; //returns []
You can use wrappers to do this quite nicely. First create a wrapper function with the additional methods defined, then create your array using that function instead of []. Here is an example (see JSFiddle):
var extendedArray = function() {
var arr = [];
arr.push.apply(arr, arguments);
arr.clearArray = function() {
this.oldValue = this.slice(0);
this.length = 0;
}
arr.undoArray = function() {
this.length = 0;
for (var i = 0; i < this.oldValue.length; i++) {
this.push(this.oldValue[i]);
}
}
return arr;
};
var a = extendedArray('f', 'o', 'o');
alert(a);
a.clearArray();
alert(a);
a.undoArray();
alert(a);

How to iterate over an object's prototype's properties

I have some code:
var obj = function() { }; // functional object
obj.foo = 'foo';
obj.prototype.bar = 'bar';
for (var prop in obj) {
console.log(prop);
}
What surprised me is that all that is logged is foo. I expected the for loop to iterate over the properties of the obj's prototype as well (namely bar), because I did not check for hasOwnProperty. What am I missing here? And is there an idiomatic way to iterate over all the properties in the prototype as well?
I tested this in Chrome and IE10.
Thanks in advance.
You're iterating over the constructor's properties, you have to create an instance. The instance is what inherits from the constructor's prototype property:
var Ctor = function() { }; // constructor function
Ctor.prototype.bar = 'bar';
var obj = new Ctor(); // instantiation
// adds own property to instance
obj.foo = 'foo';
// logs foo and bar
for (var prop in obj) {
console.log(prop);
}
If you want to maintain an inheritance hierarchy by defining all the properties even before the object instantiation, you could follow the below approach. This approach prints the prototype hierarchy chain.
Note: In this approach you don't have to create the constructor initially.
function myself() {
this.g = "";
this.h = [];
this.i = {};
myself.prototype = new parent();
myself.prototype.constructor = myself;
}
function parent() {
this.d = "";
this.e = [];
this.f = {};
parent.prototype = new grandParent();
parent.prototype.constructor = parent;
}
function grandParent() {
this.a = "";
this.b = [];
this.c = {};
}
var data = new myself();
var jsonData = {};
do {
for(var key in data) {
if(data.hasOwnProperty(key) && data.propertyIsEnumerable(key)) {
jsonData[key] = data[key];
}
}
data = Object.getPrototypeOf(data).constructor.prototype;
Object.defineProperties(data, {
'constructor': {
enumerable: false
}
});
} while (data.constructor.name !== "grandParent")
console.log(jsonData);

Categories

Resources