Need help understanding object oriented approach to Javascript - javascript

Can someone help me understand the object oriented approach to javascript? I am used to writing js code as follows:
function o_deal(id) {
var hand = gcard1 + ", " + gcard2;
var res = gcard1_val + gcard2_val;
document.getElementById(id).innerHTML = hand;
if (res == 21) {
alert("Blackjack!");
}
if (bucket == 0) {
bucket = " ";
}
var card3_val = Math.floor(Math.random() * deck.length);
var nhand = deck[card3_val];
bucket = bucket + " " + nhand + ", ";
bucket_val = bucket_val + gcard1_val + gcard2_val + card3_val;
if (bucket_val >= 22) {
var r = confirm("Bust! By " + nhand);
if (r == true) {
refresh();
}
else {
refresh();
}
}
document.getElementById(id).innerHTML = bucket;
}
But I've seen a number of posters on stack overflow that write code like this:
var Hand = function(bjcallback) {
this.cards = [];
this.onblackjack = bjcallback;
this.deck = [1,2,3,4,5,6,7,8,9,10,"Jack","Queen","King","Ace"];
this.values = {
"Jack": 10,
"Queen": 10,
"King": 10,
"Ace": 11
};
this.sum = function() {
var i, x, res = 0;
for (i in this.cards) {
x = this.cards[i];
if (typeof(x) != 'number') { x = this.values[x] };
res += x;
};
return res
};
this.pick = function() {
var pos = Math.floor(Math.random() * this.deck.length);
var card = this.deck[pos];
console.log(card);
return card
};
this.deal = function(n) {
n = n || 2;
Can someone please break the second method down so I can understand the difference? Any help would be appreciated.

Object orientation in javascript is not two hard. You just bundle functionality and data together.
So we have some functionality.
I'll just look at this snippet
function o_deal(id) {
var hand = gcard1 + ", " + gcard2,
res = gcard1_val + gcard2_val;
document.getElementById(id).innerHTML = hand;
if (res == 21) {
alert("Blackjack!");
}
}
Let's refactor this. We would need a few functions
isBlackjack. for checking whether we've won.
toString for rendering the hand
Now we need to define hand.
var Hand = {
"isBlackjack": function() {
return this.cards[0].value + this.cards[1].value === 21;
},
"toString": function() {
return this.cards[0].toString() + " " + this.cards[1].toString();
}
}
Now we can refactor o_deal to
function o_deal(id, hand) {
document.getElementById(id).innerHTML = hand.toString();
if (hand.isBlackjack()) {
alert("Blackjack!");
}
}
Of course we still need the concepts of cards and we need to be able to make a hand.
Making a hand is easy. var hand = Object.create(Hand)
We also need a Card object which needs a toString method
var CardList = [null, "1","2","3","4","5","6","7","8","9","X","A"];
var Card = {
"toString": function() {
return CardList[this.value];
}
}
Now we just need a way to create a hand
var createHand = function() {
var hand = Object.create(Hand);
var card1 = Object.create(Card);
var card2 = Object.create(Card);
card1.value = Math.floor(Math.random() * 11);
card2.value = Math.floor(Math.random() * 11);
hand.cards = [card1, card2];
return hand;
}
Now hopefully you can see how encapsulation and binding data and functionality together is useful.

Douglas Crockford is your man. He has a series of articles that really delve into the finer points of JavaScript. I recommend reading all of the articles:
http://javascript.crockford.com/
This one explains JavaScript's OO syntax:
http://javascript.crockford.com/private.html

object oriented comes into play when the whole thing is in Class.

Object oriented code is for organization and reusability. So in your second example, you have a class Hand, that you can create new instances of. So every time you want to create a new hand of cards for a player, you could do:
var playerHand = new Hand(options);
and pass in parameters to the class, which would be used to differentiate one hand from the other.
this.deck, this.cards, etc. are properties of the object, and this.sum, this.pick, etc are methods. These methods (or simply functions) can act upon the public and private properties of the object.
This particular example of OO code wouldn't be a real world example (or at least I wouldn't organize it that way). The "deal" method would be part of the CardDealer class.
So you would have the following Classes/Objects (amongst others), from which you can create new instances:
Dealer, Player, Hand, Card, Game
(Mind you, this is just one approach, as you mentioned, there are many ways to go about this)
The Game object could have a "type" property, in which case it would be blackjack. The Game object could then be responsible for loading the appropriate rules for blackjack. One instance of Dealer would be needed, and X amount of Player objects as well, and each would have a Hand object associated with it.
This way, each object is responsible for its own properties and actions (methods). It keeps everything organized and encapsulated.
As I'm writing this, #Raynos just posted examples of rewriting your procedural code as OO, so hopefully this might help you with the whys instead of the hows.

Related

How to save create global variables (with custom name) in a private function

I'm creating an algebra math tool where I take an equation, take the terms, pair like terms, and then evaluate. (I'm making this as a coding exercise)
For the equation a + b - 2a I've gotten the terms saved in an array; ["+a", "+b", "-2a"]. Now what I want is to change this into this:
var lTERMa = ["+a", "-2a"];
var lTERMb = ["+b"];
So far my "fix" is to store the variables in a div element that has white text so you can't see it. It's rather crude, and it doesn't work. Here is the full Javascript code that I've made designed to store them in the div:
function appendinformation(info) {
document.getElementById("hiddenData").innerHTML += info;
}
function runData(bool, int) {
if (bool) {
eval(document.getElementById("hiddenData").innerHTML)
console.log()
}
eval(document.getElementById("hiddenData").innerHTML)
}
var termsViewed = 0;
var lTermsFound = 0;
var seenTerms = []
function processTERMpos(val) {
if (seenTerms.includes(val.replaceAll("+", ""))) {
termsViewed++;
appendinformation(`var lLTERM${val.replaceAll("+", "")} = [${val}]`);
runData()
}
if (!seenTerms.includes(val.replaceAll("+", ""))) {
appendinformation(`lTERM${val.replaceAll("+", "")}.push(${val})`)
runData()
seenTerms.push(val.replaceAll("+", ""))
lTermsFound++;
termsViewed++;
}
}
function processTERMneg(val) {
if (seenTerms.includes(val.replaceAll("-", ""))) {
termsViewed++;
appendinformaion(`var lLTERM${val.replaceAll("-", "")} = [${val}]`);
runData()
}
if (!seenTerms.includes(val.replaceAll("-", ""))) {
appendinformation(`lTERM${val.replaceAll("-", "")}.push(${val})`)
runData()
seenTerms.push(val.replaceAll("-", ""))
lTermsFound++;
termsViewed++;
}
}
for (var i = 0; i < term.length; i++) {
var subject = term[i]
getHiddenData();
eval(hdata);
if (subject.includes("+")) {processTERMpos(subject)}
if (subject.includes("-")) {processTERMpos(subject)}
}
Don't bully me for my bad disorganised coding. I've deleted and reinstated various code, and I'm sure there is code in there than no longer has a use.
If someone could either fix my code, or be able to suggest another way for me to attempt rewriting the code. I've been deleting and rewriting this code for 2 hours and I can't think of any way I can fix it.

How to access variables and methods of objects within an array [resolved]

So I have a class that looks something like this:
class Car {
constructor(name, pos) {
this.carName = name;
this.carPos = pos;
}
}
I then end up creating an array of objects using this class as so:
newCar = new Car("Fusion","100 100");
let carManifest = [newCar]
I later in my code go on to carManifest.push() several other Car objects into the array, building it up. Eventually I run into a for loop such that:
for (index = 0; index < carManifest.length; index++) {
if(carManifest[index].carName === 'Honda')
<Do Stuff>
}
Whenever I go to access carManifest[index].carName I get "undefined". I also had a get method within the object for the same variable, but using that in this context wouldn't even compile. I am aware that the array itself does not have these variables, but as far as I'm aware there is no way to declare an array to be of a specific class in JS like you can in Java and C.
Could anyone help me out? I am new to JS and have been stumped on this for the past hour. I will be glad to update this if there is not enough info. Thanks!
Note: Not sure if it matters but I am using Node.js
Change your code from
let carManifest = [newCar]
to push item to array as
carManifest.push(newCar);
/*
function Car (name, pos) {
this.carName = name;
this.carPos = pos;
}
*/
class Car {
constructor(name, pos) {
this.carName = name;
this.carPos = pos;
}
}
newCar = new Car("Fusion","100 100");
let carManifest = [];
carManifest.push(newCar);
for (index = 0; index < carManifest.length; index++) {
if(carManifest[index].carName === 'Fusion'){
console.log(carManifest[index].carName);
}
}
Are you pushing other variables or objects into the same array? There is nothing wrong with the code examples you're showing, you are able to access the variables of the objects this way. You'll have to show more code, where exactly are you getting undefined?

Repeating Value and Replacing Value with JS

Newbie in JS here. I'm having trouble replacing and repeating the value from the function. Here's the code:
function Phone(ring) {
this.ring = ring;
}
function updateRing(newRing) {
this.newRing = ring;
}
var samsung = new Phone('Chim');
samsung.ring(2); // Needs to compute to "Chim, Chim"
var htc = new Phone('Dada');
htc.ring(3); // Needs to compute to "Dada, Dada, Dada"
htc.updateRing('Riri');
htc.ring(1); // Needs to compute to "Riri"
For the repeat value for the first function, I tried using this.repeat but it didn't work inside the Phone function.
For the updateRing function, I couldn't get the code to replace the this.ring.
I stripped down all the useless code I had written. Thanks in advance for any help.
You can repeat strings with string.repeat()
let a = "ring"
console.log(a.repeat(2))
But to get the comma separator to work cleanly you can make a disposable array and join() is with a comma.
let ringString = Array(3).fill("ring").join(", ")
console.log(ringString)
For the others, you probably want to use classes, which are pretty easy, but don't run on IE without a ployfill. Or prototypes, which can be a little confusing at first. Here's an example using prototypes to define methods on your Phone object:
function Phone(ring) {
// changed to ring_tone too prevent clash with this.ring method
this.ring_tone = ring;
}
// you need to define these on the prototype to you can use `this`
Phone.prototype.updateRing = function(newRing) {
// don't need to define a this.newRing, just update the ring
this.ring_tone = newRing;
}
Phone.prototype.ring = function(n) {
return new Array(n).fill(this.ring_tone).join(', ')
}
var samsung = new Phone('Chim');
console.log(samsung.ring(2)); // Needs to compute to "Chim, Chim"
var htc = new Phone('Dada');
console.log(htc.ring(3)); // Needs to compute to "Dada, Dada, Dada"
htc.updateRing('Riri');
console.log(htc.ring(1)); // Needs to compute to "Riri"
1) You're calling samsung.ring as a function, even though it's just an instance variable of Phone.
2) The reason why this.repeat didn't work is because repeat isn't a method of "this," which refers to Phone.
Try this instead:
var samsung = new Phone('Chim');
samsung.ring.repeat(2);
var htc = new Phone('Dada');
htc.ring.repeat(3);
Maybe try this:
class Phone {
constructor(sound) {
this.sound = sound;
}
ring(number) {
var i;
for (i = 0; i < number; i++) {
console.log(this.sound + ", ");
}
}
updateRing(newSound) {
this.sound = newSound;
}
}
var samsung = new Phone('Chim');
samsung.ring(2);
samsung.updateRing('Riri');
samsung.ring(1);
Codepen - https://codepen.io/anon/pen/MGRJOB?editors=0010

Using object inheritance when both objects pass arguments

I have a Equipment parent class which takes in args and two children Weapon and Armor which also take args. I'm not sure if there is a special way to target prototypes or if my code actually isn't working but here is a shortened DEMO
I need to create the variables used for the arguments in each object based on the value of other variables as well as an algorithm that uses a random number. Each item is unique so I need to make the hp for equipment at the same time as the damage for weapons and I'm not sure how to do that.
function Equipment(hp) {
var self = this;
this.hp = hp;
}
//create subclass for weapons
function Weapon(baseDam) {
var self = this;
this.baseDam = baseDam;
}
function generateEquipment() {
hp = Math.round(Math.random() * 10);
baseDam = Math.round(Math.random() * 50);
Weapon.prototype = new Equipment(hp);
weapon = new Weapon(baseDam);
stringed = JSON.stringify(weapon);
alert(stringed);
}
generateEquipment();
First, the answer to your question :
Your code is not really wrong, and your weapon still has its hp, except that its contained in the objects prototype, so won't show when stringified.
There are ways to get around this, like I've shown here, but this according to me is not the correct way to do it.
Normally, prototypes should only store methods and not instance variables, because if you later decide to modify the prototype, the instance variable will get modified as well ,in case it is passed by reference.
A better pattern would be to use Object.assign - it is the easiest to understand and feels most natural. Further, if you expect Weapon to be a subclass of equipment, that logic should be encapsulated in Weapon itself.
Here is the proposed new way of declaring your Weapon Class :
function Weapon(baseDam) {
var self = this;
var hp = Math.round(Math.random() * 10);
Object.assign(self, Equipment.prototype, new Equipment(hp));
this.baseDam = baseDam;
}
Since hp is also generated randomly, that logic is now encapsulated in Weapon. This is also scalable, as this pattern will work for long inheritence chains as well.
Some people may recommend ES6 classes, which is also an approach that would work, but in my opinion it is syntactical sugar, which hides most of the inner workings of your code.
Here is a working demo with my approach.
The 'pattern' you're describing is called 'Composition' and can be very powerful. There're many different ways of combining/composing new classes or objects.
Reading your question and comments, it seems to me that you're mainly interested in defining many different types of equipment without too much (repeated) code.
Have you thought about passing an array of class names to your generateEquipment method and returning a new, custom constructor? Here's an example:
function Equipment(hp) {
this.hp = Math.round(hp);
}
Equipment.prototype.describe = function() {
return "This piece of equipment has " + this.hp + " hitpoints";
}
function Weapon(baseDam) {
this.baseDam = Math.round(baseDam);
}
Weapon.prototype.describe = function() {
return "The weapon does " + this.baseDam + " damage";
}
function generateCustomEquipment(types) {
var CustomEquipment = function() {
var self = this;
// Create the properties for all types
types.forEach(function(type) {
type.call(self, Math.random() * 100);
});
};
CustomEquipment.prototype.describe = function() {
var self = this;
// Combine the 'describe' methods of all composed types
return types
.map(function(type) {
return type.prototype.describe.call(self);
})
.join(". ");
}
return CustomEquipment;
}
var Sword = generateCustomEquipment([Equipment, Weapon]);
var Armor = generateCustomEquipment([Equipment]);
var Arrow = generateCustomEquipment([Weapon]);
var sword1 = new Sword();
document.writeln("A sword: " + sword1.describe() + "<br/>");
var armor1 = new Armor();
document.writeln("A piece of armor: " + armor1.describe() + "<br/>");
var arrow1 = new Arrow();
document.writeln("An arrow: " + arrow1.describe() + "<br/>");

More efficient comparison of numbers

I have an array which is part of a small JS game I am working on I need to check (as often as reasonable) that each of the elements in the array haven't left the "stage" or "playground", so I can remove them and save the script load
I have coded the below and was wondering if anyone knew a faster/more efficient way to calculate this. This is run every 50ms (it deals with the movement).
Where bots[i][1] is movement in X and bots[i][2] is movement in Y (mutually exclusive).
for (var i in bots) {
var left = parseInt($("#" + i).css("left"));
var top = parseInt($("#" + i).css("top"));
var nextleft = left + bots[i][1];
var nexttop = top + bots[i][2];
if(bots[i][1]>0&&nextleft>=PLAYGROUND_WIDTH) { remove_bot(i); }
else if(bots[i][1]<0&&nextleft<=-GRID_SIZE) { remove_bot(i); }
else if(bots[i][2]>0&&nexttop>=PLAYGROUND_HEIGHT) { remove_bot(i); }
else if(bots[i][2]<0&&nexttop<=-GRID_SIZE) { remove_bot(i); }
else {
//alert(nextleft + ":" + nexttop);
$("#" + i).css("left", ""+(nextleft)+"px");
$("#" + i).css("top", ""+(nexttop)+"px");
}
}
On a similar note the remove_bot(i); function is as below, is this correct (I can't splice as it changes all the ID's of the elements in the array.
function remove_bot(i) {
$("#" + i).remove();
bots[i] = false;
}
Many thanks for any advice given!
Cache $("#" + i) in a variable; each time you do this, a new jQuery object is being created.
var self = $('#' + i);
var left = parseInt(self.css("left"));
var top = parseInt(self.css("top"));
Cache bots[i] in a variable:
var current = bots[i];
var nextleft = left + current[1];
var nexttop = top + current[2];
Store (cache) the jQuery object of the DOM element within the bot representation. At the moment it's been created every 50ms.
What I mean by this is that for every iteration of the loop, you're doing $('#' + i). Every time you call this, jQuery is building a jQuery object of the DOM element. This is far from trivial compared to other aspects of JS. DOM traversal/ manipulation is by far the slowest area of JavaScript.
As the result of $('#' + i) never changes for each bot, why not store the result within the bot? This way $('#' + i) gets executed once, instead of every 50ms.
In my example below, I've stored this reference in the element attribute of my Bot objects, but you can add it your bot (i.e in bots[i][3])
Store (cache) the position of the DOM element representing the bot within the bot representation, so the CSS position doesn't have to be calculated all the time.
On a side note, for (.. in ..) should be strictly used for iterating over objects, not arrays. Arrays should be iterated over using for (..;..;..)
Variables are extremely cheap in JavaScript; abuse them.
Here's an implementation I'd choose, which incorporates the suggestions I've made:
function Bot (x, y, movementX, movementY, playground) {
this.x = x;
this.y = y;
this.element = $('<div class="bot"/>').appendTo(playground);
this.movementX = movementX;
this.movementY = movementY;
};
Bot.prototype.update = function () {
this.x += this.movementX,
this.y += this.movementY;
if (this.movementX > 0 && this.x >= PLAYGROUP_WIDTH ||
this.movementX < 0 && this.x <= -GRID_SIZE ||
this.movementY > 0 && this.y >= PLAYGROUND_HEIGHT ||
this.movementY < 0 && this.y <= -GRIDSIZE) {
this.remove();
} else {
this.element.css({
left: this.x,
right: this.y
});
};
};
Bot.prototype.remove = function () {
this.element.remove();
// other stuff?
};
var playground = $('#playground');
var bots = [new Bot(0, 0, 1, 1, playground), new Bot(0, 0, 5, -5, playground), new Bot(10, 10, 10, -10, playground)];
setInterval(function () {
var i = bots.length;
while (i--) {
bots[i].update();
};
}, 50);
You're using parseInt. As far as I know, a bitwise OR 0 is faster than parseInt. So you could write
var left = $("#" + i).css("left") | 0;
instead.
Furthermore, I wouldn't make use of jQuery functions to obtain values like these every 50 ms, as there's always a bit more overhead when using those (the $ function has to parse its arguments, etc.). Just use native JavaScript functions to optimize these lines. Moreover, with your code, the element with id i has to be retrieved several times. Store those elements in a variable:
var item = document.getElementById(i);
var iStyle = item.style;
var left = iStyle.left;
…
(Please note that I'm not a jQuery expert, so I'm not 100% sure this does the same.)
Moreover, decrementing while loops are faster than for loops (reference). If there's no problem with looping through the elements in reverse order, you could rewrite your code to
var i = bots.length;
while (i--) {
…
}
Use offset() or position() depending on if you need coordinates relative to the document or the parent. position() is most likely faster since browsers are efficient at finding offsets relative to the parent. There's no need for parsing the CSS. You also don't need the left and top variables since you only use them once. It may not be as readable but you're going for efficiency:
var left = $("#" + i).position().left + bots[i][1];
var top = $("#" + i).position().top + bots[i][2];
Take a look here for a great comparison of different looping techniques in javascript.
Using for...in has poor performance and isn't recommended on arrays. An alternative to looping backwards and still using a for loop is to cache the length so you don't look it up with each iteration. Something like this:
for(var i, len = bots.length; i < len; i++) { ... }
But there are MANY different ways, as shown in the link above and you might want to test several with your actual application to see what works best in your case.

Categories