I have Created a Class Circle. Here
_radius is a private parameter
_areaCalculate is a private method
After Calculate the value from private method _areaCalculate. I need this value to public method areaPrint. But it show me undefined.
const _radius = new WeakMap()
const _areaCalculate = new WeakMap()
class Circle {
constructor(r) {
_radius.set(this, r)
}
[_areaCalculate]() {
return (Math.PI * Math.pow(this.radius, 2)).toFixed(2)
}
areaPrint() {
console.log("The area of Circle is: " + _areaCalculate.get(this))
}
}
let c = new Circle(4)
c.areaPrint()
If one sticks with the OP's approach of utilizing a weak map for accessing a Circle instance' "private member" through a prototypal method, then one just needs to simplify the code to a single reference map and a function which calculates a circle instance' area on the fly ...
function getComputedArea(circle) {
return (Math.PI * Math.pow(rMap.get(circle), 2)).toFixed(2);
}
const rMap = new WeakMap();
class Circle {
constructor(radius) {
rMap.set(this, radius);
}
areaPrint() {
console.log(
`A radius ${ rMap.get(this) } circle area is ${ getComputedArea(this) }`
);
}
}
let a = new Circle(4);
let b = new Circle(9);
a.areaPrint();
b.areaPrint();
... or one follows VLAZ's advice and starts utilizing the private field declaration syntax for private instance fields.
Edit
From the further beneath comment-based discussion with Bergi ...
"Private methods, unlike private fields, are allocated on the prototype not on the instance, just the like their respective public counterparts" . – Bergi
... the implementation for getComputedArea changed from a local helper function to a private instance method.
class Circle {
#getComputedArea(radius) {
return (Math.PI * Math.pow(this.#radius, 2)).toFixed(2);
}
#radius;
constructor(radius) {
this.#radius = radius;
}
areaPrint() {
console.log(
`A radius ${ this.#radius } circle area is ${ this.#getComputedArea() }`
);
}
}
let a = new Circle(4);
let b = new Circle(9);
a.areaPrint();
b.areaPrint();
Related
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 months ago.
class Player {
constructor() {
this.health = 100;
}
playerDamaged(applyDamage) {
applyDamage(this);
}
}
class BrickWall {
constructor() {
this.collisionDmg = 5;
}
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
class SpikyWall {
constructor() {
this.collisionDmg = 25;
}
playerCollision(player) {
player.health -= SpikyWall.collisionDmg;
}
}
const player = new Player();
const brickWall = new BrickWall();
const spikyWall = new SpikyWall();
player.playerDamaged(brickWall.playerCollision);
player.playerDamaged(spikyWall.playerCollision);
console.log(player.health);
In the Player class I want to be able to pass a callback to playerDamaged to apply damage to the Player's health.
My intended objective is that line when player.playerDamaged(brickWall.playerCollision); runs, the player's health will be reduced by 5, and when player.playerDamaged(brickWall.playerCollision); runs, the player's health will be reduce by another 25.
I want console.log(player.health); to return 70. It currently returns NaN.
Thanks for any assistance you can offer!
With this
player.health -= BrickWall.collisionDmg;
you're referring to BrickWall.collisionDmg - which would only make sense if the property is on the class itself, like so:
class BrickWall {
static collisionDmg = 5;
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
or
class BrickWall {
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
BrickWall.collisionDmg = 5;
The class is not the instance; the instance does not inherit from the class as a prototype. (The instance inherits from the <class>.prototype, but not from <class>) So, since no property exists at BrickWall.collisionDmg in your code, it returns undefined, resulting in NaN.
Add the static properties.
class Player {
constructor() {
this.health = 100;
}
playerDamaged(applyDamage) {
applyDamage(this);
}
}
class BrickWall {
static collisionDmg = 5;
playerCollision(player) {
player.health -= BrickWall.collisionDmg;
}
}
class SpikyWall {
static collisionDmg = 25;
playerCollision(player) {
player.health -= SpikyWall.collisionDmg;
}
}
const player = new Player();
const brickWall = new BrickWall();
const spikyWall = new SpikyWall();
player.playerDamaged(brickWall.playerCollision);
player.playerDamaged(spikyWall.playerCollision);
console.log(player.health);
That said, I don't think having classes for BrickWall and SpikyWall are doing that much for you here (other than making the logic uncomfortably weird to follow due to the callbacks). A class is useful when you want to tie together data associated with an instance with methods to operate on that data - but you don't have any instance data. JS isn't Java, so don't feel like you have to use a class just because the keyword exists. Consider a plain function instead, and then call that function, perhaps like:
const brickWall = player => player.health -= 25;
or
const COLLISION_DAMAGE = {
brickWall: 5,
spikeyWall: 25,
};
// ...
player.playerDamaged(COLLISION_DAMAGE.brickWall);
I'm having trouble using p5js in instance mode. I split my code into multiple files for easier maintenance, separating classes from the main sketch file.
Inside the classes i need access to variables declared in the main sketch but i get undefined when i inject the sketch object into the class.
Ideally i would like to have access to ANY variables declared in the main sketch file. Is it possible/recommended?
Here is a minimum case example of what i'm trying to achieve:
index.js
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5( (p) => {
let myColor = 75;
p.setup = function() {
p.createCanvas(1000,1000);
let t = new TestClass(100,100)
t.render(p)
}
}, "p5");
_testClass.js
export default class TestClass {
constructor(x,y) {
this.x = x;
this.y = y;
}
render(p) {
p.rect(this.x, this.y, 50, 50); // i get a square on the canvas: p.rect() is recognized
console.log(p.myColor); // undefined
}
}
Any variables that you want to be available within the TestClass's render() function either need to be 1) passed to the TestClass constructor, 2) passed to the render function, 3) declared/assigned on/to the p5 instance, or 4) declared globally.
For instance mode sketches it is not uncommon to add things to the p5 instance:
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5((p) => {
// let myColor = 75;
p.myColor = 75;
p.setup = function() {
p.createCanvas(1000,1000);
let t = new TestClass(100,100)
t.render(p)
}
}, "p5");
However, I think it would be better OOP practice to pass this to your TestClass constructor:
import p5 from "p5"
import TestClass from "./_testClass";
let sketch = new p5((p) => {
let myColor = 75;
p.setup = function() {
p.createCanvas(1000, 1000);
let t = new TestClass(100, 100, myColor)
t.render(p)
}
}, "p5");
export default class TestClass {
constructor(x, y, rectColor) {
this.x = x;
this.y = y;
this.rectColor = rectColor;
}
render(p) {
console.log(this.rectColor);
p.fill(this.rectColor);
p.rect(this.x, this.y, 50, 50);
}
}
Clearly, a new developer! Thank you for replying in advance, I know the answer must be really simple but I have been stuck for an hour.
This exercise asks to create a class and then asks to change some of the dynamic values using class methods/functions. I am not able to get my values to change when I call the function.
Here are the directions for the exercise, followed by my code, sorry that my work looks so rudimentary!
These are the directions:
// Create a new class called SuperHero
// - Your class should have the following DYNAMIC values
// - name
// - superpower
// - age
// - Your class should have the following STATIC values
// - archNemesis, assigned to "The Syntax Error"
// - powerLevel = 100
// - energyLevel = 50
//Where I start having issues is here:
// - Create the following class methods
// - sayName, should print the hero's name to the console
// - maximizeEnergy, should update the energyLevel to 1000
// - gainPower, should take an argument of a number and INCREASE the powerLevel //by that number
class SuperHero {
constructor(name, superpower, age,){
this.name = name;
this.superpower = superpower;
this.age = age;
this.archNemesis = "The Syntax Error";
this.powerLevel = 100;
this.energyLevel = 50;
}
var power = 20;
sayName() {
console.log(this.name);
}
maximizeEnergy() {
this.energyLevel = 1000;
}
gainPower(power) {
this.powerLevel = this.powerLevel + power;
}
};
var superHero1 = new SuperHero("Batman", "strength", 40);
Thanks again! :)
Well, you code is 80% correct. Only issues I see is the this.energyLevel(1000); and sayName() method. They are not really required.
this.energyLevel is a property and not a function, so calling it like this.energyLevel(1000); is wrong. You should be setting this value as this.energyLevel = 1000;
sayName() is not really doing anything useful. You are already setting/initializing the name in the constructor.
Here is a simple example of a class and how to use it's methods for changing it's property values. Check the output in your browser console.
class SuperHero {
constructor(name, superpower, age, archNemesis, powerLevel, energyLevel) {
this.name = name;
this.superpower = superpower;
this.age = age;
this.archNemesis = "The Syntax Error";
this.powerLevel = 100;
this.energyLevel = 50;
}
maximizeEnergy() {
this.energyLevel = 1000;
}
gainPower(gainPower = 20) {
this.powerLevel += gainPower;
}
}
var superHero1 = new SuperHero("Batman", "strength", 40, "The Joker", 100, 50);
var superHero2 = new SuperHero("Wonder Woman", "agility", "Cheetah", 1000, 1000);
console.log('name', superHero1.name);
console.log('superpower',superHero1.superpower);
console.log('archNemesis',superHero1.archNemesis);
console.log('energyLevel',superHero1.energyLevel);
superHero1.gainPower(100);
console.log('powerLevel',superHero1.powerLevel);
console.log('-------------------------------------');
console.log('name', superHero2.name);
console.log('superpower',superHero2.superpower);
console.log('archNemesis',superHero2.archNemesis);
console.log('energyLevel',superHero2.energyLevel);
superHero2.gainPower(200);
console.log('powerLevel',superHero2.powerLevel);
you probably want to do things more like this:
sayName() {
console.log(this.name);
}
maximizeEnergy() {
this.energyLevel = 1000;
}
gainPower(power) {
this.powerLevel = this.powerLevel + power;
}
They are all pretty self explanatory.
I am struggling to implement composition in ES6 classes! I am trying to understand a lot of things at once and have maybe made a stupid error.
I would like to avoid inheritance using extend and super for now, and have found this nice example of composition that seems to show what I am after:
class EmployeeTaxData {
constructor(ssn, salary) {
this.ssn = ssn;
this.salary = salary;
}
// ...
}
class Employee {
constructor(name, email) {
this.name = name;
this.email = email;
}
setTaxData(ssn, salary) {
this.taxData = new EmployeeTaxData(ssn, salary);
}
// ...
}
In regard to the code below, I would like to use the most simple and eloquent way to make the spin() method available for the objects created by the Hero class so it is using a shared prototype. I would like to share this method with other objects that will need it too.
Unfortunately I can't make my code work as the this.angle is not referring to to the Hero class that it needs to, but the Spinner class?
class Spinner {
constructor(direction){
this.direction = direction;
}
spin(direction) {
switch (direction) {
case 'left':
this.angle -= 1;
break;
case 'right':
this.angle += 1;
break;
// no default
}
}
}
class Hero {
constructor(xPosition, yPosition) {
this.description = 'hero';
this.width = 25;
this.height = 50;
this.xPosition = xPosition;
this.yPosition = yPosition;
this.angle = 0;
this.color = 'red';
this.spin = new Spinner();
}
spin() {
this.spin.spin();
}
}
const heroClass = new Hero(100, 200);
console.log(heroClass.angle); // result is 0
heroClass.spin.spin('left');
console.log(heroClass.angle); // result is STILL 0, it didn't work
...the this.angle is not referring to to the Hero class that it needs to, but the Spinner class?
As it should. When you're inside the spinner class, then this refers to a spinner object, which also means this.angle inside the spinner class refers to an angle property of a spinner object.
You'll probably want spinner to return a new angle value, then the hero object that uses spinner should save the returned new angle value.
class Spinner {
constructor(direction){
this.direction = direction;
}
spin(direction, angle) {
switch (direction) {
case 'left':
angle -= 1;
break;
case 'right':
angle += 1;
break;
// no default
}
return angle;
}
}
class Hero {
constructor(xPosition, yPosition) {
this.description = 'hero';
this.width = 25;
this.height = 50;
this.xPosition = xPosition;
this.yPosition = yPosition;
this.angle = 0;
this.color = 'red';
this.spinner = new Spinner();
}
spin(direction) {
this.angle = this.spinner.spin(direction, this.angle);
}
}
const heroClass = new Hero(100, 200);
console.log(heroClass.angle); // result is 0
heroClass.spin('left');
console.log(heroClass.angle); // result is -1
I had to make a few other small changes for this to work. For example, you had a data property named "spin" this.spin = new Spinner as well as a method named spin spin() {. They were overriding one another.
Firstly a little elaboration of the project I'm working on. I have started building a 'map maker' for a 2d game I am working on. The project is just for fun and has proven so far to be a great way to learn new things.
I recently showed my working map maker code to a friend who suggested it would be much more re-usable if I restructured the project to be more OOR, which I am now attempting.
The problem I have is when I add a 'Guild' instance to my map, the first one works fine, but the second causes a type error that is giving me a headache!
I will post all of the relevant code from the different files below, but the overall structure is as follows:
Map.js = Map class file, container for setting the map overall size and iterating over (and placing) map objects.
MapObject.js = Class file for simple map objects such as walls, contains the position and icon properties.
Guild.js = Class file, extends MapObject.js, this is where my problem seems to be, adds an additional 'MapIcon' and will have other features such as levels and names etc.
map-maker.js = Main file for generating the map-maker page, utilises the above class files to create the map.
Below is the code used to create an instance of 'Guild' on my map:
map-maker.js (creating the map / map object / guild instances)
// Initialise a new instance of map class from Map.js using the user
provided values for #mapX and #mapY.
var currentMap = new Map(XY,40);
// Get the user box volume * map boxsize from Map.js
currentMap.calcDimensions();
// Create a Map Object and push it to the currentMap with its position.
function createMapObject(x,y,floor){
currentMap.objects.push(new MapObject(x,y,floor));
}
// Create a Guild Object (extension of Map Object) and push it to the currentMap with its position.
function createGuildObject(x,y,floor){
currentMap.objects.push(new Guild(x,y,floor));
}
....
case 13: // Enter Key (Submit)
unhighlightTools();
currentMap.drawMap();
if(currentFloor != null){
currentFloor.hasFloor = true;
if(currentFloor.tileName == "Guild"){
createGuildObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}else {
createMapObject(currentFloor.position.x,currentFloor.position.y,currentFloor);
}
console.log("Map object created at - X:"+currentFloor.position.x+" Y:"+currentFloor.position.y);
}
currentFloor = [];
highlightTools();
break;
}
Guild.js (constructor and assigning map icon)
class Guild extends MapObject {
constructor(x,y,floor) {
super(x,y,floor);
this.levels = [];
}
mapIcon() {
this.mapIcon = new Image();
this.mapIcon.src = "../images/mapSprites/obj-1.png"
return this.mapIcon;
}
}
MapObject.js (position setup and constructor)
class MapObject {
constructor(x,y,floor) {
this.position = {x, y};
this.icon = this.wallFloorIcons(floor);
}
wallFloorIcons(floor) {
this.img = new Image();
this.name = "";
this.name += (floor.wallNorth) ? 'n' : '';
this.name += (floor.wallEast) ? 'e' : '';
this.name += (floor.wallSouth) ? 's' : '';
this.name += (floor.wallWest) ? 'w' : '';
this.name = 'wall-'+this.name+'.png';
if(this.name == 'wall-.png'){
this.img.src = "../images/mapSprites/floor.png";
}else {
this.img.src = "../images/mapSprites/"+this.name;
}
return this.img;
}
getIcon() {
return this.img;
}
}
Map.js (processing the objects at a given location and drawing the canvas)
class Map {
// Map Width / Height and number of boxes. Used to generate map and zoom level.
constructor(wh, boxSize) {
this.size = wh;
this.width = wh[0];
this.height = wh[1];
this.boxSize = boxSize;
this.objects = [];
this.boxes = wh[0];
}
// Calculates the width and height * boxSize for rendering the canvas.
calcDimensions(){
this.realX = Math.floor(this.width * this.boxSize);
this.realY = Math.floor(this.height * this.boxSize);
this.realX = parseInt(this.realX,10);
this.realY = parseInt(this.realY,10);
this.realXY = [
this.realX,
this.realY
];
return this.realXY;
}
// Draws the canvas, grid and adds the objects that belong to the map.
drawMap(){
var self = this;
self.canvas = document.getElementById("canvas");
self.c = self.canvas.getContext("2d");
self.background = new Image();
self.background.src = "../images/mapSprites/oldPaperTexture.jpg";
// Make sure the image is loaded first otherwise nothing will draw.
self.background.onload = function(){
self.c.drawImage(self.background,0,0);
self.fillMap();
}
}
fillMap(){
var self = this;
self.c.lineWidth = 1;
self.c.strokeStyle = 'black';
self.c.fillStyle = "rgba(255, 255, 255, 0.2)";
for (var row = 0; row < self.boxes; row++) {
for (var column = 0; column < self.boxes; column++) {
var x = column * self.boxSize;
var y = row * self.boxSize;
self.c.beginPath();
self.c.rect(x, y, self.boxSize, self.boxSize);
self.c.stroke();
self.c.closePath();
for (var i=0; i<self.objects.length; i++) {
var floor = self.objects[i];
if (floor.position.x == column && floor.position.y == row) {
if (self.objectsAtPosition({x:floor.position.x, y:floor.position.y}) != null) {
var mapObjects = self.objectsAtPosition({x:floor.position.x, y:floor.position.y})
for (var mapObject of mapObjects) {
this.c.drawImage(mapObject.getIcon(), x, y, self.boxSize, self.boxSize);
console.log(mapObject);
if(mapObject instanceof Guild){
console.log(mapObject);
this.c.drawImage(mapObject.mapIcon(), x, y, self.boxSize, self.boxSize);
}
}
}
}
}
}
}
}
deleteObject(pos){
this.objectsAtPosition(pos);
for( var i = 0; i < this.objects.length; i++){
if(this.objects[i] == this.objs){
delete this.objects[i];
this.objects.splice(i,1);
}
}
}
objectsAtPosition(position) {
var objs = [];
for (var o of this.objects) {
if (o.position.x == position.x && o.position.y == position.y) {
objs.push(o);
}
}
return objs;
}
}
When I run the code, this is my error:
Uncaught TypeError: mapObject.mapIcon is not a function
at Map.fillMap (Map.js:70)
at Image.self.background.onload (Map.js:39)
The error comes after I add 1 guild then try to add any other map object. Guild or otherwise.
Sorry if this question is a little vague, I'm still learning (as you can see :p).
Thanks for your time!
Earl Lemongrab
Got a solution from a friend in the end.
The issue was that I was reassigning this.mapIcon = new Image() so it exploded when it was called a second time.
Feel pretty silly for not spotting it.
Thanks for the help everyone.