A simple prototypal inheritance abstraction with an emphasis on composition. Clank adds some OO inheritance around cobble and works best with it. If you used Backbone or Ember this API should feel familiar, there is no magic here, just a few wrappers around the normal prototypal inheritance. Ultimately the goal would be to depreciate Clank when es6 classes are supported and just use them
with cobble
.
It is highly recommended that you take a look at the tests (./test/class.js
) for a far more extensive demostration of the functionality of Clank.
Works fine in IE8 but does expect certain es5 functions, include es5 shim and sham in IE8 and everything will work fine, with a single caveat: object constructors are assigned a non-enumerable property __META__
, which in IE8 is enumerable, so keep that in mind, when using Object.assign
and other "extend" functions.
require the module;
var ClankObject = require('clank').Object
The Clank module is an object that exports a single property .Object
.
Every Constructor returned from .extend()
also has an extend method.
var Human = Clank.Object.extend({ species: 'Homo Sapien' })
var Jimmy = Human.extend({ name: Jimmy })
new Jimmy
You can also pass in multiple spec objects and make use of Clanks, underlying use of cobble.
var Person = Clank.Object.extend({ species: 'homo sapian', limbs: 4 })
, docOctMixin = { limbs: 8 }
, DocOct = Person.extend(docOct, { gender: 'male' });
var doc = new DocOct()
doc.limbs // => 8
This effectively means that you can specify mixin objects before your prototype spec.
You can also use cobble's descriptors to handle overrides and super calls. See the cobble documentation for more information on descriptors
var EnglishSpeaker = Clank.Object.extend({ greet: function(){ return "hello" } })
var spanishGreeting = { greet: function(){ return "hola" } }
var germanGreeting = { greet: function(){ return "guten morgen" } }
var GermanSpanishAmerican = EnglishSpeaker.extend(spanishGreeting, germanGreeting, {
greet: cobble.reduce(function (target, next) {
return function(){
return target.call(this) + " and " + next.call(this)
}
})
});
var person = new GermanSpanishAmerican()
person.greet() // => "hello and hola and guten morgen"
// Super calls with Constructors
// ---------
var Machine = Clank.Object.extend({
constructor: function(){
Clank.Object.call(this) //call the super constructor in the traditional way
this.greeting = (this.greeting || '') + 'whizz'
}
})
var Toaster = Machine.extend({
// call the super Toaster constructor after the Toaster constructor using a Descriptor
constructor: cobble.before(function(){
this.greeting = 'whorl'
})
})
.reopen
is like .extend
but instead of creating a new Class it alters the current class prototype. Changes made to the prototype will cascade throguh the object heirarchy.
.reopen
has the same signature as .extend
.
var Person = Clank.Object.extend({ species: 'homo sapien'})
, Man = Person.extend({ gender: 'male' });
var man = new Man()
man.gender // => 'male'
man.limbs // => undefined
Man.reopen({
limbs: 4,
gender: 'irrelevant'
})
man.gender // => 'irrelevant'
man.limbs // => 4
Returns an instance of the object with instance properties set to the passed in object, or array of objects. .create
also has the same signature as .extend()
so you can use descriptors as well to compose properties.
var Person = Clank.Object.extend({ greeting: 'guten tag' });
var me = Person.create({ greeting: 'hello'})
, friend = Person.create()
me.hasOwnProperty('greeting') // => true
me.greeting // => 'hello'
friend.hasOwnProperty('greeting') // => false
friend.greeting // => 'guten tag'
In certain cases you may want to create an object Class with a default composition behaviour when extending or creating instances. Clank provides the Constructor.setCompositionStrategy(spec)
method for doing just this. The provided spec
should be an object of Descriptors
that will be mixed into the instance or extension after all user provided compositions.
var Person = Clank.Object.extend({ traits: [ 'biped', 'hair'] });
// in the future traits will be concated together
Person.setCompositionStrategy({
traits: cobble.concat()
})
var Hero = Person.extend({ traits: [ 'brave' ] }) //no need to manually resolve this conflict
var jimmy = new Hero;
jimmy.traits // => [ 'biped', 'hair', 'brave' ]
This can be very helpful for creating some default behaviour in an often used object, but beware, a user can still provide their own compositions, that run before the default strategy. If the user is unaware of the default strategy this can introduce subtle bugs when they try and duplicate a behaviour.
By default the descriptors, such as before()
and after()
, include super methods and properties, so they are composed together along with any mixins.
var Person = Clank.Object.extend({
greet: function(name){
return "hello" + " " + name
}
})
var Pirate = Person.extend({
greet: cobble.compose(function(greeting){
return "ARRRRRRG and " + greeting
})
})
var blackBeard = new Pirate()
blackBeard.greet('Steven') // => "ARRRRRRG and hello Steven"
Clank does have a "proper" super
implementation but you probably don't need it. The majority of super use-cases, can be solved by simple doing Parent.prototype[method].call(this, [args]
or by using a Descriptor. Overuse of the super keyword can lead to bad patterns (like calling _super
in a mixin object), and should be saved for situations when you cannot use a static reference, or you know the super method may change.
For a bunch of reasons, using _super
is going to be less performant than a static reference (either method). Note: super also makes use of Function.caller
, a depreciated js feature, internally.
In a few cases where you need to dynamically reference the super class, super han be accessed by this._super([method]) => returns the method
inside a class method, however, there are a bunch of pitfalls and caveats related to its use, which is why it is underscored. Consult the tests for insight into the limitations of this method.