How does the P.I. work? (Private Eye Exercise)


#1

Hi all,

I just finished the final Objects II exercise but am not confident I understand what's going on...

(NOTE: I pasted the program below for reference, but overwrote my changes back to the original, i.e. it is not "the answer".)


The instructions:

Modify the StudentReport class so that no grades will be printed to the console in the for-in loop.

However, getGPA should still function properly in the last line.

The hints:

You should be changing public variables (this.grade) to private variables (var grade).

If we want getGPA to be able to be called from outside this class, should we change it to be private?

You should find yourself needing to modify getGPA itself. this.grade1 will not be available if you did not declare it previously. Perhaps changing it to simply grade1 will work?


My questions:

  1. In the original code, the for...in loop appears to cycle through the properties in myStudentReport and print the values of each property. What does the if statement do? Is it an optional window dressing? Is it correct to infer that none of the properties are functions/methods, including the one with a method in it?

  2. If the getGPA is a return and not a method, can I assign a method to a variable by putting it parentheses? Or is there no point to this? i.e. would it be accurate to say that getGPA is a variable that holds the method's return, and (objectName.) get.GPA(); calls the method that returns the value?

  3. Furthermore, does this mean getGPA holds the value of the method before I ever use getGPA or call the method? Do methods run automatically upon object instantiation?

  4. Is the second hint a "gotcha"? I would guess so but am not sure because I don't think I understand the public/private boundary well; in Private Variables (the lesson before the one where we write a method in the constructor to access the private variable) I tried to figure out a way to add a method to the prototype to access the private variable and couldn't get it to work.

  5. What happens in the solution? Does the object create a public variable/key out of the private ones by running the method and setting its return to the key? And the for...in theoretically still behaves the same as before but can now no longer see the first three properties?

  6. Finally, how does one call a private variable? I keep tripping up and trying to use this. In terms of syntax (not scope/context/???), can I think of calling a private variable in the same way as calling a global one?

Thanks, everyone!
frisby



https://www.codecademy.com/en/courses/objects-ii/5/4


function StudentReport() {
    this.grade1 = 4;
    this.grade2 = 2;
    this.grade3 = 1;
    this.getGPA = function() {
        return (this.grade1 + this.grade2 + this.grade3) / 3;
    };
}

var myStudentReport = new StudentReport();

for(var x in myStudentReport) {
    if(typeof myStudentReport[x] !== "function") {
        console.log("Muahaha! " + myStudentReport[x]);
    }
}

console.log("Your overall GPA is " + myStudentReport.getGPA());


#2

1

The if statement is there to make sure that we will get only properties, not methods. Why is this important? Well, this for ... in loop is used to describe our object. What makes our object special (in comparison to another object of the same class)? Values of properties, methods are common for the whole class.

Simple example:

var Circle = function(x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.area = function() {
    return Math.PI * this.radius * this.radius;
  };
  this.perimeter = function() {
    return 2 * Math.PI * this.radius;
  };
};

var smallCircle = new Circle(0, 0, 1),
  bigCircle = new Circle(2, 2, 5);

var descriptionOfObject = function(obj) {
  for (var property in obj) {
    if (typeof obj[property] !== "function") {
      console.log(property + ": " + obj[property]);
    }
  }
};

var descriptionOfObjectWithoutIf = function(obj) {
  for (var property in obj) {
    console.log(property + ": " + obj[property]);
  }
};

console.log("List of properties of smallCircle.");
descriptionOfObject(smallCircle);
console.log("List of properties of bigCircle.");
descriptionOfObject(bigCircle);

console.log("List of properties and methods of smallCircle.");
descriptionOfObjectWithoutIf(smallCircle);
console.log("List of properties and methods of bigCircle.");
descriptionOfObjectWithoutIf(bigCircle);

Result of this code is:

List of properties of smallCircle.
x: 0
y: 0
radius: 1
List of properties of bigCircle.
x: 2
y: 2
radius: 5
List of properties and methods of smallCircle.
x: 0
y: 0
radius: 1
area: function() {
  return Math.PI * this.radius * this.radius;
}
perimeter: function() {
  return 2 * Math.PI * this.radius;
}
List of properties and methods of bigCircle.
x: 2
y: 2
radius: 5
area: function() {
  return Math.PI * this.radius * this.radius;
}
perimeter: function() {
  return 2 * Math.PI * this.radius;
}

Do you see? There is nothing special in printing the methods :slight_smile:


Extra note. It does not make sense to print out to the console body of the method. But it might be interesting to print result of all methods that don't take any parameters :wink:

var descriptionOfObject = function(obj) {
  for (var property in obj) {
    if (typeof obj[property] !== "function") {
      console.log(property + ": " + obj[property]);
    } else if (obj[property].length === 0) {
      console.log(property + ": " + obj[property]());
    }
  }
};

2

I am not sure if I get the question correctly, but I will try to answer it.

getGpa is a method. No matter if it's this.getGpa or var getGpa, it's a method.

can I assign a method to a variable by putting it parentheses?

If you mean something like this (variable area):

var Circle = function(x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.calculateArea = function() {
    return Math.PI * this.radius * this.radius;
  };
  this.calculatePerimeter = function() {
    return 2 * Math.PI * this.radius;
  };
  this.updateRadius = function(newRadius) {
    this.radius = newRadius;
  };
  var area = this.calculateArea();
  this.printArea = function() {
    console.log(area);
  };
};

var smallCircle = new Circle(0, 0, 1);

smallCircle.printArea();
smallCircle.updateRadius(10);
smallCircle.printArea();

Then it does not make any sense. Why? Because in the moment of object creation, this variable will get a fixed value. Just like in the example -> we have changed radius of our circle and output of this script is:

3.141592653589793
3.141592653589793

If you want to assign a function to a variable you have to omit the parentheses.

would it be accurate to say that getGPA is a variable that holds the method's return, and (objectName.) get.GPA(); calls the method that returns the value?

Not really. The value of getGpa is a function. When you call this method ((objectName).getGpa()) value that should be returned is calculated.

3

Nope. The value that should be returned is calculated when you call the method (or more generally - function). In the example I provided to second question value of area is fixed only because we implicitly called our method.

4

I tried to figure out a way to add a method to the prototype to access the private variable and couldn't get it to work.

It's simply not possible. Methods in the constructor have access to the private variables (or constructor parameters) because they are defined in the same scope.

But with this information in mind, we can simply create a simple workaround. From prototype, we can't access private variables, but we have the ability to call the public methods.

var Circle = function(x, y, radius) {
  this.getX = function() {
    return x;
  };
  this.setX = function(val) {
    x = val;
  };
  this.getY = function() {
    return y;
  };
  this.setY = function(val) {
    y = val;
  };
  this.getRadius = function() {
    return radius;
  };
  this.setRadius = function(val) {
    radius = val;
  };
};

Circle.prototype.area = function() {
  return Math.PI * this.getRadius() * this.getRadius();
};

Circle.prototype.perimeter = function() {
  return 2 * Math.PI * this.getRadius();
};

var smallCircle = new Circle(0, 0, 1);

console.log("Perimeter of smallCircle: " + smallCircle.perimeter());
console.log("Area of smallCircle: " + smallCircle.area());

The concept of getters (accessors) and setters (mutators) is a really important part of OOP. Many novice programmers looking at the code above would ask "why don't just make x, y and radius public?". OOP is about encapsulation. We don't want to give user ability to change important class fields. He can still use setRadius to make a change, but now we can check inside our method if the value given by him is, for example, a positive number. We are controlling the situation :wink:

5

Private variables (defined with var) are accessible only by the methods defined in the constructor. The for ... in loop is placed in the global scope and this is why you can't access private variables. getGpa still behaves in the same way. When you call it (console.log("Your overall GPA is "+ myStudentReport.getGPA());) code inside its body is executed and the calculated value is returned. The value that should be returned is recalculated every time you call this method.

6

A great example of a private variable is a class parameter (parameter of the constructor to be more specific). In this code:

var Circle = function(x, y, radius) {
  this.x = x;
  // (...)
};

we simply tell: create public property x and make it equal to the value of private variable x.

So yep, you access private variables just like global variables. The only difference is a scope where you can access them.


If I incorrectly understood any of the questions, or if there is something unclear - please do not hesitate to ask :slight_smile:


Private Eye 30/30
#3

Wow-- thank you for this amazing answer, @factoradic! :speak_no_evil: :speak_no_evil:

I do have some follow up questions for you if that's okay :relaxed:

1

I think when I did the exercise I thought the for...in loop was printing my GPA too, but after seeing your circle example I realised it's the bottom line that's doing that. :see_no_evil:

If the method were attached to a prototype instead of the constructor, however (assuming the grades remained public), would hasOwnProperty filter out the method for me?

I'm confused about what the program in "Extra note" does. It looks to me like the second part prints a method(?), but I don't understand when it will run -- when obj[property].length will === 0.

var descriptionOfObject = function(obj) {
  for (var property in obj) {
    if (typeof obj[property] !== "function") {
      console.log(property + ": " + obj[property]);
    } else if (obj[property].length === 0) {
      console.log(property + ": " + obj[property]());
    }
  }
};

2/3/6

I think I'm mainly confused about var x = functionName vs var x = functionName(). I can sort of understand in contrast to something like var x = functionName(())(??) but the difference between the two still trips me up.

Further, if getGPA returns the gpa, how come the value of getGPA is the method and not the return? In contrast to the IIFE(?), I can guess that the value is the method because it is not yet called(?) but then again I am lost in regards to var x = this.getGPA(); vs var x = this.getGPA;.

4

I think I understand your explanation, but in the example program I have trouble locating the private variables. Are arguments private variables if not declared in the function body?

var Circle = function(x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.calculateArea = function() {
    return Math.PI * this.radius * this.radius;
  };
  // (more methods)
};

5

Private variables (defined with var) are accessible only by the methods defined in the constructor ... When you call it (console.log("Your overall GPA is "+ myStudentReport.getGPA());) code inside its body is executed and the calculated value is returned...

Is this similar to closure? The method can access the private variables within the object but I can't like how a nested function can access the variables in its closure when I can't?

6

A great example of a private variable is a class parameter (parameter of the constructor to be more specific).

Does this mean that constructor arguments are all private variables until set to this's public keys? Are parameters of non-constructor functions/methods also private?

In my original question, I meant is declaring a private key in an object syntactically the same as declaring a "normal" variable elsewhere, e.g. "normally" in an object one would omit var but to create a private key one would include it?


Thank you again for your help and thorough explanation, Maciej!


#4

@msfrisby,

A google search
== the Book ==
javascript function prototype en-us site:developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype
in the left-hand-column unde properties
find the length property
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length


#5

Ahhh, thank you, @leonhard.wettengmx.n. I thought .length referred to the length of the property key; how embarrassing :see_no_evil:


#6

You're very welcome! I will try to explain all inaccuracies.

1

If the method were attached to a prototype instead of the constructor, however (assuming the grades remained public), would hasOwnProperty filter out the method for me?

Yes, but not intentionally. hasOwnProperty returns true only if object itself has the specified property. By property we usually mean public variable, unfortunately, this is not consistent with JavaScript itself, where property is a public variable or a public method.

So every method attached to prototype would not be printed, but you can also attach properties to prototype and this is a problem with this method. Also, it would not filter our public methods defined in the constructor.

Small example:

var Circle = function(x, y, radius) {
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.area = function() {
    return Math.PI * this.radius * this.radius;
  };
};

Circle.prototype.perimeter = function() {
  return 2 * Math.PI * this.radius;
};

Circle.prototype.rotation = 0;

var smallCircle = new Circle(0, 0, 1);

var descriptionOfObject = function(obj) {
  for (var property in obj) {
    if (obj.hasOwnProperty(property)) {
      console.log(property + ": " + obj[property]);
    }
  }
};

console.log("List of properties of smallCircle.");
descriptionOfObject(smallCircle);

And the result is:

List of properties of smallCircle.
x: 0
y: 0
radius: 1
area: function() {
  return Math.PI * this.radius * this.radius;
}

2/3/6

I will try to explain this with an example. This is not strictly related to OOP, so I will just use normal function and variables.

Let's say that we have a simple function that returns a greeting message:

var greetingMessage = function(name) {
  name = name || "John Doe";
  return "Hello, " + name + "! :)";
};

console.log(greetingMessage("Maciej"));
console.log(greetingMessage());

If you are not familiar with this expression:

name = name || "John Doe";

It simply means: assign to variable name value "John Doe" if it does not have a value yet. In functions, we use this construction to define optional parameters with default values. So if we call our function without any parameters we will get Hello, John Doe! :).

Result of this code is:

Hello, Maciej! :)
Hello, John Doe! :)

And now we create two variables:

var withParentheses = greetingMessage();
var withoutParentheses = greetingMessage;

The first one now stores a string. Why? Because we assigned to it result of the function call. Let's check what is inside it :slight_smile:

console.log(withParentheses);
// >> Hello, John Doe! :)

Why is it "John Doe"? Because we left our parentheses empty - function call without parameters.


The second one stores a function. Because we assigned to it value of object greetingMessage, which is a function.

So now we can treat our variable just like normal function:

console.log(withoutParentheses);
// >> function (name) {
// >>   name = name || "John Doe";
// >>   return "Hello, " + name + "! :)";
// >> }
console.log(withoutParentheses());
// >> Hello, John Doe! :)
console.log(withoutParentheses("Maciej"));
// >> Hello, Maciej! :)

So, in your example:

var x = this.getGPA();

x now stores the result of method getGpa. It's not a function, it's just a plain value.

var x = this.getGPA;.

x is now a function, body of x and getGpa is exactly the same.

4

Are arguments private variables

Exactly! :slight_smile: Moreover, if you define a private variable with the same name as a parameter - value of the parameter will be overwritten.

5

In JavaScript, we always have access to the parent scope. That is why we can access, for example, global variables from the body of the function.

Is this similar to closure?

Kind of. Closure is a much more complicated concept, it combines mechanism of accessing parent scope, self-invoking functions, and anonymous functions.

I will use a most common example because I am not able to come up with something cool here:

var incrementCounter = (function(initialState) {
  var counter = initialState;
  return function() {
    return counter += 1;
  };
})(0);

So, we assigned to the variable incrementCounter a self-invoking, anonymous function that takes one parameter - initialState, we provided 0 as an argument. In this function we create a local (private) variable counter and we assign to it value of parameter. This function returns a function, which is incrementing value of counter and returns it.

Now, why do we have access here to the counter? Because it is in the parent scope.

A direct parent scope of our returned function is this:

function(initialState) {
  var counter = initialState;
  return function() {
    return counter += 1;
  };
}

and this is where counter is defined.

Because the outer function is self-invoking it will be automatically executed and now our variable incrementCounter will hold new value returned by the self-invoking function.

console.log(incrementCounter);
// >> function() {
// >>   return counter += 1;
// >> }

And in this moment, we have lost access to the outer function. But incrementCounter can still access it.


I know. It's not a simple concept to grasp, but to be fair - it's strictly related to one of the most important principles of OOP - encapsulation. In this simple example - does it make any sense to let us change value of counter without using a incrementCounter function? Of course, it depends on usage, but in most cases - we should be fine without changing this value directly.

6

Does this mean that constructor arguments are all private variables until set to this's public keys?

Yes, but you can access them (in the correct scope) even after creating corresponding public variables.

Are parameters of non-constructor functions/methods also private?

Exactly. The only difference is the scope. You can access constructor parameters in the constructor and in the inner scopes (like scope of methods), parameters of methods are not accessible in the outer scope, you can use them only in the body of the method.

"normally" in an object one would omit var but to create a private key one would include it?

I have a problem understanding this question, sorry. To create a variable in the current scope (like a private variable of the class) we use var keyword. Without it - we create a global variable.

var Person = function(firstName, lastName) {
  name = firstName + " " + lastName;
  this.greet = function() {
    console.log("Hello " + name + "!");
  };
};

var me = new Person("Maciej", "Wiercioch");
var myFriend = new Person("Natalia", "Kosmowska");

me.greet();
// >> Hello Natalia Kosmowska!

Ad 4

Some time ago I asked one of my students to create a class that would represent the rectangle. This is his code:

var Rectangle = function(sideA, sideB) {
  this.sideA = sideA;
  this.sideB = sideB;
  this.perimeter = function() {
    return 2 * sideA + 2 * sideB;
  };
  this.area = function() {
    return sideA * sideB;
  };
};

var rectangle = new Rectangle(2, 5);
console.log("Perimeter: " + rectangle.perimeter());
// >> Perimeter: 14
console.log("Area " + rectangle.area());
// >> Area: 10

Do you see what is wrong? :slight_smile: