proteus
Version:
A declarative way of creating objects, properties, and classes in ES5 JavaScript
234 lines (211 loc) • 6.71 kB
JavaScript
var putil = require("proteus/util"),
merge = putil.merge,
mergeAll = putil.mergeAll,
slice = putil.slice,
create = require("proteus/create").create,
// property = require("proteus/properties").property,
hasOwnProp = Object.prototype.hasOwnProperty,
EMPTY = putil.EMPTY
;
/**
* @module Proteus
* @submodule class
* @main Proteus
*/
/**
* Create a Constructor function, optionally setting its prototype to
* the supplied object.
*
* When the Constructor function is called by 'new' it will attempt to
* initialize the instance. First by calling the Constructor's "initialize"
* method, passing the new instance and the arguments object. If that
* returns a value, that will be returned as the new instance. Otherwise,
* it will attempt to apply the instance's "init" method passing all
* arguments as they were passed to the constructor function.
*
* @method makeCtor
* @private
* @param {Object} proto, optional - object to use as the prototype
* @return {Function} the new constructor function
*/
function makeCtor (proto) {
var Ctor = function Proteus () {
var cFn, iFn, inst;
if (arguments[0] !== EMPTY) {
if ((cFn = this.constructor) &&
typeof cFn.initialize === "function" &&
(inst = cFn.initialize(this, arguments))
) {
return inst;
}
if (typeof (iFn = this.init) === "function") {
iFn.apply(this, arguments);
}
}
}
;
if (proto) {
Ctor.prototype = proto;
}
// property(Ctor).hidden().apply(Ctor.prototype, "constructor");
Object.defineProperty(Ctor.prototype, "constructor", {
value: Ctor,
configurable: true,
writable: true
});
return Ctor;
}
/**
* Extend one object with the properties of another.
*
* If the extending object, `xtndr`, has a function named `extended`, it
* will be called with one argument, the object that was extended.
*
* @method extend
* @param {Object} xtnd - object to extend
* @param {Object} rest - one, or more objects to extend with
* @return {Object} the extended object
*/
function extend (xtnd) {
var len = arguments.length,
i = 1,
xtndr
;
for (; i < len; i++) {
xtndr = arguments[i];
mergeAll(xtnd, xtndr);
if (xtndr.extended === "function") {
xtndr.extended(xtnd);
}
}
return xtnd;
}
/**
* Include properties onto the prototype of `self` from another Constructor
* function or a plain object. If the `props` has a property named 'self',
* the properties of 'self' will be merged into 'self' directly.
*
* If the supplying object, 'props', has a function named 'included' it will
* be called with one argument, the 'self' Constructor function that was
* just modified.
*
* @method include
* @param {Function} self - Constructor function
* @param {Function|object} props - either a Constructor function, or an
* object of properties to include
* @return {Function} self
*/
function include (self) {
var hasOwn = hasOwnProp,
proto = self.prototype,
len = arguments.length,
i = 1,
props
;
for (; i < len; i++) {
props = arguments[i];
if (typeof props === "function") {
mergeAll(proto, props.prototype);
}
else {
if (hasOwn.call(props, "self") || hasOwn.call(props, "static")) {
mergeAll(self, props.self || props["static"]);
}
mergeAll(proto, props);
}
if (typeof props.included === "function") {
props.included(self);
}
}
return self;
}
/**
* Derive a new Constructor function from another, optionally adding the
* supplied properties to its prototype.
*
* If the parent Constructor has a function named `inherited` it will
* be called with one argument, the new Constructor function.
*
* @method derive
* @param {Function} parent - a constructor object to derive from
* @param {Object} props, optional - properties to add to the constructors
* prototype
* @param {Object} spec, optional - additional Object.defineProperties object
* @return {Function} a constructor function
*/
function derive (parent, props, spec) {
var Ctor = makeCtor(new parent(EMPTY));
// property(parent.prototype).hidden().apply(Ctor, "__super__");
Object.defineProperty(Ctor, "__super__", { value: parent.prototype });
extend(Ctor, parent);
if (props) {
include(Ctor, props);
}
if (spec) {
Object.defineProperties(Ctor.prototype, spec);
}
if (typeof parent.inherited === "function") {
parent.inherited(Ctor);
}
return Ctor;
}
module.exports = {
derive: derive,
extend: extend,
include: include,
/**
* Base Class
*/
Class: derive(Object, {
self: {
/**
* 'include' scoped to the current Constructor
*
* @method include
* @param {Object} props - properties to include
* @return {Object}
*/
include: function () {
return include.apply(
this,
[this].concat(slice(arguments))
);
},
/**
* 'extend' scoped to the current Constructor
*
* @method extend
* @param {Object} xtnd - object to extend
* @return {Object} the extended object
*/
extend: function (xtnd) {
return extend(xtnd, this);
},
/**
* 'derive' scoped to the current constructor function
*
* @method derive
* @param {Object} props
* @param {Object} spec
* @return {Function}
* @api static
*/
derive: function (props, spec) {
return derive(this, props, spec);
},
/**
* Utility to create an instance with an array of arguments
*
* @method make
* @param {Array} args
* @return {Object} new instance
* @api static
*/
make: function (args) {
var inst = create(this.prototype);
this.apply(inst, Array.isArray(args) ? args : slice(arguments));
return inst;
}
}
})
};