This sort of object class definition is new to JavaScript with ES-2015. It simplifies defining classes over previous methods and brings JavaScript class definitions closer to the syntax in other languages. Under the hood, JavaScript classes still use prototype-based inheritance, but with a simpler syntax, and the coder doesn't even have to think about the object prototype.
We can reliably determine whether an object is a note with the instanceof operator:
$ node
> const Note = require('./Note');
> typeof Note
'function'
> const aNote = new Note('foo', 'The Rain In Spain', 'Falls mainly on the plain');
> var notNote = {}
> notNote instanceof Note
false
> aNote instanceof Note
true
> typeof aNote
'object'
This shows us the clearest method to identify an object is with the instanceof operator. The typeof operator informs us Note is a function (because of the prototype-based inheritance behind the scenes), and that an instance of the Note class is an object. With instance of, we can easily determine whether an object is an instance of a given class.
With the Note class, we have used Symbol instances to provide a small measure of data hiding. JavaScript classes don't provide a data-hiding mechanism—you can't label a field private as you can in Java, for example. It's useful to know how to hide implementation details. This is an important attribute of object-oriented programming, because it's useful to have the freedom to change the implementation at will. And there's the issue of controlling which code can manipulate the object's fields.
First, we declared getter and setter functions to provide access to the values. We went over normal getter/setter usage in Chapter 4, HTTP Servers and Clients.
Access to a getter-based field is by using the name of the property, and not by calling a function - aNote.title and not aNote.title(). It looks like you're accessing an object property by assigning a value or accessing the value. In actuality, the function defined in the class is executed on every access. You can define a read-only property by only implementing a getter, and no setter, as we did with the key field.
There are significant differences between the preceding and simply defining anonymous objects:
{
key: 'foo', title: 'The Rain in Spain',
body: 'Falls mainly on the plain'
}
We write code like that in JavaScript all the time. It's easy, it's quick, and it's a very fluid way to share data between functions. But there's no measure of hiding implementation details, and no clear identification of object type.
In the Note class, we could have used this constructor method:
class Note {
constructor(key, title, body) {
this.key = key;
this.title = title;
this.body = body;
}
}
That's effectively the same as the anonymous object, in that no details have been hidden and no control is implemented in terms of which code can do what to object instances. The only advantage over an anonymous object is using the instanceof operator to identify object instances.
The method we chose uses the Symbol class, which is also new with ES-2015. A Symbol is an opaque object with two main use cases:
- Generating unique keys to use as property fields—as in the previous Note class
- Symbolic identifiers that you can use for concepts like COLOR_RED
You define a Symbol through a factory method that generates Symbol instances:
> let symfoo = Symbol('foo')
Each time you invoke the Symbol factory method, a new and unique instance is created. For example, Symbol('foo') === Symbol('foo') is false, as is symfoo === Symbol('foo'), because a new instance is created on each side of the equality operator. However, symfoo === symfoo is true, because they are the same instance.
What this means in practice is that if we try a direct approach to access a field, it fails:
> aNote[Symbol('title')]
undefined
Remember that each time we use the Symbol factory method we get a new instance. The new instance of Symbol('title') is not the same instance used within the Note.js module.
The bottom line is that using Symbol objects for the fields provides a small measure of implementation hiding.