UNPKG

jclass

Version:

Advanced but lightweight and fast Javascript inheritance model providing class members and prototype conversion.

349 lines (271 loc) 9.15 kB
/** * jclass v1.1.9 * https://github.com/riga/jclass * * Marcel Rieger, 2015 * MIT licensed, http://www.opensource.org/licenses/mit-license */ (function(factory) { /** * Make jclass available in any context. */ if (typeof(define) == "function" && define.amd) { // AMD define([], factory); } else if (typeof(exports) == "object") { // CommonJS exports = factory(); if (typeof(module) == "object") { // NodeJS module.exports = exports; } } else if (window) { // Browser window.JClass = factory(); } else if (typeof(console) == "object" && console.error instanceof Function) { // error case console.error("cannot determine environment"); } })(function() { /** * Helper functions. */ /** * Checks whether a passed object is a function. * * @param obj - The object to check. * @returns {boolean} */ var isFn = function(obj) { return obj instanceof Function; }; /** * Extends a target object by one or more source objects with shallow key comparisons. Note that * the extension is done in-place. * * @param {object} target - The target object to extend. * @param {...object} source - Source objects. * @returns {object} The extended object. */ var extend = function(target) { var sources = Array.prototype.slice.call(arguments, 1); // loop through all sources for (var i in sources) { var source = sources[i]; // object check if (typeof(source) != "object") { continue; } // loop through all source attributes for (var key in source) { target[key] = source[key]; } } return target; }; /** * Default options. */ var defaultOptions = { // internal object for indicating that class objects don't have a class object themselves, // may not be used by users _isClassObject: false }; /** * Flags. */ // flag to distinguish between prototype and class instantiation var initializing = false; /** * Base class definition. */ // empty BaseClass implementation var BaseClass = function(){}; // add the _subClasses entry BaseClass._subClasses = []; // empty init method BaseClass.prototype.init = function(){}; /** * Extend mechanism. Returns a derived class. * * @param {object} instanceMembers - Members that will be owned by instances. * @param {object} classMembers - Members that will be owned by the class itself. * @returns {JClass} */ BaseClass._extend = function(instanceMembers, classMembers, options) { // default arguments if (instanceMembers === undefined) { instanceMembers = {}; } if (classMembers === undefined) { classMembers = {}; } if (options === undefined) { options = {}; } // mixin default options options = extend({}, defaultOptions, options); // sub class dummy constructor var JClass = function() { // nothing happens here when we are initializing if (initializing) { return; } // store a reference to the class itself this._class = JClass; // all construction is actually done in the init method if (this.init instanceof Function) { this.init.apply(this, arguments); } }; // alias for readability var SuperClass = this; // create an instance of the super class via new // the flag sandwich prevents a call to the init method initializing = true; var prototype = new SuperClass(); initializing = false; // get the prototype of the super class var superPrototype = SuperClass.prototype; // the instance of the super class is our new prototype JClass.prototype = prototype; // enforce the constructor to be what we expect // calls to the constructor will invoke the init method (see above) JClass.prototype.constructor = JClass; // store a reference to the super class JClass._superClass = SuperClass; // store references to all extending classes JClass._subClasses = []; SuperClass._subClasses.push(JClass); // make this class extendable as well JClass._extend = SuperClass._extend; // _extends returns true if the class itself extended "target" // in any hierarchy, e.g. every class extends "JClass" itself JClass._extends = function(target) { // this function operates recursive, so stop when the super class is our BaseClass if (this._superClass == BaseClass) { return false; } // success case if (target == this._superClass || target == BaseClass) { return true; } // continue with the next super class return this._superClass._extends(target); }; // propagate instance members directly to the created protoype, // the member is either a normal member or a descriptor for (var key in instanceMembers) { var property = Object.getOwnPropertyDescriptor(instanceMembers, key); var member = property.value; // descriptor flag set? if (member !== null && typeof(member) == "object" && member.descriptor) { Object.defineProperty(prototype, key, member); // getter/setter syntax } else if (!("value" in property) && ("set" in property || "get" in property)) { Object.defineProperty(prototype, key, property); // normal member, simple assignment } else { prototype[key] = member; // if both member and the super member are distinct functions // add the super member to the member as "_super" var superMember = superPrototype[key]; if (isFn(member) && isFn(superMember) && member !== superMember) { member._super = superMember; } } } // propagate class members to the _members object if (!options._isClassObject) { // try to find the super class of the _members object var ClassMembersSuperClass = SuperClass._members === undefined ? BaseClass : SuperClass._members._class; // create the actual class of the _members instance // with an updated version of our options var opts = extend({}, options, { _isClassObject: true }); var ClassMembersClass = ClassMembersSuperClass._extend(classMembers, {}, opts); // store the actual JClass in ClassMembersClass ClassMembersClass._instanceClass = JClass; // create the _members instance JClass._members = new ClassMembersClass(); } // return the new class return JClass; }; /** * Converts arbitrary protoype-style classes to our JClass definition. * * @param {function} cls - The class to convert. * @returns {JClass} */ BaseClass._convert = function(cls, options) { // the properties consist of the class' prototype var instanceMembers = cls.prototype; // add the constructor function instanceMembers.init = function() { // simply create an instance of our target class var origin = this._origin = BaseClass._construct(cls, arguments); // add properties for each own property in _origin Object.keys(origin).forEach(function(key) { if (!origin.hasOwnProperty(key)) { return; } Object.defineProperty(this, key, { get: function() { return origin[key]; } }); }, this); }; // finally, create and return our new class return BaseClass._extend(instanceMembers, {}, options); }; /** * Returns an instance of a class with a list of arguments. This provides an apply-like * constructor usage. Note that this approach does not work with native constructors (e.g. String * or Boolean). * * @param {Class|JClass} cls - The class to instantiate. This may be a JClass or a prototype-based * class. * @param {array} [args=[]] - Arguments to pass to the constructor. * @returns {instance} */ BaseClass._construct = function(cls, args) { // empty default args if (args === undefined) { args = []; } // create a class wrapper that calls cls like a function var Class = function() { return cls.apply(this, args); }; // copy the prototype Class.prototype = cls.prototype; // return a new instance return new Class(); }; /** * Returns a property descriptor of the super class. * * @param {JClass|instance} cls - A JClass or an instance of a JClass to retrieve the property * descriptor from. * @param {string} prop - The name of the property descriptor to get. * @returns {object} */ BaseClass._superDescriptor = function(cls, prop) { // if cls is an instance, use its class if ("_class" in cls && cls instanceof cls._class) { cls = cls._class; } // a JClass? if ("_extends" in cls && cls._extends instanceof Function && cls._extends(this)) { return Object.getOwnPropertyDescriptor(cls._superClass.prototype, prop); } else { return undefined; } }; /** * Return the BaseClass. */ return BaseClass; });