From 0a98de7da86d103ba830be24a6bc69d042c46acc Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 16 Mar 2013 13:36:29 -0700 Subject: [PATCH] first commit --- Readme.md | 52 ++++++++++++++++ base.js | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.js | 39 ++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 Readme.md create mode 100644 base.js create mode 100755 test.js diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..a40d18d --- /dev/null +++ b/Readme.md @@ -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 + +[MIT License](http://sjs.mit-license.org) diff --git a/base.js b/base.js new file mode 100644 index 0000000..ff30de0 --- /dev/null +++ b/base.js @@ -0,0 +1,174 @@ +// +// base +// A simple yet reasonable and useful inheritance system for JavaScript. +// https://github.com/samsonjs/base +// +// Copyright 2013 Sami Samhuri +// 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 || '', + 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); + } +}; diff --git a/test.js b/test.js new file mode 100755 index 0000000..f103e1f --- /dev/null +++ b/test.js @@ -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');