At a cliffhanger!
(Actually… doing some magic tricks)
Let's check out this code. →
// an empty object
const hat = {};
// printing out a properties
console.log(hat.toString);
// calling a method
console.log(hat.toString());
hat
, yet? →
[function: toString]
[object object]
const hat = {};
console.log(hat.toString); // a function
console.log(hat.toString()); // returns object
All objects have a link to another object that's called its [[prototype]]
.
[[prototype]]
means the concept, prototype not the actual syntax (confusingly, there are properties and objects in JavaScript that are named prototype
but are not exactly the concept [[prototype]]
)[[prototype]]
objects have [[prototype]]s as well!null
prototype is reached / the last object in the chain: Object.prototype
The top level prototype is Object.prototype:
Object
Object.prototype
Object.prototype
's [[prototype]] is null
Let's do some exploring. →
Object.getPrototypeOf(obj)
obj
…
console.log(
Object.getPrototypeOf({}) == Object.prototype);
console.log(
Object.getPrototypeOf(Object.prototype));
Object.prototype
isn't always an object's direct [[prototype]]; instead, most objects have a [[prototype]] of another object
Function.prototype
Array.prototype
Let's see what happens when we call getPrototypeOf
. →
function f(x) { return x;}
console.log(
Object.getPrototypeOf(f) == Function.prototype);
console.log(
Object.getPrototypeOf([1, 2, 3]) == Array.prototype);
What do you think the [[prototype]] of Array.prototype is?→
console.log(
Object.getPrototypeOf(Array.prototype) == Object.prototype);
Object.prototype
is at the top of the prototype chain (it's the last object checked for properties)toString()
hasOwnProperty()
(we've seen this before!)Object.create
Object.create
- creates a new object with the specified [[prototype]] object and properties
// our "template" object
const protoWerewolf = {
description: 'hairy',
howl: function(thing) {
console.log('The werewolf howls at the ' + thing + '.');
}
};
// make a new werewolf with Object.create
const sadWerewolf = Object.create(protoWerewolf);
sadWerewolf.mood = 'sullen';
sadWerewolf.howl('moon');
(It turns out, for inheritance, it's common to use Object.create(MyObj.prototype) … we'll see why later)
Another way to create an object with a particular prototype is to use a constructor.
new
keyword in front of it
new
this
object is bound to a fresh, empty objectnew
(unless the constructor explicitly returns a different object)In the code below, both sadWerewolf
and partyWerewolf
are instances of Werewolf
. Note that:
this
objectthis
is the object that's returned after calling new Werewolf
function Werewolf(mood) {
this.mood = mood;
}
const sadWerewolf = new Werewolf('sad');
const partyWerewolf = new Werewolf('partying');
console.log(partyWerewolf.mood);
You can think of the above constructor as doing the following when invoked with new…
function Werewolf(mood) {
// this = {}
this.mood = mood;
// return this
}
Let's try adding some more properties to this
.→
All constructors have a property named prototype
.
from Object.prototype
Function.prototype
Werewolf.prototype.howl = function(thing) {
console.log('The werewolf howls at the ' + thing + '.');
}
sadWerewolf.howl('moon');
partyWerewolf.howl('bowl of chips');
When we added a property to the constructor's prototype, something unusual happened! How were the instances of that constructor affected? →
The instances immediately had access to the new property, even though they were instantiated before the prototype was set.
When a property is requested from an object, where are the places that the property is searched for?
Object.prototype
If you add a property directly to an object, it is added to the object itself, not the object's prototype. What's the output of the following code? →
Werewolf.prototype.clothing = 'tattered shirt';
console.log(partyWerewolf.clothing);
partyWerewolf.clothing = 'backwards cap';
console.log(partyWerewolf.clothing);
console.log(sadWerewolf.clothing);
tattered shirt
backwards cap
tattered shirt
Again, when you add a property to an object, that property is added to the object itself…
Let's break down all of the properties of our partyWerewolf
object and determine where they came from →
partyWerewolf properties
=====
from partyWerewolf object
-----
clothing: backwards cap
mood: partying
from Werewolf.prototype
-----
clothing: tattered shirt (masked)
howl: (function)
from Object
-----
toString: (function)
etc.
Why would overriding properties be useful? →
toString()
method →
[mood] werewolf
console.log(partyWerewolf);
Werewolf.prototype.toString = function() {
return this.mood + ' werewolf';
};
console.log(partyWerewolf + '');
A common pattern for implementing inheritance is to: →
Object.create
Using our parent constructor, Werewolf
function Werewolf(mood) {
this.mood = mood;
}
Werewolf.prototype.howl = function(thing) {
console.log('The werewolf howls at the ' + thing + '.');
}
Create a constructor for a space werewolf (!!!) by setting its prototype to a new object who's prototype is Werewolf.prototype
function SpaceWerewolf() {}
SpaceWerewolf.prototype = Object.create(Werewolf.prototype);
This isn't quite complete, though. →
In the previous implementation, there's actually some stuff missing when we create a SpaceWerewolf
. What's missing from the previous implementation that results in incomplete inheritance? →
mood
)?
const w = new SpaceWerewolf();
console.log(mood)
Hm. The constructor, Werewolf
, sets the property, mood
…
super
in Java).call
function SpaceWerewolf(mood) {
Werewolf.call(this, mood);
}
All object's have a property named constructor
. constructor
is the function that was used to create the instance's prototype.
const a = [];
console.log(a.constructor); // [Function: Array]
So we should probably set that on our child constructor's prototype property explicitly so that all objects created from SpaceWerewolf
have that as its constructor.
SpaceWerewolf.prototype.constructor = SpaceWerewolf;
function Werewolf(mood) {
this.mood = mood;
}
Werewolf.prototype.howl = function(thing) {
console.log('The werewolf howls at the ' + thing + '.');
}
function SpaceWerewolf(mood) {
Werewolf.call(this, mood);
}
SpaceWerewolf.prototype = Object.create(Werewolf.prototype);
SpaceWerewolf.prototype.constructor = SpaceWerewolf;
const w = new SpaceWerewolf('in space');
console.log(w.mood);
console.log(w.constructor);
Check out the following example… →
function Monster() {
this.scary = true;
}
Monster.prototype.boo = function() { console.log('Boo!');}
function Werewolf(mood) {
Monster.call(this);
this.mood = mood;
}
Werewolf.prototype = Object.create(Monster.prototype);
Werewolf.prototype.constructor = Werewolf;
Werewolf.prototype.howl = function(thing) {
console.log('The werewolf howls at the ' + thing + '.');
}
What would the output be if the following code were run… →
const sadWerewolf = new Werewolf('sad');
const partyWerewolf = new Werewolf('partying');
partyWerewolf.scary = false;
console.log(sadWerewolf.scary);
console.log(partyWerewolf.scary);
partyWerewolf.boo();
true
false
Boo!
Some notes on the example:
Monster.prototype
as the prototypeconst sadWerewolf = new Werewolf('sad');
sadWerewolf
How do we list every property in an object? →
for (const prop in obj) {
console.log(prop)
}
Let's list every property and value in partyWerewolf
and sadWerewolf
. →
for (const p in partyWerewolf) {
console.log(p + ': ' + partyWerewolf[p]);
}
for (const p in sadWerewolf) {
console.log(p + ': ' + sadWerewolf[p]);
}
What if we only want the properties that were explicitly set on our object, rather than including inherited ones. →
We could use the hasOwnProperty
method that every object inherits from Object.prototype
!
console.log('party\n-----');
for (const p in partyWerewolf) {
if (partyWerewolf.hasOwnProperty(p)) {
console.log(p + ': ' + partyWerewolf[p]);
}
}
console.log('\n');
console.log('sad\n-----');
for (const p in sadWerewolf) {
if (sadWerewolf.hasOwnProperty(p)) {
console.log(p + ': ' + sadWerewolf[p]);
}
}
If you have an object, and you'd like to know what constructor it came from, you can use the instanceof
operator.
What do you think the following code will print out? →
console.log(myCar instanceof Car);
console.log(myCar instanceof Bike);
true
false
(in actuality instance of checks if an object has in its prototype chain the prototype property of a constructor)
There are two ways to set properties automatically when using a constructor. What are they? →
function Thing() {
this.prop1 = 'some value';
}
Thing.prototype.prop2 = 'another value';
There are a few ways to create objects… what are they? →
{}
Object.create
seems to be gaining some traction.
What object is at the top of the prototype chain? →
Object.prototype
is at the top of the prototype chain
What is Object.prototype
's prototype? →
Its prototype is null.
What are some properties that are inherited from Object.prototype
? →
toString, hasPropertyOf
What does the hasOwnProperty
method do? →
return true
if property was set on actual object rather than inherited
What does the instanceof
operator do? →
It determines whether the operand on the left is an instance of the operand on the right.
In ES6, there's yet another way to create a prototype chain of objects, and that's using a familiar construct, classes.
These two bits of code both produce a function called HttpRequest
! →
ES6 class:
class HttpRequest {
}
ES5 constructor:
function HttpRequest() {
}
Both result in the same output when used in the following manner:
const req = new HttpRequest();
console.log(HttpRequest);
console.log(typeof req.constructor);
console.log(req.constructor.name);
ES6 style classes allow for a constructor to be defined as follows:
this
which represents the instance that is created
class HttpRequest {
constructor(method, url) {
this.method = method;
this.url = url;
}
}
The above code is mostly the same as this ES5 function that can be used as a constructor:
function HttpRequest(method, url) {
this.method = method;
this.url = url;
}
We'll see later that subclass constructors must call super before using this
.
In ES5, to add a method to the prototype, we'd have to do something like this:→
function HttpRequest(method, url) {
this.method = method;
this.url = url;
}
HttpRequest.prototype.makeRequest = function() {
return this.method + ' ' + this.url + ' HTTP/1.1';
}
In ES6, we can define methods directly in the class definition, and they will show up in the instances' prototype →
class HttpRequest {
constructor(method, url) {
this.method = method;
this.url = url;
}
makeRequest() {
return this.method + ' ' + this.url + ' HTTP/1.1';
}
}
function
this
, and if the method is called within the context of an instance, then this
refers to the instanceNote that creating these methods in ES6 style classes is actually just adding to the prototype! →
const req = new HttpRequest('GET', 'http://foo.bar/baz');
console.log(req.makeRequest());
console.log(Object.getPrototypeOf(req).makeRequest);
Use extends
to inherit from a class! (read: set up a prototype chain)
class Element {
constructor(name) {
this.name = name;
}
}
class ImgElement extends Element {
// make sure to call super before using this
// within subclass
constructor(url) {
super('img');
this.url = url;
}
}
const img = new ImgElement('http://foo.bar/baz.gif');
console.log(img.name);
console.log(img.url);
In the previous example, super
was used to call the base class constructor. →
super
must be called in your subclass constructor if…this
within your constructorthis
properties the way that the superclass wouldsuper
must be called before using this within a subclassEvery object in JavaScript links to another object called its [[prototype]]. →
Object.create
__proto__
that allows direct access to a [[prototype]], but its use is discouraged)