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.
- klass.js: full source, with comments and all (9.6kB).
- 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 = ;
Or, if you have downloaded the file manually (substituting path/to/klass
with the real path to the file you downloaded):
var 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:
;
Now, you can use the API on the Klass
variable in your module.
Browser Global
Add the following snippet to your HTML file:
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;
In this example, we register a car
class. From now on, we can add it to objects or constructors and start making and using car
s 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;
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;
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; Klass;
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;
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" { thisfuel -= 1; console; } { thisfuel = 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; Klass; Klass;
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;
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; Klass;
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:
- 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. - The name of the script itself (a string). This is the same value as the key in the
scripts
object. - The arguments to call the script with (an Array containing data of any type). This will be
apply
d to the script. This element is optional: if you don't provide it, no arguments will be passed to the script.
- The name of the class to which the script we want to call belongs (a string), i.e. the class that holds the
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;
To demonstrate this functionality, let's create a new class: modded-car
which creates a car with some modifications predefined.
Klass;
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;
...you could write this:
Klass; > __Note:__ Because of this 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 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 destroyable this is what you could do: ```javascriptKlass.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 de
s. 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; Klass;
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; var myCar = {};Klass; // 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;Klass; var myCar = {};Klass;Klass; // 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; var myCar = {};Klass; // 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; var myCar = {};Klass; // myCar: {fuel: 100, mods: [], drive: function() {...}, ...} Klass; // 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;Klass; var myCar = {};Klass;Klass; // myCar: {fuel: 100, mods: [], drive: function() {...}, ...} Klass; // 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;Klass; var { ;} Klass; var myCar = ; // 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;
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 asexec
.
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; //falseKlass;Klass; //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.