P.js is a lightweight layer over javascript's built-in inheritance system that keeps all the good stuff and hides all the crap.
Okay.
// adapted from coffeescript.org
// P.js exposes the `P` variable
var Animal = P(function(animal) {
animal.init = function(name) { this.name = name; };
animal.move = function(meters) {
console.log(this.name+" moved "+meters+"m.");
}
});
var Snake = P(Animal, function(snake, animal) {
snake.move = function() {
console.log("Slithering...");
animal.move.call(this, 5);
};
});
var Horse = P(Animal, function(horse, animal) {
horse.move = function() {
console.log("Galloping...");
animal.move.call(this, 45);
};
});
var sam = Snake("Sammy the Python")
, tom = Horse("Tommy the Palomino")
;
sam.move()
tom.move()
Most class systems for JS let you define classes by passing an object. P.js lets you pass a function instead, which allows you to closure private methods and macros. It's also 559 bytes minified (see make report
).
Unlike some other frameworks out there, Pjs doesn't do any of this:
- interfaces, abstract static factory factories, and other bloat
- use Object.create (it even works in IE < 8!)
- break
instanceof
- hack functions onto
this
at runtime - rely on magical object keys which don't minify (the only special name is
init
)
- inheritable constructors (via the optional
init
method) - closure-based "private" methods (see below)
- easily call
super
on public methods without any dirty hacks - instantiate your objects without calling the constructor (absolutely necessary for inheritance)
- construct objects with variable arguments
You can call P
in a few different ways:
// this defines a class that inherits directly from Object.
P(function(proto, super, class, superclass) {
// define private methods as regular functions that take
// `self` (or `me`, or `it`, or anything you really want)
function myPrivateMethod(self, arg1, arg2) {
// ...
}
proto.init = function() {
myPrivateMethod(this, 1, 2)
};
// you can also return an object from this function, which will
// be merged into the prototype.
return { thing: 3 };
});
// this defines a class that inherits from MySuperclass
P(MySuperclass, function(proto, super, class, superclass) {
proto.init = function() {
// call superclass methods with super.method.call(this, ...)
// or super.method.apply(this, arguments)
super.init.call(this);
};
});
// for shorthand, you can pass an object in lieu of the function argument,
// but you lose the niceness of super and private methods.
P({ init: function(a) { this.thing = a } });
MyClass = P(function(p) { p.init = function(a, b) { console.log("init!", a, b) }; });
// instantiate objects by calling the class
MyClass(1, 2) // => init!, 1, 2
// allocate blank objects with `new`
new MyClass // nothing logged
// to initialize with varargs, use `apply` like any other function.
var argsList = [1, 2];
MyClass.apply(null, argsList) // init!, 1, 2
// or do it the sneaky way
new MyClass(argsList) // init!, 1, 2
// you can use `.open` to reopen a class. This has the same behavior
// as the regular definitions.
// note that _super will still be set to the class's prototype
MyClass = P({ a: 1 });
var myInst = MyClass();
MyClass.open(function(proto) { proto.a = 2 });
myInst.a // => 2
MyClass.open(function(proto, _super) { /* _super is Object.prototype here */ });
// you can use `.mixin` to extend the prototype chain. This is similar
// to .open, but _super works normally, and it doesn't affect existing instances.
MyClass.mixin({ a: 3 });
myInst.a // => 2
MyClass.mixin(function(proto, _super) { proto.foo = function() { return _super.a }; });
MyClass().foo() // => 3
It's super useful! In addition to make
, Pjs uses some build tools written on
Node. With the Node Package Manager that comes with recent versions
of it, just run
npm install
from the root directory of the repo and make
will start working.
Here are the things you can build:
-
make minify
generatesbuild/p.min.js
-
make commonjs
generatesbuild/p.commonjs.js
, which is the same but hasexports.P = P
at the end -
make amd
generatesbuild/p.amd.js
, which is the same but hasdefine(P)
at the end -
make test
runs the test suite using the commonjs version. Requiresmocha
.
(I tested these tasks with GNU make. If someone could verify this all works with BSD make (like on a Mac) that'd be awesome.)