JavaScript (Part 1)

These first lessons are meant to teach you (at least one way) to use JavaScript effectively, assuming you have learned basic object-oriented programming in a language like Java. Most of the ideas here come from JavaScript Patterns, by Stoyan Stefanov, and JavaScript: The Good Parts, by Douglas Crockford.

I suggest you used the Atom text editor, with the “jslint” extension installed. (You’ll get a lot of error messages from the early examples, however, so you might want to disable jslint initially.)

Objects

In JavaScript, you don’t need a class to create an object. Here’s code to create an empty object called player and then give it two fields, holdAmount and score.

// Create a new empty object, assign it to player.
player = {};

// Add fields.
player.holdAmount = 20;
player.score = 0;

Here’s code to add a method, takeTurn, which simulates a turn in the dice game “Pig” (https://en.wikipedia.org/wiki/Pig_(dice_game)).

// Add a method.
player.takeTurn = function () {
    turnScore = 0;

    // Keep rolling until reaching holdAmount or rolling
    // a "pig" (rolling a 1), in which case any points
    // accumulated during the turn are lost, and the turn
    // ends.
    while (turnScore < player.holdAmount) {
        rollScore = Math.floor(Math.random() * 6) + 1;

        if (rollScore > 1) {
            turnScore += rollScore;
        } else {
            turnScore = 0;
            break;
        }
    }

    // Add points accumulated during the turn to overall
    // score.  (If a pig was rolled, only the points for
    // the turn are lost, not points from previous turns.)
    player.score += turnScore;
};

If you link to this code from a web page, you can run it in a browser. Here’s a minimal web page (assuming the code above is saved in “pig.js”).

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <script src="pig.js"></script>
  </head>
  <body>
  </body>
</html>

If you save this as “pig.html” in the same folder where you saved “pig.js,” and then open “pig.html” in a web page, it will run the JavaScript code above. But you won’t see anything…until you add the code below, which uses player to simulate a solitaire version of Pig and prints the results in the browser’s developer console.

// Play a solitaire version of Pig.  (How many turns will it
// take to reach 100 points?)

turnCount = 0;

while (player.score < 100) {
    player.takeTurn();
    turnCount++;
}

console.log(player.score + " points in " + turnCount + " turns.");

Exercise : Enter the code above, saved in “pig.html” and “pig.js,” in the same folder. Open “pig.html” in a web browser. In Chrome, you will be able to see the output if you choose “More Tools…Developer Tools…Console,” starting from the options menu in the upper right. You’ll also see error messages if you made any mistakes entering the code. Assuming it’s working correctly, you should see a score of at least 100, earned in something like 10 to 15 turns.

Functions

In JavaScript, you don’t need a class to create an object, as the code above illustrates. But what if you want to create two similar objects, objects that would be instances of a single class in a typical object-oriented programming language? You could just repeat a big chunk of the code above, first creating player and adding the fields and method, and then creating player2 and adding the same fields and the same method. But there is a better way. You can define a function that creates a Pig player object, and then each time you need one you can call the function.

PigPlayer = function (holdAmount) {
    var player = {};    // var makes player local.

    player.holdAmount = holdAmount;
    player.score = 0;

    player.takeTurn = function () {
        turnScore = 0;

        while (turnScore < player.holdAmount) {
            rollScore = Math.floor(Math.random() * 6) + 1;

            if (rollScore > 1) {
                turnScore += rollScore;
            } else {
                turnScore = 0;
                break;
            }
        }

        player.score += turnScore;
    };

    return player;
};

Here’s how you’d use the PigPlayer function to create two objects you can use to simulate a two-player game of Pig.

p = PigPlayer(15);
q = PigPlayer(25);

while (p.score < 100 && q.score < 100) {
    p.takeTurn();
    q.takeTurn();
}

if (p.score > q.score) {
    console.log("First player won " + p.score + " to " +
            q.score + ".");
} else if (p.score < q.score) {
    console.log("First player lost " + p.score + " to " +
            q.score + ".");
} else {
    console.log("The two players tied at " + p.score + ".");
}

Notice the use of var in the second line of the PigPlayer function. By default JavaScript makes every new variable global. If you declare the variable with var, however, its scope will be limited to the function in which it’s declared.

In this code, if you take out var the result is that the first player doesn’t get any points. Why? The second time PigPlayer is called, the player object created by the first call is overwritten by the second. After this happens, any time the first player’s takeTurn method is called it updates the second player’s player object’s score field rather than that of the first player, so the second player gets whatever points should have been given to the first.

Exercise : Write a modified version of the code above, with var taken out and additional console.log statements added, to clearly show the error explained in the previous paragraph.

Constructors

As shown in the previous example, you can define a function that returns a new object, something like a constructor in a typical object-oriented language. JavaScript actually provides a special syntax designed for this—designed to create constructor functions, using new and this in a way you might expect to see in a Java program. Here’s our Pig program again, this time using a proper JavaScript constructor. (Notice that takeTurn’s variables turnScore and rollScore have been declared with var to make them local to that function.)

PigPlayer = function (holdAmount) {
    this.holdAmount = holdAmount;
    this.score = 0;

    this.takeTurn = function () {
        var turnScore = 0, rollScore;

        while (turnScore < this.holdAmount) {
            rollScore = Math.floor(Math.random() * 6) + 1;

            if (rollScore > 1) {
                turnScore += rollScore;
            } else {
                turnScore = 0;
                break;
            }
        }

        this.score += turnScore;
    };
};

p = new PigPlayer(15);
q = new PigPlayer(25);

while (p.score < 100 && q.score < 100) {
    p.takeTurn();
    q.takeTurn();
}

if (p.score > q.score) {
    console.log("First player won " + p.score + " to " +
            q.score + ".");
} else if (p.score < q.score) {
    console.log("First player lost " + p.score + " to " +
            q.score + ".");
} else {
    console.log("The two players tied at " + p.score + ".");
}

PigPlayer is called with new when p and q are created. When you call a function with new in JavaScript, a new empty object will be created and assigned to a local variable this inside the function. Within the function you can add fields and methods to this, the new object, and at the end of the function this will be returned automatically (as long as the function doesn’t have a return statement).

This is cool…it makes the code look a lot like what you’d see in a Java program. But what would happen if you forgot to use new when you called the function? You might guess that you’d get an error, since the function tries to add things to this, but this would be undefined, right? Not exactly. If new is omitted no new object is created and assigned to this, but this isn’t undefined, it refers to a global object. So anything added to this in the constructor ends up being a new global variable.

Besides this referring to a global object (so that anything added to this becomes a new global), when new is omitted the function no longer automatically returns this. For the code above, that’s a very good thing, because it means it means omitting new will generate an error message. p (and q as well) ends up being undefined, since PigPlayer doesn’t return anything, which means p.score will generate an error. But it would be possible to write a program in which a missing new doesn’t generate an error—but does interfere with pre-existing global variables. This would be a very bad thing.

Fortunately it’s possible to write a constructor function in a way that makes it work correctly even if new is omitted, as shown here.

PigPlayer = function (holdAmount) {

    // If "new" is omitted when this function is called, "this"
    // will refer to a global object and therefore won't be an
    // instance of PigPlayer.  In that case, replace the bad
    // function call with one that uses "new".
    if (!(this instanceof PigPlayer)) {
        return new PigPlayer(holdAmount);
    }

    this.holdAmount = holdAmount;
    this.score = 0;

    this.takeTurn = function () {
        var turnScore = 0, rollScore;

        while (turnScore < this.holdAmount) {
            rollScore = Math.floor(Math.random() * 6) + 1;

            if (rollScore > 1) {
                turnScore += rollScore;
            } else {
                turnScore = 0;
                break;
            }
        }

        this.score += turnScore;
    };
};

Exercise : Write a modified version of the Pig game code above, without the check for new, with “return this;” at the end of the constructor, and without new in the statements that create new PigPlayer objects. Add output statements to show that now the two players both get each other’s points, so that they always tie.

Exercise : In this example, if the earlier version of the constructor without the check for new is called without new, you end up getting an error message because the constructor returns undefined. Describe a situation where a missing new, and resulting global this, might pollute the global scope without triggering an error.