21. My own practice code is not working like the lesson code


#1


Hi All,
Working through Objects II I've had no problems with the lessons. I feel I get the concept and pass each lesson, moving along pretty steadily. However, when I practice on my own, making "characters" for a game, I'm not getting the expected response. According to my code the prototype chain should be "Character" > "Actionable" > "Playable". For whatever reason, I'm not getting the inheritance that I should because when I check hasOwnProperty for "isAlive" on Imp which is an "Actionable" object , and "hp" and "isAlive" on Paladin which is a "Playable" object I get a return of "false". Can somebody help me out? Thanks!


//Main character class
function Character() {
    this.isAlive = true;
}


//Actionable class
function Actionable(hp, atk, def, mp) {
    this.hp = hp;
    this.atk = atk;
    this.def = def;
    this.mp = mp;
    this.totalDef = function() {
        var totalDef = (this.hp + this.def);
        return totalDef;
    }
}
Actionable.prototype = new Character();


//Enemies
var imp = new Actionable(25, 5, 5, 0);
var knoll = new Actionable(30, 7, 5, 0);
var orc = new Actionable(35, 10, 8, 0);
var orcShaman = new Actionable(35, 5, 3, 15);
var troll = new Actionable(45, 13, 10, 0);
console.log(imp.hasOwnProperty("isAlive"));
console.log(imp.hasOwnProperty("hp"));

//Playable class
function Playable() {
    this.isPlayable = true;
}
Playable.prototype = new Actionable();

//Heroes
var paladin = new Playable(35, 10, 10, 7);
console.log(paladin.hasOwnProperty("isAlive"));
console.log(paladin.hasOwnProperty("hp"));


#2

Refer the docs carefully:

Every object descended from Object inherits the hasOwnProperty method. This method can be used to determine whether an object has the specified property as a direct property of that object; unlike the in operator, this method does not check down the object's prototype chain.

There's a reason why it's called hasOwnProperty :wink:


#3

I don't know if I'm more grateful for the help, or more frustrated that I wasted two days instead of checking out the docs. lol. Thank you so much for the help! :smiley:


#4

So now I've run into another problem. I'm no longer checking inheritance with hasOwnProperty, but when I check for the paladin's hp, or totalDef I get "undefined" and "NaN". So I'm still not getting inheritance from Actionable to Playable for some reason. Also, if I run console.log(paladin.isAlive); , which isn't in the code below, I get a return of true. So it's inheriting that from "Character" but not the "hp" from "Actionable". I can't seem to find the disconnect between Actionable and Playable.

function Character() {
    this.isAlive = true;
}


//Actionable class
function Actionable(hp, atk, def, mp) {
    this.hp = hp;
    this.atk = atk;
    this.def = def;
    this.mp = mp;
    this.totalDef = function() {
        var totalDef = (this.hp + this.def);
        return totalDef;
    }
}
Actionable.prototype = new Character();


//Enemies
var imp = new Actionable(25, 5, 5, 0);
var knoll = new Actionable(30, 7, 5, 0);
var orc = new Actionable(35, 10, 8, 0);
var orcShaman = new Actionable(35, 5, 3, 15);
var troll = new Actionable(45, 13, 10, 0);
console.log(imp.hp);

//Playable class
function Playable() {
    this.isPlayable = true;
}
Playable.prototype = new Actionable();

//Heroes
var paladin = new Playable(35, 10, 10, 7);
console.log(paladin.totalDef());
console.log(paladin.hp);

#5

Add a simple modification to your code:

//Actionable class
function Actionable(hp, atk, def, mp) {
    console.log("Invoked!");
    // to track when your Actionable constructor was invoked

and run it on an online interpreter. See the output, and deduce from it what's going wrong :wink:


#6

OK, I've commented out all of the enemies except Imp for ease.
After the initial log of Invoked from setting the object up, Invoked is recorded to the console when "imp" is declared, then we get the log of the imp's hp, then Invoked is recorded again when "Playable" is set up as a prototype of "Actionable" (that's a good thing), but that's where it get lost and I can't seem to figure out why. Maybe I've just been dealing with this particular issue too long and my mind is blocking it out. Since Invoked is recorded to the console when "Playable" is set up as a prototype of "Actionable", then all of the properties should carry down, correct? Especially, since as you can see in the image, isAlive has carried down all the way from the parent object of "Character".

I'm just not seeing the disconnect between Actionable and Playable. Sorry.


#7

As you can see in the screenshot you've posted, "Invoked" appears two times. Correlating it with lines in the code, we have the lines:

var imp = new Actionable(...);

and

Playable.prototype = new Actionable();

As you can see, the Actionable constructor only got invoked when it was called directly, using its name, as new Actionable() completely.

This means that the line:

var paladin = new Playable(args);

does NOT invoke Actionable constructor. Hence, the four arguments that you are passing to Playable constructor, in the above statement, are NOT passed on to Actionable constructor. Hence, the four properties (hp, atk, etc.) are NOT initialised to the values you'd passed as arguments.

Now, the solution to this problem requires more advanced so I'd just paste it directly in here:

// inside the child constructor
parent_constructor.apply(this, [].slice.call(arguments, 0));

You can use this snippet in your code, until you've more deeper understanding of the apply method and the arguments object. You might want to look for online tutorials in this regard.

Hope that helps! :slight_smile:


#8

All constructors in the chain should be called.

function Playable(hp, atk, def, mp) {
    Actionable.call(this, hp, atk, def, mp);  // call super constructor
    ...

The thing with JS is you can do just about whatever you want, there's nothing telling you right from wrong and as a result there are plenty of those who will observe the results they expected and then declare whatever hack they are doing as "working"

JavaScript will do whatever you want. That might sound like something that makes the language easy to use, but what it really means is that it will allow even the worst of ideas to be implemented.

ECMAScript 6 introduced the class keyword (and a few others) that is mostly just syntactic sugar but makes it a whole lot harder to get prototype chaining wrong.

class Playable extends Actionable {
    constructor(hp, atk, def, mp) {
        super(hp, atk, def, mp);  // call super constructor
        ...
    }

    method1() {
        ...
    }

    method2() {
        ...
    }
}

You might just want to go with that instead for everyone's sake.


#9

@gaurangtandon

OK, thank you very much for that. I still have one question though. In objects 2 lesson 21, this is the code for the lesson, and it works:

// original classes
function Animal(name, numLegs) {
    this.name = name;
    this.numLegs = numLegs;
    this.isAlive = true;
}
function Penguin(name) {
    this.name = name;
    this.numLegs = 2;
}
function Emperor(name) {
    this.name = name;
    this.saying = "Waddle waddle";
}

// set up the prototype chain
Penguin.prototype = new Animal();
Emperor.prototype = new Penguin();

var myEmperor = new Emperor("Jules");

console.log( myEmperor.saying ); // should print "Waddle waddle"
console.log( myEmperor.numLegs ); // should print 2
console.log( myEmperor.isAlive ); // should print true

And this is my latest in the online interpreter:

function Character(name) {
	this.name = name;
    this.isAlive = true;
}


//Actionable class
function Actionable(name, hp, atk, def, mp) {
	console.log("Invoked!");
	this.name = name;
    this.hp = hp;
    this.atk = atk;
    this.def = def;
    this.mp = mp;
    this.totalDef = function() {
        var totalDef = (this.hp + this.def);
        return totalDef;
    }
}

//Playable class
function Playable(name) {
	this.name = name;
    this.isPlayable = true;
}

//Set up the prototype chain
Actionable.prototype = new Character();
Playable.prototype = new Actionable();

//Enemies
var imp = new Actionable('Imp', 25, 5, 5, 0);
//var knoll = new Actionable(30, 7, 5, 0);
//var orc = new Actionable(35, 10, 8, 0);
//var orcShaman = new Actionable(35, 5, 3, 15);
//var troll = new Actionable(45, 13, 10, 0);
console.log(imp.hp);

//Heroes
var paladin = new Playable('Paladin', 35, 10, 10, 7);
console.log(paladin.name);
console.log(paladin.hp);
console.log(paladin.isAlive);

As you can see, in the lesson code, there are no arguments in the section under the //Set up the prototype chain comment. I went ahead and added a name argument that carries all the way down, just to make it match closer to the actual lesson. As best as I can tell, you can just swap "myEmporer" for "paladin" and the code is pretty much the same. My question now is, why does it work on the lesson without the extra line of code that you provided?

Also, thank you very very much for taking so much time on this one particular topic. I've worked as an advisor before I know first hand that it can be frustrating when you're trying to help somebody and, for whatever reason, they keep coming back with more questions. Just wanted to let you know I appreciate it.


@ionatan Thanks for that. That looks very similar to the classes I used while playing around in PHP. I will definitely implement this in real-world work, but I still want to wrap my head around why this seems to work in the lesson, but not outside of it.


#10

Because when you did this,

var myEmperor = new Emperor("Jules");

the Emperor constructor assigns the name property a value. The specific reason for this to work is that JavaScript goes from bottom to top in the prototype chain. When you access a property on an object, it will go from bottom to top and as soon as it finds an object, in the prototype chain, that has a particular property you're looking for, it will use that object's property's value.

Since Emperor object has the name property defined and is at the bottom most level, its property's value ("Jules") gets used. Also note that you can use the __proto__ property to manually see that while the upper prototype objects do have the name property, they're undefined.

console.log( myEmperor.__proto__.__proto__ ); // { name: undefined, numLegs: undefined, isAlive: true }
console.log( myEmperor.__proto__); // { name: undefined, numLegs: 2 }

You could also modify the code a bit to this:

// set up the prototype chain
Penguin.prototype = new Animal();
Emperor.prototype = new Penguin("Hello");

var myEmperor = new Emperor();

now if you do:

console.log(myEmperor.name);

you'll get undefined, since the bottom-most object, in the prototype chain, which has the name property set is myEmperor.


#11

And it did "work":

> console.log(paladin.__proto__)
Character {
  hp: undefined,
  atk: undefined,
  def: undefined,
  mp: undefined,
  totalDef: [Function] }

They were given values when the constructor was called to create an object to use as a prototype. Arguments weren't given, so those parameters are undefined, which is what was assigned to those names.


#12

@gaurangtandon, @ionatan

Great! Thanks again for your help and patience. :slight_smile:


#13

Yes it did. You can run it here and see that the output is:

{ name: undefined,
  hp: undefined,
  atk: undefined,
  def: undefined,
  mp: undefined,
  totalDef: [Function] }

it lists the properties and methods present on Actionable, which is the immediate parent object of paladin in the prototype chain, and which is what I wanted to print. Not sure why you would put those double quotes on work :confused:


#14

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.