Created
October 26, 2013 13:18
-
-
Save mbrowne/7169377 to your computer and use it in GitHub Desktop.
simpleoo.js new version preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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