traditional constructors
// Create an object with a specific prototype
let protoRabbit = {
speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`)
}
}
let killerRabbit = Object.create(protoRabbit)
killerRabbit.type = "killer"
killerRabbit.speak("SKREEEE!")
// The killer rabbit says 'SKREEEE!'
Prototypes can be thought of as similar to classes in other languages. They're
useful for defining properties and methods which are passed on to instances.
Properties that differ on a per instance level, such as the above rabbit's type
value, need to be stored in the objects directly. In javascript constructor
functions are used to create an instance of a given prototype and set it's
default values. The new keywork makes defining these type of functions easier.
If you put the new keywork in front of a function call, the function is
treated as a constructor. This means that an object with the right prototype is
automatically created, bound to this in the function, and returned at the end
of the function. By convention, the names of constructors are capitalized so
they can quickly be distinguished from other functions.
function Rabbit(type) {
this.type = type
}
Rabbit.prototype.speak = function(line) {
console.log(`The ${this.type} rabbit says '${line}'`)
}
let weirdRabbit = new Rabbit("weird")
One quirk is that the prototype associated with a contructor is actually
different a property prototype of the constructor. The constructor itself will
have the prototype of Function.prototype.
console.log(Object.getPrototypeOf(Rabbit) ==
Function.prototype)
// true
console.log(Object.getPrototypeOf(weirdRabbit) ==
Rabbit.prototype)
// true
ES6 class notation
class Rabbit {
constructor(type) {
this.type = type;
}
speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`)
}
}
let killerRabbit = new Rabbit("killer")
let blackRabbit = new Rabbit("black")
Any number of methods may be written inside the declaration's braces. The one
named constructor is treated specially. It provides the actual constructor
function, which will be bound to the name Rabbit. The others are packaged into
that constructor's prototype. Class declarations currently allow only methods
(properties that hold functions) to be added to the prototype. This makes it
inconvenient to add non-function properties. You can of course add them by
manipulating the prototype after you've defined the class.
overriding derived properties
When you add a property to an object it will be added to the object itself regardless of if it was present in the prototype.
getters, setters, and statics
Interfaces often consist mostly of methods, but it's okay to include properties
that hold non-function values. It is not even necessary for such an object to
computer and store such a property in the instance. Even properties that are
accessed directly may hide a method call. Such methods are called getters, and
the are defined by writing get in front of the method name in an object
expression or class declaration.
let varyingSize = {
get size() {
return Math.floor(Math.random() * 100)
},
}
console.log(varyingSize.size)
console.log(varyingSize.size)
A setter allows doing something similar when the property is written into and
static can be used to store a method in the constructor which in the below
example is used to create an "alternative" constructor:
class Temperature {
constructor(celsius) {
this.celsius = celsius
}
get fahrenheit() {
return this.celsius * 1.8 + 32
}
set fahrenheit(value) {
this.celsius = (value - 32) / 1.8
}
// Allow creating a temperature from fahrenheit.
static fromFahrenheit(value) {
return new Temperature((value - 32) / 1.8)
}
}
let temp = new Temperature(22)
console.log(temp.fahrenheit) // 71.6
temp.fahrenheit = 86
console.log(temp.celsius) // 30
let ftemp = Temperature.fromFahrenheit(22)
console.log(ftemp.fahrenheit) // 22
inheritance
The use of the word extends indicates that this class shouldn't be directly
based on the default Object prototype, but on some other class (the super
class Matrix shown in js looping). To initialize a SymentrixMatrix
instance, the constructor calls its super-class's constructor through the
super keyword.
// SymentrixMatrix is a Matrix that is always symmetrical. Values stored at x,y
// are always the same as values at y,x.
class SymentrixMatrix extends Matrix {
constructor(size, element = (x, y) => undefined) {
super(size, size, (x, y) => {
// To ensure the matrix is symmetrical, the constructor wraps the content
// method to swap the coords below the diagonal.
if (x < y) return element(y, x)
else return element(x, y)
})
}
set(x, y, value) {
// We're redefining set, but using the super-class's set method in the new
// one by calling super.set().
super.set(x,y,value)
if (x != y) {
super.set(y, x, value)
}
}
}
let symmetrical = new SymentrixMatrix(5, (x,y) => `${x},${y}`)
console.log(symmetrical.get(2,3))
instanceof
This operator returns true/false if an object was derived from a particular class.
console.log([1] instanceof Array) // true