webgyo
6/11/2014 - 8:27 AM

class-example.js

/**
 * @fileoverview Create Function like Class
 * @author teramako teramako.at.gmail.com
 * @version 0.1
 * @license MIT
 * @requires ECMASCript 5th and object.__proto__ property
 */

/**
 * Create Function constructor
 * @constructor
 * @augments Object
 * @param {String} [name] class name
 * @param {Function} [base] base class
 * @param {Object} proto
 * @return {Function}
 */
function Class (name, base, proto) {
  var args = Array.prototype.slice.call(arguments);
  if (typeof args[0] == "string")
    var name = args.shift();

  var superClass = Class;
  if (typeof args[0] == "function")
    superClass = args.shift();

  proto = args[0];

  if (!name)
    name = proto.__CLASS_NAME__ || superClass.__CLASS_NAME__ || "Anonymous";

  proto.__proto__ = superClass.prototype;

  Class.setSuper(proto, superClass.prototype);
  Object.defineProperty(proto, "__CLASS_NAME__", { value: name });

  var constructor = function () {
    var o = Object.create(proto, { constructor: { value: constructor }});
    var res = o.init.apply(o, arguments);
    return res != undefined ? res : o;
  };
  constructor.prototype = proto;
  var bound = constructor.bind(null);
  bound.prototype = proto;
  bound.__proto__ = Class;
  return bound;
}
Object.defineProperties(Class, {
  /**
   * Show the first class name.
   * @name Class.toString
   * @function
   * @return {String}
   */
  toString: {
    value: function () { return "[class " + this.prototype.__CLASS_NAME__ + "]"; },
  },
  /**
   * set "super" property is references the same name property in super
   * to functions and gettter/setter properties in proto
   * @name Class.setSuper
   * @function
   * @constant
   * @param {Object} proto
   * @param {Object} super
   */
  setSuper: {
    value: function (proto, super) {
      var keys = Object.getOwnPropertyNames(proto);
      out:
      for (var i = 0, len = keys.length; i < len; ++i) {
        var key = keys[i];
        if (!(key in super))
          continue;

        var desc = Object.getOwnPropertyDescriptor(proto, key);
        if ("get" in desc) {
          var p = super;
          while (p !== null) {
            if (Object.prototype.hasOwnProperty(p, key)) {
              Object.defineProperty(proto[key], "super", desc);
              continue out;
            }
            p = Object.getPrototypeOf(p);
          }
        } else if (typeof proto[key] == "function") {
          Object.defineProperty(proto[key], "super", { value: super[key] });
        }
      }
    },
  },
  /**
   * marge properties of the second or later arguments
   * to the first arguments
   * @function
   * @name Class.update
   */
  update: {
    value: function () {
      var args = Array.prototype.slice.call(arguments);
      var base = args.shift();
      for (var i = 0; i < args.length; ++i) {
        var o = args[i];
        var keys = Object.getOwnPropertyNames(o);
        for (var k = 0, len = keys.length; k < len; ++k) {
          var key = keys[i];
          Object.defineProperty(base, key, Object.getOwnPropertyDescriptor(o, key));
        }
      }
    },
  },
});
Class.prototype = Object.create({}, {
  /**
   * @memberof Class.prototype
   * @type {String}
   * @private
   */
  __CLASS_NAME__: { value: "Class", },
  /**
   * do nothing
   * @methodof Class.prototype
   * @constructs
   */
  init: {
    value: function Class_init () {},
  },
  /**
   * Show the first class name of the instance.
   * @methodof Class.prototype
   * @return {String}
   */
  toString: {
    value: function Class_toString () { return "[instance " + this.__CLASS_NAME__ + "]"; },
  },
});

// vim: sw=2 ts=2 et:
// ===================================
// Example
// ===================================
/**
 * Point
 * @class
 * @augments Class
 * @param {Number} x x-axis
 * @param {Number} y y-axis
 */
const Point = Class("Point", /** @lends Point# */ {
  /** @constructs */
  init: function Point (x, y) {
    if (x) this.x = x;
    if (y) this.y = y;
  },
  /**
   * x-axis
   * @type Number
   */
  x: 0,
  /**
   * y-axis
   * @type Number
   */
  y: 0,
});
/**
 * Rectangle
 * @class
 * @augments Point
 * @param {Number} x
 * @param {Number} y
 * @param {Number} width
 * @param {Number} height
 */
const Rectangle = Class("Rect", Point, /** @lends Rectangle# */ {
  /** @constructs */
  init: function Rectangle (x, y, width, height) {
    if (width)  this.width = width;
    if (height) this.height = height;
    arguments.callee.super.call(this, x, y);
  },
  getDimension: function Rect_getDimension () {
    return this.width * this.height;
  },
  /**
   * width of the rect
   * @type Number
   */
  width: 0,
  /**
   * height of the rect
   * @type Number
   */
  height: 0,
});
/**
 * Square
 * @class
 * @augments Rectangle
 * @param {Number} x
 * @param {Number} y
 * @param {Number} length
 */
const Square = Class("Square", Rectangle, /** @lends Square */ {
  /** @constructs */
  init: function Square (x, y, length) {
    arguments.callee.super.call(this, x, y, length, length);
  },
  /**
   * @name Square#length
   * @type Number
   */
  get length () {
    return this.width;
  },
  set length (val) {
    return this.width = this.height = val;
  }
});

var p = new Point(10,10); // Point instance;
p instanceof Point; // true
p instanceof Class // true

var rect = Rectangle(10, 15, 20, 30); // can omit `new' operator
rect instanceof Rect; // true
rect instanceof Point; // true
rect instanceof Class; // true
rect.x; // 10
rect.y; // 15
rect.width; // 20
rect.height; // 30

var sq = Square(20, 10, 30);
sq.getDimension(); // 900 (30 * 30), 

// vim: sw=2 ts=2 et: