mirror of
https://github.com/samsonjs/base.git
synced 2026-03-25 09:25:50 +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