first commit

This commit is contained in:
Sami Samhuri 2013-03-16 13:36:29 -07:00
commit 0a98de7da8
3 changed files with 265 additions and 0 deletions

52
Readme.md Normal file
View file

@ -0,0 +1,52 @@
# Base
A simple yet reasonable and useful inheritance system for JavaScript.
The [implementation](/index.js) is just a few dozen lines of code and it is
well commented. Refer to the code for documentation.
Here is some example code illustrating the use of this system in Node:
var Base = require('path/to/base');
// Use `extend` for inheritance.
var Person = Base.extend('Person');
console.log(Person.name) // => Person
// Inherit from other base objects.
var Canadian = Person.extend('Canadian');
// There are `init` methods.
Canadian.prototype.init = function(fields) {
fields = fields || {};
fields.nationality = 'Canadian';
// Calls `Person.init` on `this`
// (short for Person.prototype.init.call(this, fields))
this.callSuper('init', fields);
};
// Instantiate objects with `create`
var p = Canadian.create({ name: 'samsonjs' });
// `init` calls `this.mixin(fields)` by default
// (mixin does exactly what you think it does)
console.log(p.name); // => samsonjs
console.log(p.nationality); // => Canadian
// You can check if an object inhertits from any base object.
console.log(p.like(Canadian)); // => true
console.log(p.like(Person)); // => true
console.log(p.like(Base)); // => true
console.log(p.like(Object)); // => true
var American = Person.extend('American');
console.log(p.like(American)); // => false
# License
Copyright 2013 Sami Samhuri <sami@samhuri.net>
[MIT License](http://sjs.mit-license.org)

174
base.js Normal file
View file

@ -0,0 +1,174 @@
//
// base
// A simple yet reasonable and useful inheritance system for JavaScript.
// https://github.com/samsonjs/base
//
// Copyright 2013 Sami Samhuri <sami@samhuri.net>
// MIT License
// http://sjs.mit-license.org
//
//
// Declare a base object called `Base`. It will get 2 methods and its
// prototype will get 4 methods. That's the entire system.
//
var Base = Object.create(Object.prototype, {
name: {
value: 'Base',
enumerable: true
},
prototype: {
value: {},
enumerable: false,
writable: false,
configurable: false
}
});
// Ship it if we're running in Node (or another CommonJS module system).
if (typeof module.exports !== 'undefined') {
module.exports = Base;
}
//
// To create a new class-ish object you use `Base.extend(name)`, e.g.
//
// var Person = Base.extend('Person');
//
// Inheritance is set up from `Person` to `Base` as well as from `Person.prototype`
// to `Base.prototype`. This makes it easy to extend Person if you want:
//
// var Canadian = Person.extend('Canadian');
//
Base.extend = function(name) {
// Inherit Base methods with Object.create(this, ...)
return Object.create(this, {
// Attach the given name
name: {
value: name || '<anonymous>',
enumerable: true
},
// Set up a link to the super object.
_super: {
value: this
},
// Set up the prototype chain.
prototype: {
value: Object.create(this.prototype),
enumerable: true,
writable: true,
configurable: true
}
});
};
//
// This inheritance system doesn't use the `new` keyword. Instead each base object
// has a `create` method that accepts an optional object of fields for the new
// object.
//
// var p = Person.create({ name: 'samsonjs' });
// p.name == 'samsonjs' // => true
//
Base.create = function(/* ... */) {
var obj = Object.create(this.prototype, {
base: {
value: this,
enumerable: false,
writable: false,
configurable: false
},
constructor: {
value: this.create.bind(this),
enumerable: false,
writable: false,
configurable: false
}
});
obj.init.apply(obj, Array.prototype.slice.call(arguments));
return obj;
};
//
// If you want a prototype method to call a parent object's method you can call it
// directly, just like you normally would in JS:
//
// Canadian.prototype.speak = function(words) {
// Person.prototype.speak.call(this, words.split(' ').join(' eh '));
// };
//
// Or you can use `this.callSuper(method, ...)` like so:
//
// Canadian.prototype.speak = function(words) {
// this.callSuper('speak', words.split(' ').join(' eh '));
// };
//
Base.prototype.callSuper = function(method /*, ... */) {
var base = this.base;
if (!base._super) {
throw new Error(base.name + '._super not found');
}
var superFn = base._super.prototype && base._super.prototype[method];
if (typeof superFn != 'function') {
throw new Error(base.name + '._super.prototype.' + method + ' not found or not a function');
}
var args = Array.prototype.slice.call(arguments, 1);
return superFn.apply(this, args);
};
//
// That's it for the base object methods. Next up are the prototype methods.
//
// First we'll create a method like `instanceof` that walks up the prototype chain
// checking if this object inherits from a given super-object.
//
Base.prototype.like = function(sup) {
var x = this;
while (x && Object.getPrototypeOf(x)) {
if (sup.prototype.isPrototypeOf(x)) {
return true;
}
x = Object.getPrototypeOf(x);
}
return false;
};
//
// Objects get a `mixin` method that merges the properties from the given
// argument into themselves.
//
Base.prototype.mixin = function(fields) {
if (fields.length == 0) return;
var keys = Object.keys(fields),
i = keys.length;
while (i--) {
this[keys[i]] = fields[keys[i]];
}
};
//
// Like many other JavaScript inheritance systems, an `init` method is available
// and called by when you call `create`. You can override it.
//
// Canadian.prototype.init = function(fields) {
// if (fields) {
// fields.nationality = 'Canadian';
// }
// Canadian.callSuper('init', this, fields);
// };
//
// The default `init` method uses `mixin` to initialize properties from the
// optional argument.
//
// var c = Canadian.create({ name: 'samsonjs' });
// c.name == 'samsonjs'; // => true
//
Base.prototype.init = function(fields) {
if (fields) {
this.mixin(fields);
}
};

39
test.js Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env node
var assert = require('assert');
var Base = require('./base.js');
assert(Object.prototype.isPrototypeOf(Base), 'Base inherits from Object');
assert.equal('Base', Base.name);
assert.equal('function', typeof Base.extend);
assert.equal('function', typeof Base.create);
assert.equal('function', typeof Base.prototype.callSuper);
assert.equal('function', typeof Base.prototype.like);
assert.equal('function', typeof Base.prototype.mixin);
assert.equal('function', typeof Base.prototype.init);
var Person = Base.extend('Person');
assert(Base.isPrototypeOf(Person), 'Person inherits from Base');
assert.equal('Person', Person.name);
assert(Base.prototype.isPrototypeOf(Person.prototype), 'Person proto inherits from Base proto');
assert.equal('function', typeof Person.extend);
assert.equal('function', typeof Person.create);
assert.equal('function', typeof Person.prototype.callSuper);
assert.equal('function', typeof Person.prototype.like);
assert.equal('function', typeof Person.prototype.mixin);
assert.equal('function', typeof Person.prototype.init);
var p = Person.create({ name: 'samsonjs' });
assert(p.like(Person), 'p inherits from Person');
assert(p.like(Base), 'p inherits from Base');
assert(p.like(Object), 'p inhertis from Object');
assert.equal('samsonjs', p.name, 'p has the correct "name" property');
Person.prototype.foo = function(x) { return x; };
var Canadian = Person.extend('Canadian');
Canadian.prototype.foo = function(x) {
return this.callSuper('foo', x);
};
assert.equal(42, Canadian.create().foo(42));
console.log('ok');