Skip to content
/ clank Public

simple js object modeling and inheritance. light mixins

License

Notifications You must be signed in to change notification settings

jquense/clank

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clank

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.

Browser Support

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.

API

require the module;

var ClankObject = require('clank').Object

The Clank module is an object that exports a single property .Object.

Clank.Object

.extend(...spec)

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(...spec)

.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

.create(...properties)

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'

Default Object Composition

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.

Super Calls

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.

About

simple js object modeling and inheritance. light mixins

Resources

License

Stars

Watchers

Forks

Packages

No packages published