mirror of
https://github.com/samsonjs/base.git
synced 2026-04-26 14:57:42 +00:00
first commit
This commit is contained in:
commit
0a98de7da8
3 changed files with 265 additions and 0 deletions
52
Readme.md
Normal file
52
Readme.md
Normal 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
174
base.js
Normal 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
39
test.js
Executable 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');
|
||||||
Loading…
Reference in a new issue