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.”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.
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'}