JavaScript (Part 2)
Continuing on from the previous lesson, this lesson shows how to create objects with members (i.e., fields and methods) that behave the way public and private instance members, and public and private class (i.e., static) members, would in a language like Java.
Using Functions to Create Objects with Private Members
In a typical object-oriented programming language, the distinction made between a class and an object goes something like this: a class is a blueprint; an object is a thing created based on that blueprint. So you can expect the object to have whatever members are specified in the blueprint…etc. But there’s another way to think about the distinction: a class is a bit of code you can look at in a text editor; an object is a thing that’s created when your program runs, based on that code. When you create an object, memory is allocated for the new object. When you create another object, although it might be based on the same class, you have to allocate more memory for it. Each object has its own memory, to store its own unique state.
So what does it means to say that, in JavaScript, a function is an object? Not just that the function can have member fields and methods, but that the function is created, not defined. The function is not (just) a bit of code you can look at in a text editor, it’s a thing that gets created when your program runs. And when the function object is created, memory is allocated to store its unique state. The fact that functions have state, in JavaScript, means that they can remember things from one invocation to the next.
To illustrate this, here’s yet another version of the second example from the section on immediate functions.
(function () {
var name;
name = "David";
hello = function () {
console.log(name);
name = "Dave";
};
}());
hello(); // prints "David"
hello(); // prints "Dave"
A function created this way, so that it includes as part of its
state a variable declared outside it, is called a closure. Notice
that, even though hello
is being called outside the immediate
function that contains its definition (i.e., the statement that
creates it), it still has access to the name
variable. hello
behaves like a public method with access to a private field, a
standard pattern in a typical object-oriented language like Java.
Here’s the PigPlayer
constructor again, this time using
closures to make holdAmount
and score
private fields.
PigPlayer = function (holdAmountArg) {
var holdAmount, score;
if (!(this instanceof PigPlayer)) {
return new PigPlayer(holdAmountArg);
}
holdAmount = holdAmountArg;
score = 0;
this.getScore = function () { return score; };
this.takeTurn = function () {
var turnScore = 0, rollScore;
while (turnScore < holdAmount) {
rollScore = Math.floor(Math.random() * 6) + 1;
if (rollScore > 1) {
turnScore += rollScore;
} else {
turnScore = 0;
break;
}
}
score += turnScore;
}
}
To clarify what’s going on here… The getScore
function
includes a reference to PigPlayer
’s score
field; the takeTurn
function includes references to both score
and holdAmount
.
Because score
and holdAmount
are declared within PigPlayer
,
you might expect they’d be deallocated when PigPlayer
returns
and they go out of scope. But they are kept around for the sake
of score
and takeTurn
, because they’ll be needed if these
functions are called later.
holdAmount
and score
act like private fields would
in a language like Java—they aren’t directly accessible from
outside the object, but they persist as part of the object’s
state, accessible to the object’s methods (and therefore
indirectly accessible outside the object). We’ll take this
idea further in the next section, considering how we can create
variables and functions that act like private or public members
of an object, and also static vs. instance members.
Exercise : Write a function (or more than one) to read multiple files
concurrently. It should create XMLHttpRequest
objects and
call send
for all the files, not waiting for one to finish
loading before sending the request for another. In addition,
it should be able to determine when all of the files are loaded,
and should at that point call a callback function passed in as
an argument.1
Here’s how it might look to call your function.
printResponses = function (responses) {
var i;
for (i = 0; i < responses.length(); i++) {
console.log(responses[i]);
}
};
fileNames = ["hello.txt", "salut.txt", "hola.txt"];
readFiles(fileNames, printResponses);
Hint: One approach to solving this problem would be to put an immediate function inside a loop, in order to limit the scope of variables declared within that function to just the code within the loop, as shown here.
j = 1;
for (i = 0; i < 5; i++) {
(function () {
var j = 2;
console.log(j); // prints "2" (5 times)
}());
}
console.log(j); // prints "1"
Hint (continued): Unfortunately JSList doesn’t allow you to create a function inside a loop; it would usually be a wasteful mistake. A way to work around this would be to create a function that creates and returns a new function, and to call the function-creating function from within a loop. The example below illustrates this.
j = 1;
createJPrinter = function () {
return function () {
var j = 2;
console.log(j);
};
};
for (i = 0; i < 5; i++) {
jPrinter = createJPrinter();
jPrinter(); // prints "2" (5 times)
}
console.log(j); // prints "1"
Public, Private, Static
If you add a variable to this
in a constructor, it acts
like a public instance field. Similarly, if you add a function
to this
in a constructor, it acts like a public instance method.
PigPlayer = function (holdAmountArg) {
if (!(this instanceof PigPlayer)) {
return new PigPlayer(holdAmountArg);
}
// public instance field
this.holdAmount = holdAmountArg;
// public instance method
this.increaseHold = function (amount) {
this.holdAmount += amount;
};
};
p = new PigPlayer(25);
console.log(p.holdAmount); // prints "25"
p.increaseHold(10);
console.log(p.holdAmount); // prints "35"
If you add a variable or function within the constructor but don’t
assign it to this
, it will act like a private field or method.
PigPlayer = function (holdAmountArg) {
var holdAmount, score, rollDice;
if (!(this instanceof PigPlayer)) {
return new PigPlayer(holdAmountArg);
}
// private instance fields
holdAmount = holdAmountArg;
score = 0;
// public instance method
this.getScore = function () { return score; };
// private instance method
rollDice = function () {
return Math.floor(Math.random() * 6) + 1;
};
// public instance method
this.takeTurn = function () {
var turnScore = 0, rollScore;
while (turnScore < holdAmount) {
rollScore = rollDice();
if (rollScore > 1) {
turnScore += rollScore;
} else {
turnScore = 0;
break;
}
}
score += turnScore;
};
};
p = new PigPlayer(25);
console.log(p.holdAmount); // prints "undefined"
p.takeTurn();
console.log(p.getScore()); // prints updated score...
Notice a couple things about the code above… The third to last
line prints undefined
because holdAmount
isn’t accessible
directly as a member of p
. But holdAmount
is still part of
p
’s state; it’s accessed indirectly when takeTurn
is called.
So holdAmount
acts like a private instance field.
Also notice the rollDice
method, marked by the comment above it
as a private instance method. Actually it could have been marked
as a private static method instead, since it doesn’t access any
instance fields or methods, i.e., it doesn’t use this
. What if
it did? You would probably expect that, if p.takeTurn()
were
used to call takeTurn
and, within takeTurn
, rollDice
were
called—you’d expect that if rollDice
used this
that it would
refer to p
. But it wouldn’t. In JavaScript, if you call a
function from an object variable (x.doSomething()
), this
is
assigned the object (x
) within the function; but if you don’t
(doSomething()
), this
still points to a global object. And
this is true even for rollDice
in the example above.
How can we get around this problem? We’ll look at two ways here.
First, this
could be assigned to a local variable. Second, we
could forget this
altogether—just create a new object, assigned
to a local variable, and return that object at the end of the
constructor (instead of returning this
, which is what happens
when you don’t include a return
statement).
Here’s the first way. (Unfortunately the convention is to use
that
when you assign this
to a local variable; it’s confusing,
but I’m sticking to the convention here.)
PigPlayer = function (holdAmountArg) {
var that, holdAmount, score, rollDice;
if (!(this instanceof PigPlayer)) {
return new PigPlayer(holdAmountArg);
}
// Assign "this" to a local variable, for the sake of any
// private (i.e., declared with "var" in this function)
// methods that need to access public instance members.
that = this;
holdAmount = holdAmountArg;
score = 0;
that.getScore = function () { return score; };
// If rollDice used "this" it would refer to a global object,
// which would be (very) bad. But if it used "that" it would
// refer to the object rollDice was added to when it was
// created, which would be good.
rollDice = function () {...};
that.takeTurn = function () {...};
};
And here’s the second way. I’ve called the new object variable
pb
, for “public” (less to type, but also because public
is a
keyword in JavaScript), since anything I add to pb
will become
a public member of the object. Notice that the if
statement at
the beginning, to make sure new
was used when the constructor
was called, is no longer necessary. The point of making sure
new
was used was to make sure this
wouldn’t refer to a global
object. But this version of PigPlayer
doesn’t use this
at all,
so it doesn’t matter whether new
is used to call it.
PigPlayer = function (holdAmountArg) {
var pb, holdAmount, score, rollDice;
// Create a new empty object; assign it to pb, which will be
// a container for public members.
pb = {};
holdAmount = holdAmountArg;
score = 0;
pb.getScore = function () { return score; };
// Would be fine for a private instance method like rollDice
// to refer to "pb". (Compare to previous version...would not
// have been fine for rollDice to refer to "this" in that
// version.)
rollDice = function () {...};
pb.takeTurn = function () {...};
// Instead of returning "this" (which would happen
// automatically if PigPlayer were called with new and had
// no return statement), return pb, the container object for
// public members.
return pb;
};
To make the code a little clearer, we can use pv
as a container
for private members in the same way pb
is used for public
members.
PigPlayer = function (holdAmount) {
var pb = {}, pv = {};
pv.holdAmount = holdAmount;
pv.score = 0;
pb.getScore = function () { return pv.score; };
pv.rollDice = function () {...};
pb.takeTurn = function () {...};
return pb;
};
What about static members? In the following example, numDice
would act like a private static field. It’s created just once,
when the immediate function wrapping PigPlayer
runs. (This
is in contrast to pb
and pv
above, which are created every
time the constructor PigPlayer
runs.) Since it’s not returned
there won’t be a way to access it directly from outside a
PigPlayer
object, and yet if it’s accessed within a function
that’s a member of the PigPlayer object (e.g., pv.rollDice
)
a reference to it will be maintained as part of the state of
the object. That is, it will act like a private static field.
(function () {
var numDice = 2;
PigPlayer = function (holdAmount) {...};
}());
We have a container object for public instance members (pb
),
and one for private instance members (pv
). Why not create one
for private static members like numDice
? And why not make
rollDice
a (private) static method, since it doesn’t need to
access any instance members?
(function () {
var ps = {};
// private static field
ps.numDice = 2;
// private static method
ps.rollDice = function () {
var i, total = 0;
for (i = 0; i < ps.numDice; i++) {
total += Math.floor(Math.random() * 6) + 1;
}
return total;
};
PigPlayer = function (holdAmount) {...};
}());
One more possibility would be to include public static members.
In Java, these would be accessed via the class name (i.e.,
Math.random
). It’s possible to do the same thing in JavaScript,
as illustrated by the example below.2 (To avoid repetition I left
out parts of the code in the last few examples, but I’ve included
the whole thing here since this is the last version of the code.)
(function () {
var ps = {};
// private static field
ps.numDice = 2;
// private static method
ps.rollDice = function () {
var i, total = 0;
for (i = 0; i < ps.numDice; i++) {
total += Math.floor(Math.random() * 6) + 1;
}
return total;
};
PigPlayer = function (holdAmount) {
var pb = {}, pv = {};
// private instance fields
pv.holdAmount = holdAmount;
pv.score = 0;
// public instance methods
pb.getScore = function () { return pv.score; };
pb.takeTurn = function () {
var turnScore = 0, rollScore;
while (turnScore < pv.holdAmount) {
rollScore = ps.rollDice();
if (rollScore > ps.numDice) {
turnScore += rollScore;
} else {
turnScore = 0;
break;
}
}
pv.score += turnScore;
};
return pb;
};
// public static method
PigPlayer.setNumDice = function (numDice) {
ps.numDice = numDice;
};
}());
It would be possible to add a public static field in the same way;
just add it as a member to PigPlayer
after the PigPlayer
function. There’s just one possible problem to be aware of.
Suppose we defined maxHold
, after setNumDice
, in the following
way.
PigPlayer.maxHold = 50;
As long as we always accessed maxHold
via PigPlayer
, everything
would be fine. But what about this?
p = new PigPlayer(20);
...
p.maxHold = 40;
Rather than changing the static field PigPlayer.maxHold
, this
statement would add maxHold
to p
as a new public instance
field and initialize it to
40. You wouldn’t get any error message, the code just wouldn’t
do what you intend. Because of this you might want to avoid
public instance fields. Instead, you could just use a variable
declared in such a way that its scope contains all
instances.
Exercise : Why is it that setNumDice
isn’t defined at the
beginning
with private static members? Or what if setNumDice
were
defined inside PigPlayer
? What difference would that make?
Exercise : Translate the following Java class into a JavaScript
function,
following the pattern of the final version of PigPlayer
above: use ps
for any private static members, pb
for
any public instance members, and pv
for any private instance
members; add any public static members to the constructor object
(e.g., PigPlayer
) after adding the constructor function.
(You may assume SpriteDrawer
is defined somewhere else, i.e,
add it to your JSLint /*global
line at the beginning of the
file.)
public class Sprite {
private static int minX, minY, maxX, maxY;
static {
minX = minY = 0;
maxX = 640;
maxY = 480;
}
private String filename;
private double xPos = 0, yPos = 0;
private boolean movable;
private boolean visible = true;
public Sprite(String filename, double xPos, double yPos,
boolean movable) {
this.filename = filename;
forceMove(xPos, yPos);
this.movable = movable;
}
private void forceMove(double dx, double dy) {
xPos += dx;
yPos += dy;
if (xPos < minX) xPos = minX;
if (xPos > maxX) xPos = maxX;
if (yPos < minY) yPos = minY;
if (yPos > maxY) yPos = maxY;
}
public void move(double dx, double dy) {
if (movable) forceMove(dx, dy);
}
public void hide() {
visible = false;
}
public void show() {
visible = true;
}
public void draw() {
if (visible)
SpriteDrawer.draw(filename, xPos, yPos);
}
public boolean checkCollision(Sprite that,
double collisionDistance) {
double dx = this.xPos - that.xPos;
double dy = this.yPos - that.yPos;
double distance = Math.sqrt(dx * dx + dy * dy);
return distance <= collisionDistance;
}
}
-
In case you are worried about the possibility of a race condition, if for example you had multiple callback functions trying to modify a shared variable at the same time…you don’t need to worry about that. In JavaScript a function runs as an atomic piece of code, i.e., its execution won’t be interleaved with that of another function. For more details: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop. ↩
-
A different way to simulate public static members would be to use the
prototype
member automatically created for function objects and assigned tothis
when the function is invoked as a constructor (i.e., withnew
). I’ve chosen not to do it this way, and to avoid a general discussion of howprototype
works in Javascript, in order to keep this lesson from getting too long. ↩