Skip to content

Instantly share code, notes, and snippets.

@mbrowne
Created October 26, 2013 13:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbrowne/7169377 to your computer and use it in GitHub Desktop.
Save mbrowne/7169377 to your computer and use it in GitHub Desktop.
simpleoo.js new version preview
(function(define) {
/*
Example usage:
function Animal() {}
Animal.prototype.eat = function() { console.log('yum'); }
function Cat() {}
simpleoo(Cat).extend(Animal, {
meow: function() { console.log('meow'); }
});
var garfield = new Cat();
console.log(garfield instanceof Cat); //true
console.log(garfield instanceof Animal); //true
*/
define([], function() {
/**
* Example usage:
* simpleoo(Cat).extend(Animal, {
* meow: function() { console.log('meow'); }
* });
*/
function simpleoo(ctorOrObj) {
if (typeof ctorOrObj == 'function') {
return simpleoo._constructorMode(ctorOrObj);
}
else { //object
return simpleoo._objectMode(ctorOrObj);
};
}
//This method is not meant to be used directly.
//(It's exported only so it can be overridden by simpleoo-extended)
simpleoo._constructorMode = function(ctor) {
return {
//Two forms:
//1. simpleoo(Constructor).extend(parentConstructorOrPrototype [, mixin1, mixin2 ...])
//2. simpleoo(Constructor).extend(mixin)
//
//The second form will be activated if the mixin argument is a plain JSON object
//
//Example:
//simpleoo(Cat).extend(Animal, {
// meow: function() {}
//});
extend: function extend() {
simpleoo._extendImpl.apply(ctor, arguments);
return this;
},
//Usage:
//simpleoo(Constructor).inheritPrototype(parentConstructorOrPrototype)
inheritPrototype: function inheritPrototype(parentCtorOrProto) {
ctor.prototype = simpleoo.inheritPrototype(ctor, parentCtorOrProto);
return this;
},
//Usage:
//simpleoo(Constructor).mixin(prototypeProperties, staticProperties)
//
//To create *instance* properties, create them from within the constructor
//(you can optionally use the initProperties() convenience function from simpleoo-extended)
mixin: function mixin() {
simpleoo._mixinCtorModeImpl.apply(ctor, arguments);
return this;
}
};
};
//This method is not meant to be used directly.
//(It's exported only so it can be called directly in simpleoo-extended)
simpleoo._extendImpl = function() {
var ctor = this;
if (arguments.length==1) {
var arg = arguments[0];
if (simpleoo.isPlainObject(arg))
simpleoo.mixin(ctor.prototype, arg);
else
ctor.prototype = simpleoo.inheritPrototype(ctor, arg);
}
else {
var parentCtorOrProto = arguments[0];
if (parentCtorOrProto) ctor.prototype = simpleoo.inheritPrototype(ctor, parentCtorOrProto);
var mixinArgs = Array.prototype.slice.call(arguments);
mixinArgs[0] = ctor.prototype; //mixinArgs[0] is the target object, not one of the mixins
simpleoo.mixin.apply(null, mixinArgs);
}
};
//This method is not meant to be used directly.
//(It's exported only so it can be called in simpleoo-extended)
//Usage:
//simpleoo(Constructor).mixin(prototypeProperties, staticProperties)
simpleoo._mixinCtorModeImpl = function() {
var ctor = this;
if (arguments.length==1) {
var attributes = arguments[0],
prototypeProperties = arguments[1],
staticProperties = arguments[2];
}
else if (arguments.length==2) {
var prototypeProperties = arguments[0],
staticProperties = arguments[1];
}
else {
throw new Error('simpleoo(Constructor).mixin: This method only accepts one or two arguments. '+
'To do multiple mixins, use simpleoo(obj).mixin() or simpleoo.mixin()');
}
simpleoo.mixin(ctor.prototype, prototypeProperties);
if (staticProperties) simpleoo.mixin(ctor, staticProperties);
};
//This method is not meant to be used directly.
//(It's exported only so it can be overridden by simpleoo-extended)
simpleoo._objectMode = function(obj) {
return {
extend: function() {
throw new Error('The extend() method is only available when passing a constructor function to simpleoo(), not when passing an object.');
},
mixin: function mixin() {
simpleoo._mixinObjModeImpl.apply(obj, arguments);
return this;
}
};
};
simpleoo._mixinObjModeImpl = function() {
var obj = this;
var mixinArgs = Array.prototype.slice.call(arguments);
mixinArgs.unshift(obj);
simpleoo.mixin.apply(null, mixinArgs);
};
//If Object.create isn't already defined, we just do the simple shim, without the second argument,
//since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
object_create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}
var supportsES5 = null;
/**
* Returns whether the browser supports ES5 Object functions
* @returns bool
*/
function browserSupportsES5() {
if (supportsES5 === null) {
//If the environment supports ES5 (or if an ES5 shim has been loaded),
//use defineProperty rather than simple assignment in order to preserve
//property attributes such as 'writable' and 'configurable'.
//In ES5 environments, it can mix in non-enumerable properties.
if (Object.defineProperty) {
//test it to make sure it's ES5-compliant
var obj = {};
try {
Object.defineProperty(obj, 'x', {});
}
catch (e) {}
supportsES5 = ('x' in obj) && Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor;
}
}
return supportsES5;
}
var emptyObject = {};
//This method supports multiple mixins
simpleoo.mixin = function mixin(dst, src1 /*, src2, src3, ... */) {
for(var i = 0; i < arguments.length; i++) {
singleMixin(dst, arguments[i], true);
}
var lastSrc = arguments[arguments.length-1];
if(lastSrc.hasOwnProperty('toString') && typeof lastSrc.toString === 'function') {
dst.toString = lastSrc.toString;
}
return dst;
}
function singleMixin(dst, src, replaceExisting) {
if (typeof replaceExisting=='undefined') replaceExisting = false;
if (browserSupportsES5()) {
//If the environment supports ES5 (or if an ES5 shim has been loaded),
//use defineProperty rather than simple assignment in order to preserve
//property attributes such as enumerability
//Kudos to http://www.2ality.com/2012/01/js-inheritance-by-example.html
var dstIsFunction = (typeof dst == 'function');
Object.getOwnPropertyNames(src)
.forEach(function(propName) {
if (!(propName in emptyObject)) {
if ((!dstIsFunction || !(propName in Function)) &&
(replaceExisting || !dst.hasOwnProperty(propName)))
{
Object.defineProperty(dst, propName,
Object.getOwnPropertyDescriptor(src, propName)
);
}
}
});
}
else {
for(var s in src) {
if(src.hasOwnProperty(s) && !(s in emptyObject)) {
if (replaceExisting || !dst.hasOwnProperty(s))
dst[s] = src[s];
}
}
}
return dst;
}
/**
* Second parameter can be either the constructor for a parent type
* or an object to be used as the parent prototype.
*
* Usage: Cat.prototype = inheritPrototype(Cat, Animal);
*/
simpleoo.inheritPrototype = function inheritPrototype(childCtor, parentCtorOrProto) {
if (typeof parentCtorOrProto == 'function') {
var parentProto = parentCtorOrProto.prototype;
childCtor._parent = parentProto;
}
else var parentProto = parentCtorOrProto;
var proto = object_create(parentProto);
if (browserSupportsES5()) { //for ES5 environments
//enumerable and configurable attributes default to false, just like
//the constructor property that Javascript automatically creates on functions
//when they're first declared
Object.defineProperty(proto, 'constructor', {writable: true, value: childCtor});
}
else proto.constructor = childCtor;
return proto;
}
/**
* Deep copy an object (make copies of all its object properties, sub-properties, etc.)
* An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
* that doesn't break if the constructor has required parameters
*
* It also borrows some code from http://stackoverflow.com/a/11621004/560114
*/
function deepCopy(src, /* INTERNAL */ _visited) {
if(src == null || typeof(src) !== 'object'){
return src;
}
// Initialize the visited objects array if needed
// This is used to detect cyclic references
if (_visited == undefined){
_visited = [];
}
// Otherwise, ensure src has not already been visited
else {
var i, len = _visited.length;
for (i = 0; i < len; i++) {
// If src was already visited, don't try to copy it, just return the reference
if (src === _visited[i]) {
return src;
}
}
}
// Add this object to the visited array
_visited.push(src);
//Honor native/custom clone methods
if(typeof src.clone == 'function'){
return src.clone(true);
}
//Special cases:
//Date
if (src instanceof Date){
return new Date(src.getTime());
}
//RegExp
if(src instanceof RegExp){
return new RegExp(src);
}
//DOM Elements
if(src.nodeType && typeof src.cloneNode == 'function'){
return src.cloneNode(true);
}
//If we've reached here, we have a regular object, array, or function
//make sure the returned object has the same prototype as the original
var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
if (!proto) {
proto = src.constructor.prototype; //this line would probably only be reached by very old browsers
}
var ret = object_create(proto);
for(var key in src){
//Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
//For an example of how this could be modified to do so, see the singleMixin() function
ret[key] = deepCopy(src[key], _visited);
}
return ret;
}
// TODO - if we don't end up depending on this for simpleoo(objOrCtor).mixin(), move it to simpleoo-extended
// Is given value a plain {} Javascript object?
// Mostly copied from here: https://github.com/tauren/underscore/blob/master/underscore.js
// Based on the jQuery implementation
simpleoo.isPlainObject = function isPlainObject(obj) {
var ctor, key;
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if (typeof obj != 'object' || !obj || Object.prototype.toString.call(obj) !== '[object Object]'
|| obj.nodeType || "setInterval" in obj) {
return false;
}
// Not own constructor property must be Object
ctor = typeof obj.constructor === 'function' && obj.constructor.prototype;
if (!ctor || !Object.hasOwnProperty.call(ctor, 'isPrototypeOf')) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
for ( key in obj ) {}
return typeof key == 'undefined' || Object.hasOwnProperty.call( obj, key );
}
return simpleoo;
});
})(typeof define != 'undefined' ? define : function(deps, factory) { module.exports = factory(); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment