6.3. Classes, Functions and Constructors

In Chapter 2 we mentioned that object-orientation and class inheritance are distinct language design concepts, although many people mistakenly conflate them because popular languages like Java use both. While JavaScript is object-oriented and supports inheritance, it does not have classes, despite the addition of a new class keyword in the ECMAScript 6 standard. However, classes have not been added to JavaScript; the keyword is syntactic sugar for JavaScript’s built-in mechanism of prototype inheritance, in which every object inherits from some prototype object and delegates to its prototype any slot lookup that fails on the object itself.

Unfortunately, the design of this mechanism has led to confusion for newcomers to JavaScript, especially regarding the behavior of the keyword this. We will concern ourselves with three common uses of this. In this section we introduce the first two of these uses, and an associated pitfall. In Section 6.6 we introduce the third use.

Lines 1–8 of Figure 6.6 show a function called Movie. This syntax for defining functions may be unfamiliar, whereas the alternate syntax in lines 9–11 looks comfortably familiar. Nonetheless, we will use the first syntax for two reasons. First, unlike Ruby, functions in JavaScript are true first-class objects—you can pass them around, assign them to variables, and so on. The syntax in line 1 makes it clear that Movie is simply a variable whose value happens to be a function. Second, although it’s not obvious, the variable Movie in line 9 is being declared in JavaScript’s global namespace—hardly beautiful. In general we want to minimize clutter in the global namespace, so we will usually create one or a few objects named by global variables associated with our app, and all of our JavaScript functions will be the values of properties of those objects.

 1let Movie = function(title,year,rating) {
 2    this.title = title;
 3    this.year = year;
 4    this.rating = rating;
 5    this.full_title = function() { // "instance method"
 6        return(this.title + ' (' + this.year + ')');
 7    };
 8};
 9function Movie(title,year,rating) {  // this syntax may look familiar...
10// ...
11}
12// using 'new' makes Movie the new objects' prototype:
13pianist = new Movie('The Pianist', 2002, 'R');
14pianist.full_title;   // => function() {...}
15pianist.full_title(); // => "The Pianist (2002)"
16// BAD: without 'new', 'this' is bound to global object in Movie call!!
17juno = Movie('Juno', 2007, 'PG-13'); // DON'T DO THIS!!
18juno;               // undefined
19juno.title;         // error: 'undefined' has no properties
20juno.full_title();  // error: 'undefined' has no properties
Figure 6.6: Since functions are first-class objects, it is fine for an object to have a property whose value is a function, as full_title is. We will make extensive use of this characteristic. Note the pitfall in lines 14–18.

If we call the Movie function using JavaScript’s new keyword (line 13), the value of this in the function body will be a new JavaScript object that will eventually be returned by the function, similar to Ruby’s self inside an initialize constructor method. In this case, the returned object will have properties title, year, rating, and full_title, the last of which is a property whose value is a function. If line 14 looks like a function call to you, then you’ve been hanging around Ruby too long; since functions are first-class objects in JavaScript, this line just returns the value of full_title, which is the function itself, not the result of calling it! To actually call it, we need to use parentheses, as in line 15. When we make that call, within the body of full_title, this will refer to the object whose property the function is, in this case pianist.

Remember, though, that while these examples look just like calling a class’s constructor and calling an instance method in Ruby, JavaScript has no concept of classes or instance methods. In fact, there is nothing about a particular JavaScript function that makes it a constructor; instead, it’s the use of new when calling the function that makes it a constructor, causing it to create and return a new object. The reason this works is because of JavaScript’s prototype inheritance mechanism, which we don’t discuss further (but see the Elaboration below to learn more). Nonetheless, forgetting this subtle distinction may confuse you when you expect class-like behaviors and don’t get them.

However, a JavaScript misfeature can trip us up here. It is (unfortunately) perfectly legal to call Movie as a plain old function without using the new keyword, as in line 17. If you do this, JavaScript’s behavior is completely different in two horrible, horrible ways. First, in the body of Movie, this will not refer to a brand-new object but instead to the global object, which defines various special constants such as Infinity, NaN, and null, and supplies various other parts of the JavaScript environment. When JavaScript is run in a browser, the global object happens to be a data structure representing the browser window. Therefore, lines 2–5 will be creating and setting new properties of this object—clearly not what we intended, but unfortunately, when this is used in a scope where it would otherwise be undefined, it refers to the global object, a serious design defect in the language. (See Fallacies and Pitfalls and To Learn More if you want to learn about the reasons for this odd behavior, a discussion of which is beyond the scope of this introduction to the language.)

Second, since Movie doesn’t explicitly return anything, its return value (and therefore the value of juno) will be undefined. Whereas a Ruby function returns the value of the last expression in the function by default, a JavaScript function returns undefined unless it has an explicit return statement. (The return in line 6 belongs to the full_title function, not to Movie itself.) Hence, lines 19–20 give errors because we’re trying to reference a property (title) on something that isn’t even an object.

You can avoid this pitfall by rigorously following the widespread JavaScript convention that a function’s name should be capitalized if and only if the function is intended to be called as a constructor using new. Functions that are not “constructor-like” should be given names beginning with lowercase letters.

Self-Check 6.3.1. What is the difference between evaluating square.area and square.area() in the following JavaScript code?

let square = {
    side: 3,
    area: function() {
        return this.side*this.side;
    }
};
6.7
Figure 6.7: A simplified view of the DOM tree corresponding to the RottenPotatoes “list of movies” page with skeletal HTML markup. An open triangle indicates places where we’ve elided the rest of the subtree for brevity. this.document is set to point to the DOM tree’s root when a page is loaded.

square.area() is a function call that in this case will return 9, whereas square.area is an unapplied function object.

Self-Check 6.3.2. Given the code in Self-Check 6.3.1, explain why it’s is incorrect to write s=new square.

square is just an object, not a function, so it cannot be called as a constructor (or at all).

Self-Check 6.3.3. In Ruby, when a method call takes no arguments, the empty parentheses following the method call are optional. Why wouldn’t this work in JavaScript?

Because JavaScript functions are first-class objects, a function name without parentheses would be an expression whose value is the function itself, rather than a call to the function.