klasssy

0.5.3 • Public • Published

Klass

v0.5.3

Klass is a simple library that let's you get rid of JavaScript prototype chains.
Works in the browser and on node.js / io.js .

Contents

Installation

Just download one of the following files from this repo.

  1. klass.js: full source, with comments and all (9.6kB).
  2. klass.min.js: minified, ready to be used in production (3.5kB or 1.2kB gzipped).

Or get the code from NPM by typing the following command in a terminal (without the $):

$ npm install klasssy

You can find instruction on how to load Klass in your project below.
When you have installed Klass, head over to the Getting Started section.

Node.js or Browserify

Add the following snippet to your code:

var Klass = require("klasssy");

Or, if you have downloaded the file manually (substituting path/to/klass with the real path to the file you downloaded):

var Klass = require("path/to/klass");

Now, you can use the API on the Klass variable.

Require.js

If you're using require.js in your project, use the following snippet to load Crash:

define(["path/to/klass"], function(Klass) {
    // Your code...
});

Now, you can use the API on the Klass variable in your module.

Browser Global

Add the following snippet to your HTML file:

<script type="text/javascript" src="path/to/klass.js"></script>

Now, you can use the API on the global Klass variable (window.Klass).

Getting Started

Welcome to the Getting Started guide of Klass! Going through this guide shouldn't take more than 15 minutes, while covering all the key concepts and functions of Klass.
Klass is a library for JavaScript, so I assume you have at least a basic understanding of this language. Also, I highly recommend you understand the native prototype and inheritance mechanisms of JavaScript before using Klass.

Happy coding!

Registering a class

Before you can add classes to objects or constructors, you need to register them with Klass. Fortunately, the basics aren't very difficult.

To register a class, you call Klass.register, passing it a name and an object describing your class. Let's try an example:

Klass.register("car", {
  
});

In this example, we register a car class. From now on, we can add it to objects or constructors and start making and using cars in our code. We'll cover how to do that in a bit.

Properties

But, there's still a problem: as you probably noticed, the object we passed to Klass.register is empty. As a result, Klass doesn't know what exactly a car is. It just knows cars are a thing and thus, isn't doing anything when we add this class to an object or constructor. Let's change that.

Fun Fact: Actually, Klass will change the object: it will add a _klasses property. At this point, you shouldn't worry about that, but if you're curious, you can learn more in the Overview.

The first thing we can do with classes, is adding methods and other properties to its prototype. With Klass, this is done in the properties property:

Klass.register("car", {
  properties: {
    drive: function() {
      this.fuel -= 1;
      console.log("Vrooooom!");
    },
    refuel: function() {
      this.fuel = 100;
    }
  }
});

Any object we would now add the car class to, would receive drive and refuel methods.

Initialization

But again, there's a problem: the drive and refuel methods rely on the fuel property, but it doesn't exist yet! Luckily, Klass has us covered: we'll use the init method. Assuming all cars are delivered with a full tank of gas, this would be the init function:

Klass.register("car", {
  properties: {...},
  init: function() {
    this.fuel = 100;
  }
});

The init function will be run every time a new car is created. Its context (this) and its first argument will be the car being created (arguments[0] === this).

Note: The init method won't be added to the new car, it is only called on it.

Dependencies

All the above is pretty basic for any class / prototype implementation. This is even pretty easy to do with JavaScript's native prototypes. But Klass wouldn't exist if it hadn't a few tricks up its sleeve that make it stand out from the crowd.

Enter dependencies. With Klass, you can easily define classes that depend on other classes. Let's demonstrate this by making different models of cars.
We'll make a class for red cars and one for Audi's:

Klass.register("red-car", {
  init: function() {
    this.color = "red";
  },
  depends: "car"
});
 
Klass.register("audi", {
  init: function() {
    this.brand = "Audi";
  },
  depends: "car"
});

Now we have two classes, that each define a different property in their init functions, but both depend on the same base class, car, defined in the depends property.

In the example above, we set depends to a string, but you can also specify an Array. Let's make a class for red Audi's:

Klass.register("red-audi", {
  depends: ["red-car", "audi"]
});

Any object to which we add the red-audi class will have all the properties we defined in the car, red-car and audi classes. It should look something like this:

{
  fuel: 100,
  color: "red",
  brand: "Audi",
  drive: function() {
    this.fuel -= 1;
    console.log("Vrooooom!");
  },
  refuel: function() {
    this.fuel = 100;
  }
}

Destroying

Of course, cars can't only be built, they can also be destroyed. With Klass, all the destroy logic is located in destroy function. Usually, this would do the opposite of the init function.
Assuming the tank of the car gets emptied, the brand information gets lost and the car turns black, let's see how to destroy cars:

Klass.register("car", {
  properties: {...},
  init: function() {
    this.fuel = 100;
  },
  destroy: function() {
    this.fuel = 0;
  }
});
 
Klass.register("red-car", {
  init: function() {
    this.color = "red";
  },
  destroy: function() {
    this.color = "black";
  }
});
 
Klass.register("audi", {
  init: function() {
    this.brand = "Audi";
  },
  destroy: function() {
    this.brand = undefined;
  }
});

Note that in this example, we didn't have to change anything about red-audi; destroying a red Audi will automatically call car's, red-cars's and audi's destroy functions.
Also note that we don't have to remove the properties defined in car's properties property manually in the destroy function: they are automatically removed by Klass.

The last trick: scripts

Don't lose hope, we're nearly there!

Klass has one last - but very powerful - trick up its sleeve: scripts.
First, let's define some scripts on the car class:

  • color: sets the color of the car.
  • brand: sets the brand of the car.

Scripts are just functions that can optionally take some arguments and will be called on objects (the instances of the class). They are stored in an object under the scripts property, mapping them to their names.

Let's see how that works:

Klass.register("car", {
  properties: {...},
  init: function() {...},
  destroy: function() {...},
  scripts: {
    color: function(color) {
      this.color = color;
    },
    brand: function(brand) {
      this.brand = brand;
    }
  }
});

The really neat thing about scripts is that they can be called from dependent classes on initialization, with the exec property. This syntax is pretty tricky to describe in words, so let's just demonstrate with the red-car and audi classes:

Klass.register("red-car", {
  init: function() {...},
  destroy: function() {...},
  exec: [
    ["car", "color", ["red"]]
  ]
});
 
Klass.register("audi", {
  init: function() {...},
  destroy: function() {...},
  exec: [
    ["car", "brand", ["Audi"]]
  ]
});

This is probably pretty abstract, so let's break it down into different parts:

  • The exec property is an Array of Arrays, each describing a script and the arguments to call it with. Let's call these Arrays Definitions for now.
  • Every Definition consists of three parts:
    1. The name of the class to which the script we want to call belongs (a string), i.e. the class that holds the scripts object the script belongs to.
    2. The name of the script itself (a string). This is the same value as the key in the scripts object.
    3. The arguments to call the script with (an Array containing data of any type). This will be applyd to the script. This element is optional: if you don't provide it, no arguments will be passed to the script.

Do you understand all the above? If not, please take some time to do so. There's quite a bit of information about this functionality ahead. If you do, good! Keep on reading!

You may or may not have noticed the weird syntax used to define script executions. Why an Array of Arrays, with three elements defining the script to run? Why not just a simple object mapping commands to Arrays of arguments?
Well, believe it or not, there's a good reason for that: with this weird syntax, it is possible to run scripts multiple times.

Before we dive into the details, let's add another script to the car class: modify, which takes a name as only argument and adds it to a new mods property.

Klass.register("car", {
  ...,
  init: function() {
    this.fuel = 100;
    this.mods = [];
  },
  scripts: {
    ...,
    modify: function(name) {
      this.mods.push(name);
    }
  }
});

To demonstrate this functionality, let's create a new class: modded-car which creates a car with some modifications predefined.

Klass.register("modded-car", {
  depends: "car",
  exec: [
    ["car", "modify", ["Cold air intake"]],
    ["car", "modify", ["High flow exhaust"]],
    ["car", "modify", ["Sway bars"]]
  ]
});

Now every modded-car has the Cold air intake, High flow exhaust and Sway bars modifications, all while inheriting from car.

If your exec entry doesn't pass any arguments, you can just leave out the arguments array, or even use a shorter syntax. Assuming we have defined a addPassenger script in the car class, instead of this:

Klass.register("car-with-passengers", {
  depends: "car",
  exec: [
    ["car", "addPassenger"],
    ["car", "addPassenger"]
  ]
});

...you could write this:

Klass.register("car-with-passengers", {
  depends: "car",
  exec: [
    "car.addPassenger",
    "car.addPassenger"
  ]
});
 
> __Note:__ Because of this, class names cannot contain dots!
 
One last question remains, though: what happens with the things I did with scripts when an object is destroyed? The answer is `teardown`. The `teardown` property of class definition works just the same as the `scripts` property, with only one difference: it will run the specified scripts when the object is being destroyed, instead of initialized. So, if we want to make the above class easily destroyable, this is what you could do:
 
```javascript
Klass.register("car", {
  ...,
  init: function() {
    this.fuel = 100;
    this.mods = [];
  },
  scripts: {
    ...,
    modify: function(name) {
      this.mods.push(name);
    },
    demodify: function(name) {
      var index = this.mods.indexOf(name);
      
      if(index > -1) {
        this.mods.splice(index, 1);
      }
    }
  }
});
 
Klass.register("modded-car", {
  depends: "car",
  exec: [
    ["car", "modify", ["Cold air intake"]],
    ["car", "modify", ["High flow exhaust"]],
    ["car", "modify", ["Sway bars"]]
  ],
  teardown: [
    ["car", "demodify", "Cold air intake"],
    ["car", "demodify", "High flow exhaust"],
    ["car", "demodify", "Sway bars"]
  ]
});

In the example above, we define a new script on the car class, called demodify, which removes modifications from the mods property, and we added a teardown property on the modded-car class, which makes sure all the modifications it added during initialization are removed during teardown.

But, you probably noticed how atrociously verbose and repetitive this is: twice the same array, except for a few des. That's why there is one last property, teardownExec, which, when set to true, will execute the scripts in the exec property, with the same arguments, but with a slightly different name, during teardown. That name is the original name, prefixed with teardown_ (mind the underscore!). Let's change the above example to save some space:

Klass.register("car", {
  ...,
  init: function() {
    this.fuel = 100;
    this.mods = [];
  },
  scripts: {
    ...,
    modify: function(name) {
      this.mods.push(name);
    },
    teardown_modify: function(name) {
      var index = this.mods.indexOf(name);
      
      if(index > -1) {
        this.mods.splice(index, 1);
      }
    }
  }
});
 
Klass.register("modded-car", {
  depends: "car",
  exec: [
    ["car.modify", "Cold air intake"],
    ["car.modify", "High flow exhaust"],
    ["car.modify", "Sway bars"]
  ],
  teardownExec: true
});

So all we did was change the name of the demodify script to teardown_modify and replace the teardown scripts with teardownExec: true. This will do exactly the same as the first example.

Adding and removing classes

Now that we know how to register classes with Klass, let's get to the juicy bits: actually using them. To add a class to an object, just use Klass.add:

Klass.register("car", {...});
 
var myCar = {};
Klass.add(myCar, "car");
 
// myCar: {fuel: 100, mods: [], drive: function() {...}, ...}

This will merge the properties defined in the car class with myCar, call the init function of the car class on myCar and execute any scripts defined in the exec property of the car class. You can also add multiple classes to the same object:

Klass.register("red-car", {...});
Klass.register("audi", {...});
 
var myCar = {};
Klass.add(myCar, "red-car");
Klass.add(myCar, "audi");
 
// myCar: {fuel: 100, mods: [], color: "red", brand: "Audi", drive: function() {...}, ...}

One last thing we can do with Klass.add is passing arguments, by passing them as an Array to Klass.add's third argument. This Array will be applied to the class's init function.

Klass.register("colored-car", {
  init: function(color) {
    this.color = color;
  }
});
 
var myCar = {};
Klass.add(myCar, "colored-car", ["blue"]);
 
// myCar: {fuel: 100, mods: [], color: "blue", drive: function() {...}, ...}

Removing classes from an object is just as easy with the Klass.remove method. This method takes the same arguments as Klass.add, but removes the class instead of adding it. Arguments will be passed to the destroy function.

Klass.register("car", {});
 
var myCar = {};
Klass.add(myCar, "car");
 
// myCar: {fuel: 100, mods: [], drive: function() {...}, ...}
 
Klass.remove(myCar, "car");
 
// myCar: {}

Easy, isn't it? Yes, but not very convenient when you want to destroy an object and remove all of its classes. Fear not, there is a remedy: Klass.destroy. This method will remove all the classes from the specified object. Again, you can specify arguments to pass to the classes' destroy functions. If you don't specify arguments, those passed to Klass.add will be used.

Klass.register("red-car", {});
Klass.register("audi", {});
 
var myCar = {};
Klass.add(myCar, "red-car");
Klass.add(myCar, "audi");
 
// myCar: {fuel: 100, mods: [], drive: function() {...}, ...}
 
Klass.destroy(myCar);
 
// myCar: {}

Even easier, isn't it?

Constructors

To assure compatibility with other libraries, third-party code or old code, Klass can also work with constructors.
Making constructors with Klass is easy: you use the same functions as detailed above, with the same arguments. But you pass in a constructor function instead of an object. The only requirement for the constructor is that it calls Klass(this, arguments). This is needed to enable initialization steps. Without it, properties will be added to the prototype, but the init functions and any scripts in exec won't be called.

Klass.register("red-car", {...});
Klass.register("audi", {...});
 
var CoolCar = function() {
  Klass(this, arguments);
}
 
Klass.add(CoolCar, ["red-car", "audi"]);
 
var myCar = new CoolCar();
 
// myCar: {fuel: 100, mods: [], color: "red", brand: "Audi", drive: function() {...}, ...}

Now, you can not only make constructors with Klass, you can also use them as class definitions. First, if you pass a function instead of an object to Klass.register, like so:

Klass.register("car", function() {
  return {
    properties: {...},
    init: function() {...},
    ...
  }
});

the function is expected to return a class definition. But if you pass a constructor function, and true as the third argument, Klass will deconstruct the constructor like so:

  • The constructor function itself will be used as init.
  • The constructor's prototype will be used as properties.
  • All the other properties of the constructor will be used just like a regular object, e.g. constructor.exec will be used as exec.

Checking for a class

On to Klass's very last feature: Klass.has. Give it an object and a name and it returns a boolean indicating whether or not the klass has been added to the object.

Klass.has(myCar, "car"); //false
Klass.add(myCar, "car");
Klass.has(myCar, "car"); //true

And that is it! In this guide we covered all functionality of Klass, except for some details and some arguments that allow you to control Klass's behaviour even more. If you want to learn all the nifty details, take a look at the API documentation.

Happy coding!

Contributing

All contributions are very welcome!
Typos, bug fixes, code cleanup, documentation, tests, a website, you name it!

Questions and feature requests belong in the issue tracker, with the right tags.

Overview

Overview coming soon

API

API reference coming soon

License

The MIT License (MIT)

Copyright (c) 2015 Tuur Dutoit

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Dependents (0)

Package Sidebar

Install

npm i klasssy

Weekly Downloads

2

Version

0.5.3

License

MIT

Last publish

Collaborators

  • tuur