Skip to content

Instantly share code, notes, and snippets.

@amatiasq
Last active December 17, 2015 13:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amatiasq/5619166 to your computer and use it in GitHub Desktop.
Save amatiasq/5619166 to your computer and use it in GitHub Desktop.
A function to create
/**
* Provides:
* Function extend(Function parent, Object config, Object statics);
*
* It extends constructors with it's methods
* Also provides to every method who overwrites another one
* with a this.base() method to invoke overwrote method.
* This feature is based in Dean Edwards implementation:
* http://dean.edwards.name/weblog/2006/03/base/
*
* Created constructor has methods
* .extend(Object config)
* and
* .inject(Object config)
*/
//jshint camelcase:false
/*globals module*/
(function(root, undefined) {
'use strict';
var has = Object.prototype.hasOwnProperty;
var _ = Object.keys ? {
each: function each_ECMA5(collection, callback) {
Object.keys(collection).forEach(callback);
},
getOwn: function getOwn_ECMA5(target, prop) {
return Object.getOwnPropertyDescriptor(target, prop);
},
get: function get_ECMA5(target, prop) {
var descriptor;
while (target) {
descriptor = Object.getOwnPropertyDescriptor(target, prop);
if (descriptor) return descriptor;
target = Object.getPrototypeOf(target);
}
},
set: function set_ECMA5(target, prop, descriptor) {
Object.defineProperty(target, prop, descriptor);
},
wrap: function wrap_ECMA5(funct, base) {
// HACK: Performance of the second function is NEFAST!
if (extend.performanceHack) {
return function() {
var a = this.base;
this.base = base.value;
// If you are here and don't know what to do, debug into the next line
var result = funct.apply(this, arguments);
this.base = a;
return result;
};
}
base.configurable = true;
return function() {
var original = Object.getOwnPropertyDescriptor(this, 'base');
Object.defineProperty(this, 'base', base);
// If you are here and don't know what to do, debug into the next line
var result = funct.apply(this, arguments);
Object.defineProperty(this, 'base', original || empty);
return result;
};
}
} : {
each: function each_FALLBACK(collection, callback) {
for (var i in collection)
if (has.call(collection, i))
callback(i);
},
getOwn: function getOwn_FALLBACK(target, prop) {
return has.call(target, prop) ? { value: target[prop] } : null;
},
get: function get_FALLBACK(target, prop) {
return { value: target[prop] };
},
set: function set_FALLBACK(target, prop, descriptor) {
if (descriptor)
target[prop] = descriptor.value;
},
wrap: function wrap_FALLBACK(funct, base) {
return function() {
var a = this.base;
this.base = base.value;
// If you are here and don't know what to do, debug into the next line
var result = funct.apply(this, arguments);
this.base = a;
return result;
};
}
};
/**
* Adds every item in config to obj
* If it's a function it will wrap it in order to have this.base();
*
* @param obj <Object> The object where the properties will be injected.
* @param config <JSON> The object with methdos to inject.
*/
function inject(target, props) {
if (!props) return;
_.each(props, function(prop) {
var desc = _.getOwn(props, prop);
var base = _.get(target, prop);
if (desc && typeof desc.value === 'function' && base)
desc.value = _.wrap(desc.value, base);
_.set(target, prop, desc);
});
}
function copy(target, source) {
_.each(source, function(prop) {
_.set(target, prop, _.getOwn(source, prop));
});
}
/// Dummy, just for prototype
function intermediate() { }
/**
* Creates a new function who's prototype property extend <Parent>'s prototype property.
*
* @param Parent <Function> The constructor of the type to extend.
* @param config <JSON> Object with methods to add to the new type.
* @returns <Function> The constructor of the new type.
*/
function extend(Parent, config, statics) {
config = config || {};
// We create the constructor
var ctor = has.call(config, 'constructor') &&
typeof config.constructor === 'function' ?
_.wrap(config.constructor, Parent) :
function() { Parent.apply(this, arguments); };
// Add basic static methods
ctor.extend = function(desc, statics) {
return extend(this, desc, statics);
};
ctor.inject = function(desc, statics) {
inject(this.prototype, desc);
inject(this, statics);
return this;
};
// Copy parent's statics
copy(ctor, Parent);
inject(ctor, statics);
// Extend parent prototype
intermediate.prototype = Parent.prototype;
ctor.prototype = new intermediate;
// Apply new methods
ctor.inject(config);
// Fix constructor
ctor.prototype.constructor = ctor;
return ctor;
}
extend.performanceHack = false;
if (typeof module !== 'undefined' && module.exports)
module.exports = extend;
else if (typeof define !== 'undefined' && define.amd)
define(function() { return extend });
else
root.extend = extend;
})(this);
var Animal = extend(Object, {
constructor: function(name) {
// Invoke Object's constructor
this.base();
this.name = name;
// Log creation
console.log('New animal named ' + name);
},
// Abstract
makeSound: function() {
console.log(this.name + ' is going to make a sound :)');
},
});
var Dog = Animal.extend({
constructor: function(name) {
// Invoke Animals's constructor
this.base(name);
// Log creation
console.log('Dog instanciation');
},
bark: function() {
console.log('WOF!!!');
},
makeSound: function() {
this.base();
this.bark();
}
});
var pet = new Dog('buddy');
// New animal named buddy
// Dog instanciation
pet.makeSound();
// buddy is going to make a sound :)
// WOF!!!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment