To create a new object, use the new
keyword followed by a call to a constructor function. Javascript provides the Object()
constructor out-of-the-box:
var toilet = new Object();
Once you have an object, you can set and get properties on it, like this:
toilet.tankCapacity = 4.8;
toilet.tankCapacity;
Curly brackets are a shorthand that lets you create a new object and set a bunch of properties on it. You could instead create the toilet object this way:
var toilet = {tankCapacity: 4.8, brand: 'Sloan'};
In the brackets, you provide a bunch of property names and corresponding values, separated by commas.
Functions are reusable pieces of code. You can declare a function like this. Functions can be stored in a variable or object property, just like numbers and strings:
var flush = function() {
console.log("clearing out contents");
}
A shorthand is available for storing a function in a variable. This is exactly equivalent to assigning the function to the flush
variable:
function flush() {
console.log("clearing out contents");
}
Accessing a function variable with just its name will only print it:
> flush
function flush() {
console.log("clearing out contents");
}
To invoke it, you need to add parentheses after its name.
> flush();
Functions can of course accept arguments. Just include them in the function definition between the parentheses. The arguments will then be available for use by statements within the function.
var fillTank = function(volume) {
console.log("Filling tank with " + volume + " liters of water");
}
Then include the arguments in the parenthesis in the function call:
fillTank(4.8);
Methods are just an object property that happens to be a function. If we want to add a flush()
method to our toilet
object from before, we can just assign a function object to its flush
property:
toilet.flush = function() {
console.log("clearing out contents");
}
Then we just access the flush
property of toilet
to get our function, and follow it with parenthesis to call it:
toilet.flush();
Within an object's methods, you can't access other properties directly. You can see that if we try to access the brand
property by itself, it's not found.
toilet.brand = "Sloan";
toilet.flush = function() {
console.log("clearing out contents of " + brand);
}
> toilet.flush();
ReferenceError: brand is not defined
Instead, use the keyword this
, which is a reference to the object the method was called on, and access brand
as a property of that:
toilet.flush = function() {
console.log("clearing out contents of " + this.brand);
}
> toilet.flush();
clearing out contents of Sloan
Methods are only loosely tied to the objects they're attached to. For example, they have a call()
method that lets you specify any value you want for this
. We can use it to call the flush
method with a completely different object.
> toilet.flush.call( {brand: 'Delta'} )
Obviously setting up each object from scratch each time would be a lot of work. So instead, make a constructor.
A constructor is just another function, but by convention it's named with upper-case. Here's a constructor we could use to make as many toilet objects as we like. It accepts a brand as an argument, then sets up a new object with the given brand and a default tank capacity.
function Toilet(brand) {
this.brand = brand;
this.tankCapacity = 4.8;
}
When a call to the function is preceded by the keyword new
, the this
variable gets set to a new object instance, which will automatically be the function's return value.
> var myToilet = new Toilet("Sloan");
> console.log(myToilet.tankCapacity);
4.8
> console.log(myToilet.brand);
Sloan
The returned instance will also have a reference to the constructor used to create it:
> myToilet.constructor
function Toilet(brand) {
this.brand = brand;
this.tankCapacity = 4.8;
}
The constructor may be just another function, but you probably don't want to call it without the new
keyword! Otherwise, the this
variable may be set to something unexpected...
function ThisTest() {
console.log(this);
}
> new ThisTest()
ThisTest
> ThisTest()
Window {chrome: Object, myToilet: Toilet, eventLog: Array[29], document: #document, v8Intl: Object…}
A prototype is an object on which other objects are based. If you attempt to access a property (or method) on an object, and it can't be found, Javascript will look for the property on that object's prototype.
Here, we add a sanitize()
method to the prototype associated with the Toilet
constructor:
Toilet.prototype.sanitize = function() {
console.log("Sanitizing " + this.brand);
}
That will allow us to call sanitize()
on any instance created via that constructor:
> myToilet.sanitize();
Sanitizing Sloan
When we made the call to myToilet.sanitize()
, Javascript looked first on the myToilet
instance. When it saw there was no sanitize()
method there, it switched to the prototype. What if we defined sanitize()
on myToilet
?
myToilet.sanitize = function() {
console.log("Setting fire to " + this.brand);
}
> myToilet.sanitize();
Setting fire to Sloan
The next time myToilet.sanitize()
is called, its own sanitize()
method is found and executed first, meaning the prototype is never checked. We've overridden sanitize
for myToilet
only.
Javascript has a form of inheritance, too. Each prototype has its own parent prototype. If Javascript checks the prototype for a property or method and it doesn't have it, it'll look on the prototype's prototype, and so on up the chain.
Let's make a FancyToilet
constructor based on the Toilet
constructor. Remember our call
method for functions from before? We can use it with constructors too. Here, we'll use it to call the Toilet
constructor from within the FancyToilet
constructor.
function FancyToilet(brand) {
Toilet.call(this, brand);
}
This creates a new object, initializes it via the Toilet
constructor, and then returns it from the FancyToilet
constructor.
By setting a parent object as a constructor's prototype, objects built with that constructor gain access to all the methods on that object, including the methods on its constructor's prototype, and so on. Let's use an instance of Toilet
as the prototype for FancyToilet
:
FancyToilet.prototype = new Toilet();
But then we can also add our own new methods to this prototype:
FancyToilet.prototype.useBidet = function() {
console.log("Ahh, refreshing!");
}
Now, let's call the constructor:
> var newToilet = new FancyToilet("Toto");
We can call methods from the prototype on the resulting instance:
> newToilet.useBidet();
Or methods from the prototype's prototype:
> newToilet.sanitize();
The call will fail only if no ancestor has the method:
> newToilet.startEngine();
TypeError: Object #<Toilet> has no method 'startEngine'
There's one problem here. The error message says newToilet
is just a Toilet
, shouldn't it be a FancyToilet
? Let's look at its constructor property.
> newToilet.constructor
function Toilet(brand) {
this.brand = brand;
this.tankCapacity = 4.8;
}
Yup, it points to the Toilet
constructor, not FancyToilet
. That's because we set the FancyToilet.prototype
object to a new instance created with the Toilet
constructor. So of course, that object points back to its own constructor, not the FancyToilet
constructor.
The fix is to set the constructor manually within the FancyToilet
constructor.
function FancyToilet(brand) {
Toilet.call(this, brand);
this.constructor = FancyToilet;
}
Now, if we create a new instance, and check its constructor
property, we can see that it points to FancyToilet
.
> var fixedToilet = new FancyToilet("Kohler");
> fixedToilet.constructor
function FancyToilet(brand) {
Toilet.call(this, brand);
this.constructor = FancyToilet;
}
I should mention one last way to create objects. Recent browsers support an Object.create
method that lets you create a new object with a given prototype, without invoking any constructors.
We can create a new object and use our most recently created object as a prototype, like this:
> var createdToilet = Object.create(fixedToilet);
The object has no methods of its own until you define them, so anything you call will be passed to the prototype:
> createdToilet.useBidet();
Ahh, refreshing!
The same is true for properties:
> createdToilet.brand
"Kohler"