Can I use a property of an instance to update other properties? - javascript

I'm trying to update a property of a constructor based on other property values that are getting changed when a certain function fires.
I've tried to create a function as a property and I will still get the same result.
class Rps {
constructor () {
this.userChoice
this.appChoice
this.userPoints = 0
this.pcPoints = 0
this.score = `${this.userPoints} - ${this.pcPoints}`
console.log(score)
this.registeredChoice = []
}
}
After I call a function that increments the user and pc Points, the properties will be updated after i try to console.log() the new object but it will not be the same for the score. The value of the score will not change.

A getter is perfect for this:
class Rps {
constructor () {
this.userChoice
this.appChoice
this.userPoints = 0
this.pcPoints = 0
this.registeredChoice = []
}
get score() {
return `${this.userPoints} - ${this.pcPoints}`
}
}
Alternatively, don't just update properties of the object in that function you have, but call setUser(points) and setPc(points) methods that can update the score property.

Related

Two classes one with reference to function to another different this value

I have problem and I don't know how to solve it:
this.bWords.push(word);
^
TypeError: Cannot read property 'push' of undefined
here is my code:
function multiWords(words) {
class AWords {
constructor(words = []) {
this.words = words;
this.addWordFn = () => {};
}
setAddWordFn(fn) {
this.addWordFn = fn;
}
passWords() {
this.words.forEach(word => this.addWordFn(word));
}
}
class BWords {
constructor() {
this.bWords = [];
}
addWord(word) {
this.bWords.push(word);
}
}
let x = new AWords(words);
let y = new BWords();
x.setAddWordFn(y.addWord);
x.passWords();
return y.bWords;
}
console.log(multiWords(["one", "two", "three"]));
Do you have any ideas why there is different this value?
Many thanks
Pati
It appears that the problem occurs here:
this.words.forEach(word => this.addWordFn(word));
because the function you've set for addWordFn here:
x.setAddWordFn(y.addWord);
Needs a different value of this than you are calling it with. You can fix it by binding the right value of this to your callback:
x.setAddWordFn(y.addWord.bind(y));
Remember that for regular functions, the value of this inside the function is determined by how the function is called. When you call a function with obj.method(), the this value inside of method() will be set to obj. So, you're calling addWord with the wrong this value because you've made it a method of some other object (that does not also have the data it needs) and are calling it off that object.

Javascript getter/setter scope access [duplicate]

This question already has an answer here:
Object.assign getters and setters in constructor
(1 answer)
Closed 3 years ago.
I'm trying to use JS with classic prototypical inheritance, instead of the new ES6 class model, mainly to be able to access the closure scope.
In the example bellow, I want to expose a variable current declared inside the function Counter trough this object created by the new operator.
function Counter(start, stop) {
var current = start;
function inc() { if (current < stop) return current++ }
function getCurrent() { return current }
Object.assign(this, { inc, getCurrent,
get current() { return current }, set current(value) { current = value }
})
}
counter = new Counter(0, 3)
while ((v = counter.inc()) !== undefined)
console.log(counter.getCurrent(), counter.current)
I expected the following output:
1 1
2 2
3 3
Because counter.current & counter.getCurrent() should both return the same result. But instead, I'm receiving
1 0
2 0
3 0
If I replace that Object.assign(...) with the code bellow, it works as expected.
Object.assign(inc, getCurrent })
Object.defineProperty(Counter.prototype, 'current',
{ get: () => { return current }, set: (value) => { current = value }
I could use this model, (and currently using), but I would like to use the former, because is simpler and less verbose. It seems that there are 2 different scopes here.
I tested with node 10, Chrome 73 & Firefox 68, and received the same results.
What I'm missing here?
In the example above, I tried to be as terser as I could. But to be more precise and better illustrate the point, follow a more complete test, with some commented things I've tried.
Here, I renamed the variable current to _current in order to avoid confusion with the current property, but that shouldn't be obligatory.
function Counter(start, stop) {
var _current = start;
function inc() { if (_current < stop) return _current++ }
function getCurrent() { return _current }
// Object.assign(this.constructor.prototype,
// Object.assign(this.__proto__,
// Object.assign(Counter.prototype, {
Object.assign(this, {inc, getCurrent,
get current() { return _current }, set current(value) { _current = value }
// get current() { return current } // supposed to be read-only, but not
})
// This works as expected
// Object.defineProperty(Counter.prototype, 'current',
// { get: () => { return _current }, set: (value) => { _current = value } })
}
counter = new Counter(0, 3)
while ((v = counter.inc()) !== undefined) {
console.log(counter.getCurrent(), counter.current)
counter.current -= 0.5
}
the output of the code above is:
1 0
2 -0.5
3 -1
Where that counter.current -= 0.5 is storing its value?
When your code calls Object.assign(), it's passing in an object that has a getter and setter for current. Thus the Object.assign() process will itself invoke that getter to get the value for the property "current" while it's copying the property values to the new object. Thus, the Counter object ends up without the getter and setter, which explains your results. Its "counter" property is just a simple property with a copy of the value of the local counter variable at the time the constructor code ran.
Object.assign() just copies property values, accessing them the same way any other code would.
Note that if you don't call Object.assign() at all, and just return the object you're passing into it, you'll get a working object that behaves like you expect.

Calling a method on every instance of a class in Javascript?

I am trying to call a class' method on every instance of that class.
Currently, I have all of my classes stored in an array, called checkers[]
I loop through every instance of the class Checker() using this for loop:
this.drawCheckers = function() {
for(var checker in this.checkers) {
checker.draw();
}
}
When I run the code, I get the error:
localcheckers.js:57 Uncaught TypeError: checker.draw is not a function
How would I fix this?
In your for...in loop, the value stored in checker is not the property in this.checkers, but the property name.
What you need to do is access the property then call draw on it.
this.drawCheckers = function()
{
for(var checker in this.checkers) {
this.checkers[checker].draw();
}
}
See a working example here:
https://jsbin.com/fezixaboso/edit?js,console
call draw() only if checker is an instance of Checker
this.drawCheckers = function() {
for(var i = 0; i < this.checkers.length; i++) {
if(this.checkers[i] instanceof Checker)
this.checkers[i].draw();
}
}

Knockout arrayForEach undefined property

I'm having trouble trying to get a number from each item in a knockout observable array and add the numbers together and assign it to another computed variable. Here's what I have right now...
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses, function (course) {
total += course.MinHours();
});
return total;
}),
},
...
What I'm trying to do is, in the totalCredits variable, I'm trying to iterate through the PlannedCourses array and get the MinHours variable for each item and add them together in the total variable. Then I return it to the totalCredits item in the Semesters array. The issue I'm having is getting the PlannedCourses variable in the ko.utils.arrayForEach part. I'm getting an undefined on it and I'm not sure why. I think it's a simple syntax error but I can't see what's wrong.
The PlannedCourses observable array is a dynamic object that is getting the list of PlannedCourses properly. It's defined in the context of itself but I'm not passing it to the totalCredits computed function properly.
I hope this is clear enough. Thank you for your help!
Note: All the rest of the code is working as intended. The only part that isn't working is the totalCredits computed function. I'm not sure if anything within the ko.utils.arrayForEach is working as I haven't been able to get that far.
You're going to need to change the way you populate your Semesters observable array to use a constructor function in order to get a reference to the correct scope for this:
function semester(name, code) {
this.Name = name;
this.Code = code;
this.PlannedCourses = ko.observableArray([]);
this.totalCredits = ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses(), function (course) {
//Note the change to "this.PlannedCourses()" above to get the underlying array
total += course.MinHours();
});
return total;
}, this); //now we can pass "this" as the context for the computed
}
See how we can now pass in an object to the second argument for ko.computed to use as the context for this in the inner function. For more information, see the knockout docs: Managing 'this'.
You then create new instances of semester when populating your array:
Semesters: ko.observableArray([
new semester("Fall", "300"),
new semester(...)
]);
This approach also means you have a consistent way of creating your semester objects (the computed is only defined once for one thing), rather than possibly incorporating typos etc in any repetition you may originally have had.
As others already mentioned your this is not what you think it is. In your case the context should be passed to the computed as follows:
totalCredits: ko.computed(function() {
// Computation goes here..
}, this)
Another approach could be to store the correct this to some local variable during the object creation (ex. var self = this; and then use self instead of this).
However, ko.utils.arrayForEach doesn't work with observable arrays but works on pure JavaScript arrays, so you should unwrap the observable array to access the elements of the underlying array:
ko.utils.arrayForEach(this.PlannedCourses(), function(course) {
// ...
});
// Or
ko.utils.arrayForEach(ko.unwrap(this.PlannedCourses), function(course) {
// ...
});
The scope (this) isn't what you think it is.
See http://knockoutjs.com/documentation/computedObservables.html
try adding your context, like the following:
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
ko.utils.arrayForEach(this.PlannedCourses, function (course) {
total += course.MinHours();
});
return total;
}, this), // new context passed in here
},
...
Doing this passes in the context of the array item itself into your computed function.
Edit:
you may need to access the Semesters object inside you loop, and add some way to reference the current item:
Semesters: ko.observableArray([
{
semesterName: "Fall",
semesterCode: "300",
PlannedCourses: ko.observableArray([]),
totalCredits: ko.computed(function(){
var total = 0;
for( var i = 0, len = Semesters().length; i < len; i++ ) {
// check current array item, possibly add an id?
if( Semesters()[i].semesterName === "Fall" &&
Semesters()[i].semesterCode === "300" ) {
ko.utils.arrayForEach(Semesters()[i].PlannedCourses, function (course) {
total += course.MinHours();
});
break; // done searching
}
}
return total;
})
},

Javascript Setter Being Bypassed

Say I have this code:
function test() {
this._units = {};
}
test.prototype = {
get units() { return this._units; },
set units(val) {
this._units = val;
for (var unit in this._units) {
if (this._units[unit] === 0)
delete this._units[unit];
}
}
};
Now, I can assign a unit via the following:
x = new test();
x.units['foo'] = 1;
This all works. However, when I do this,
x.units['foo'] = 0;
// x.units = { 'foo' : 0 }
It doesn't remove foo from the units as it should. How can I change this?
Thanks!
You cannot intercept when a property is created, you'd need a Proxy for that. Unfortunately, it is only a harmony draft and currently only supported in Firefox' Javascript 1.8.5.
Your setter only detects an assignment to x.units = {foo:1}, but not to x.units.foo. You could create setters for every property on the units objects that you know of, but you need an extra method to make them known in the first place (assuming you're not only assigning objects to x.units).
However, you might better not do this at all. A property that deletes itself when being set to 0 is very counterintuitive. Think of
x.units.foo = -1; x.units.foo += 2;
Would you expect this to do the same as
x.units.foo = -1; x.units.foo++; x.units.foo++;
? The second would yield NaN instead of 1.

Categories

Resources