Created by Carl Olsen / @unstoppableCarl
Who has written something like this before?
var User = function User(name){
this.name = name;
};
User.prototype = {
greet: function(){ return 'Hi my name is ' + this.name; }
};
var steve = new User('Steve');
steve.greet(); // Hi my name is Steve
Then extended it
var LoudUser = function LoudUser(name, volume){
User.prototype.constructor.call(this, name);
this.volume = volume;
};
LoudUser.prototype = {
volume: 1,
greet: function(){
var greeting = User.prototype.greet.call(this);
return greeting.toUpperCase() + '!'.repeat(this.volume);
}
};
var loudLarry = new LoudUser('Larry', 3);
loudLarry.greet(); // HI MY NAME IS LARRY!!!
In JS object types and definitions can change at any time.
Using the instanceof
keyword to make assumptions about an object is unreliable
There are many cases (MDN instanceof docs) where it does not work anyway
Inspect the actual object.
X can do Y
if(obj.connect && obj.sync){
// can connect and sync
}
X has a Y
if(obj.collection){
// has a collection
}
You must inherit the whole class.
“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”‐ Joe Armstrong
creator of Erlang
But ES6 Has the class
keyword…
... Not really an implementation of classical OOP
The ES6 class
implementation still has some problems
Any function can instantiate and return objects. When you do so without a constructor, it is called a factory function.
new
or a constructor.Classes (in ES5 and ES6) do not give you ANYTHING that is not already supplied by factory functions and the prototypal OO built into JS.
When you create a class you are opting into a less powerful, less flexible mechanism than a simple factory.
// player game object example
var movementTrait = {
move: function(distance){ }
};
var solidTrait = {
collide: function(object){ }
};
var player = _.assign({}, movementTrait, solidTrait); // same as Object.assign() or _.extend
AKA functional mixins
// simple example factory
var thingFactory = function(obj, settings){
obj = obj || {};
settings = settings || {};
// compose code
var proto = {
config: { flag: true },
whatever: function() {}
};
_.assign(obj, proto); // same as Object.assign() or _.extend
// @TODO init / constructor code using settings
return obj;
};
var a = thingFactory();
// object instance factory, NOT intended to be composed
// DO NOT USE in other factories to avoid nested dependencies
// I prefix these functions with `make` to denote this
var makeFoo = function(settings){
var obj = {};
obj = thingFactory(obj, settings);
obj = otherFactory(obj, settings);
return obj;
};
var foo = makeFoo({
cacheOrWhatever: true,
someOtherSetting: false,
});
var foo = new Foo();
foo.someFunc(); // behavior needs to be changed
The behavior of foo.someFunc() needs to be changed, but multiple sub-classes depend on the current behavior of Foo.prototype.someFunc()
var makeFoo = function(settings){
var obj = {};
obj = thingFactory(obj, settings);
obj = otherFactory(obj, settings); // adds `someFunc` to obj
return obj;
};
var foo = makeFoo();
foo.someFunc(); // behavior needs to be changed
The behavior of foo.someFunc() needs to be changed, but other objects depend on the current behavior of someFunc() provided by the otherFactory()
var makeFoo = function(settings){
var obj = {};
obj = thingFactory(obj, settings);
// obj = otherFactory(obj, settings);
obj = altFactory(obj, settings); // swap out the factory completely
return obj;
};
var makeFoo = function(settings){
var obj = {};
obj = thingFactory(obj, settings);
obj = otherFactory(obj, settings);
obj = additionalFactory(obj, settings); // add a new factory
return obj;
};
var defaults = {
name: 'my bar',
specials: 'todays specials',
location: {lat: null, lng: null}
};
var members = {
add: function(member) {
this.members[member.name] = member;
},
getMember: function(name) {
return this.members[name];
},
members: {} // new obj per instance
};
var availability = {
close: function(){ /* close bar */},
open: function(){ /* open bar */},
isOpen: fucntion() { /* check if bar is open */ }
}; // handle internal availability status privately
var defaultsStamp = stampit().props({
name: 'The Bar',
specials: 'Vodka with Vodka',
location: { lat: null, lng: null }
});
var myDefaults = defaultsStamp({
name: 'Moes Bar',
location: {
lat: 1.5,
lng: 2.3
}
});
myDefaults.name; // 'Moes Bar'
myDefaults.specials; // 'Vodka with Vodka'
myDefaults.location; // { lat: 1.5, lng: 2.3 }
var d1 = defaultStamp();
var d2 = defaultStamp();
d1.location === d2.location // false
var membershipStamp = stampit({
methods: {
add: function(member) {
this.members[member.name] = member;
},
getMember: function(name) {
return this.members[name];
}
},
props: {
members: {}
}
});
var myMembership = membershipStamp();
myMembership.add({name: 'Homer', status: 'sober'});
myMembership.getMember('Homer'); // {name: 'Homer', status: 'sober'}
var availabilityStamp = stampit().init(function(settings) {
var instance = settings.instance;
var isOpen = false; // private
instance.open = function() {
isOpen = true;
};
instance.close = function() {
isOpen = false;
};
instance.isOpen = function() {
return isOpen;
};
});
var myAvailability = availabilityStamp();
myAvailability.isOpen(); // false
myAvailability.open();
myAvailability.isOpen(); // true
// compose multiple stamps into one
var bar = stampit.compose(defaults, availability, membership);
// you can override references on instantiation
var myBar = bar({name: 'Moes Bar'});
myBar.name; // 'Moes Bar'
myBar.add({name: 'Homer', status: 'sober'});
myBar.open();
myBar.isOpen(); // true
myBar.getMember('Homer'); // {name: 'Homer', status: 'sober'}