Jordan Kasper
A way of representing things as collections of code with data and actions, including mechanisms for reusing code among related collections.
"Abstraction is the stripping away of details so that you can see the essence." (Douglas Hofstatder)
Creating an interface for your object model and abstracting the implementation of the actions of that model.
Can you explain the relationship between two objects "A" and "B" by: "A is a B" within the code structure provided in the language?
Literally: "many forms". The ability for a member of a collection to have different forms within the inheritance chain without changing the interface exposed outside the object.
// Functional!
function addTo(x, y) {
return x + y;
}
// Not so much...
var x = 1;
function addTo(y) {
return x + y;
}
JavaScript is a multi-paradigm language.
var name = "Jordan";
name.length; /* 6 */
name.length.toFixed(1); /* 6.0 */
name.toUpperCase(); /* "JORDAN" */
function foo() {
return "bar";
}
/* Get the name and content of our function */
console.log(foo.name); /* "foo" */
console.log(foo.toString()); /* "function foo() { return \"bar\" }" */
FYI, while undefined
is clearly not an object,
null
is: typeof null === "object"
Using the "curly brace" syntax (object literals)...
var literalDog = {
name: "Vincent",
speak: function() {
return this.name + " says woof";
}
};
which is equivalent to...
var literalDog = new Object();
literalDog.name = "Vincent";
literalDog.speak = function() {
return this.name + " says woof";
};
var literalDog = {
name: "Vincent",
speak: function() {
return this.name + " says woof";
}
};
literalDog.speak(); /* "Vincent says woof" */
this
inside an object method refers to the current context.
Context is typically the variable before the "." when called:
this === literalDog
inside speak()
function Dog(name) {
if (name) { this.name = name; }
this.speak = function() {
return this.name + " says woof";
};
}
var v = new Dog("Vincent");
v.speak(); /* "Vincent says woof" */
var b = new Dog("Brian");
b.speak(); /* "Brian says woof" */
function Dog(name) { /* ... */ }
console.log(Dog.prototype);
{
constructor: function Dog(name) {
if (name) { this.name = name; }
this.speak = function() {
return this.name + " says woof";
};
},
__proto__: Object { /* ... */ }
}
Notice how our name
and speak
members are NOT in our prototype!
var v = new Dog("Vincent");
JavaScript is actually taking 3 distinct actions:
__proto__
property to point to Dog.prototype
;constructor
method on the new object.In fact, we can do these actions on our own:
var v = Object.create(Dog.prototype);
v.constructor("Vincent");
v.speak(); /* "Vincent says woof" */
Note that Object.create()
does not exist in IE < 9, but you can use a shim.
What about name
and speak
not being on the prototype?
We probably want them to be...
function Dog(name) {
if (name) { this.name = name; }
}
Dog.prototype.name = "Bubbles";
Dog.prototype.speak = function() {
return this.name + " says woof";
};
var b = new Dog(); /* no name passed in! */
b.speak(); /* "Bubbles says woof" */
console.log(Dog.prototype);
{
constructor: function Dog(name) {
if (name) { this.name = name; }
},
name: "Bubbles",
speak: function() {
return this.name + " says woof";
},
__proto__: Object { /* ... */ }
}
(That's pronounced "dunder proto", as in: "double underscore proto")
var v = new Dog("Vincent");
v.__proto__ === Dog.prototype; /* true */
Object.getPrototypeOf( v ) === Dog.prototype /* true */
__proto__
has existed for a long time, and in ES6/ES2015 it is deprecated
public, private, privileged
Anything attached to the prototype
:
Dog.prototype.name = "Bubbles"; /* public! */
Dog.prototype.speak = function() { /* ... */ }; /* public! */
Anything on this
or a reference to the object instance:
function Dog(name) {
if (name) { this.name = name; }
this.fur = "black"; /* public! */
}
var v = new Dog("Vincent");
v.human = "Jordan"; /* public! */
And privileged methods
Variables declared inside a function
block only exist while that function's execution context exists.
function Dog(name) {
if (name) { this.name = name; }
/* notice that we don't use: this.alive */
var alive = true; /* private */
}
var v = new Dog("Vincent");
v.alive; /* undefined */
If we want access to a private variable we have to create a "privileged" method...
function Dog(name) {
if (name) { this.name = name; }
/* notice that we don't use: this.alive */
var alive = true; /* private */
this.isAlive = function() { return alive; }; /* privileged */
this.die = function() { alive = false; }; /* privileged */
}
Access to the alive
variable from isAlive()
and die()
is possible
because functions in JavaScript are closures.
If we want access to a private variable we have to create a "privileged" method...
function Dog(name) {
if (name) { this.name = name; }
/* notice that we don't use: this.alive */
var alive = true; /* private */
this.isAlive = function() { return alive; }; /* privileged */
this.die = function() { alive = false; }; /* privileged */
}
function Dog(name) {
if (name) { this.name = name; }
/* notice that we don't use: this.alive */
var alive = true; /* private */
this.isAlive = function() { return alive; }; /* privileged */
this.die = function() { alive = false; }; /* privileged */
}
var v = new Dog("Vincent");
v.isAlive(); /* true */
v.die(); /* sets private "alive" var to false */
v.isAlive(); /* false */
v.alive; /* undefined */
function Dog(name) {
if (name) { this.name = name; }
/* notice that we don't use: this.alive */
var alive = true; /* private */
this.isAlive = function() { return alive; }; /* privileged */
this.die = function() { alive = false; }; /* privileged */
}
Dog.prototype.kill = function() { this.alive = false; }; /* NOT privileged */
var v = new Dog("Vincent");
v.kill(); /* sets this.alive to false */
v.isAlive(); /* true */
Static members are set directly on our Dog constructor function.
function Dog(name) {
if (name) { this.name = name; }
}
Dog.prototype.name = "Bubbles"; /* public! */
Dog.prototype.speak = function() { /* ... */ }; /* public! */
Dog.GENUS = "Canis"; /* static property */
Dog.mergeBreeds = function(a, b) { /* static method */
return ("Breeding " + a + " and " + b);
};
Dog.GENUS = "Canis"; /* static property */
Dog.mergeBreeds = function(a, b) { /* static method */
return ("Breeding " + a + " and " + b);
};
console.log(Dog.GENUS); /* Canis */
Dog.mergeBreeds(v.breed, b.breed);
Be careful when using static methods,
you cannot access the instance (this
) inside them!
function Dog(name) {
if (name) { this.name = name; }
}
Dog.getName = function() {
return this.name; /* what is "this" pointing to? */
};
Dog.getName(); /* what does the method return? */
var v = new Dog("Vincent");
v.getName(); // will throw an Error!
function Animal() {
var alive = true;
this.isAlive = function() { return alive; };
this.die = function() { alive = false; };
}
function Dog(name) {
/* call to parent constructor */
Animal.apply(this);
if (name) { this.name = name; }
}
Dog.prototype = Object.create(Animal.prototype); /* the magic happens */
Dog.prototype.constructor = Dog; /* on these two lines */
Dog.prototype.name = "Bubbles"; /* public! */
Dog.prototype.speak = function() { /* ... */ }; /* public! */
var v = new Dog("Vincent");
console.log(v);
{
name: "Vincent",
__proto__: Dog {
constructor: function Dog(name) { /* ... */ },
name: "Bubbles",
speak: function () { /* ... */ },
__proto__: Animal {
constructor: function Animal() { /* ... */ },
__proto__: Object { /* ... */ }
}
}
}
var v = new Dog("Vincent");
(v instanceof Dog); /* true */
(v instanceof Animal); /* true */
(v instanceof Object); /* true */
Dog.prototype.isPrototypeOf( v ); /* true */
function Animal(age) {
if (age) { this.age = age; }
/* ... */
}
Animal.prototype.age = 1;
Animal.prototype.getAge = function() { return this.age; };
function Dog(name, age) {
Animal.apply(this, [age]);
/* ... */
}
function Animal(age) {
if (age) { this.age = age; }
/* ... */
}
Animal.prototype.age = 1;
Animal.prototype.getAge = function() { return this.age; };
Dog.prototype.getAge = function() {
/* call the parent method first */
var age = Animal.prototype.getAge.apply(this);
return (age * 7);
};
var v = new Dog("Vincent", 11);
v.getAge(); /* 77 */
var Domesticated = {
happiness: 1,
lickFace: function(count) {
this.happiness += count;
}
};
We need to copy our properties and methods
from the mixin to the desired prototype...
Use a helper method!!
// jQuery ($), Underscore (_), or Angular (ng)
$.extend(target, obj1, obj2, /* ... */ );
// Prototype.js
Object.extend(target, obj);
// Backbone
Model.extend({ /* ... */ });
// Node
util.inherits(target, obj);
// Dojo
lang.extend(Class, obj);
function Animal(age) { /* ... */ }
function Dog(name, age) { /* ... */ }
var Domesticated = {
happiness: 1,
lickFace: function(count) {
this.happiness += count;
}
};
$.extend(Dog.prototype, Domesticated);
var v = new Dog("Vincent", 10);
console.log( v.happiness ); // 1
v.lickFace( 5 );
console.log( v.happiness ); // 6
class Dog extends Animal {
constructor(name, age) {
super(age); /* Call parent method of same name */
if (name) { this.name = name; }
}
/* Note the shorthand function definition */
speak() { return this.name + " says woof"; }
getAge() { return (super() * 7); }
/* Getters & Setters are actually in ES5! */
get name() { return this._name; }
set name(value) { this._name = value; }
}
/* for data members, we still have to do this... */
Dog.prototype._name = "Bubbles";
This looks nice, but it's just syntactic sugar!
In my opinion, this will just lead to more confusion...
There are plenty of good solutions out there...
Jordan Kasper