From 3841c251917fb2a4eb036dbe492ee4bdbcf7adc2 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:20:19 +0100 Subject: [PATCH 01/23] Added base rewrite and tests for one method. Need to test inherit still. --- heir.js | 144 +++++++----------------------- tests/tests.js | 234 ++++--------------------------------------------- 2 files changed, 46 insertions(+), 332 deletions(-) diff --git a/heir.js b/heir.js index a061f7e..0ee8e6e 100644 --- a/heir.js +++ b/heir.js @@ -8,126 +8,44 @@ /*global define,module*/ 'use strict'; - /** - * Works out if a variable is a true object (created with {} etc) and not an array or anything else that usually shows up as an object. - * - * @param {Mixed} chk The variable to check to see if it is an object. It must be a pure object, not even a prototype. - * @return {Boolean} True if it is a true object, false if it is anything else. - */ - function isObject(chk) { - return (chk && Object.prototype.toString.call(chk) === '[object Object]') === true; - } - - /** - * Recursively merges two objects. Object `a` will be overridden by the values in object `b`. - * Please run the values through a cloning function first, this function does not try to clone them for you. - * The base object will be edited directly, please be careful! - * - * @param {Object} a The base object to merge into. - * @param {Object} b The object to merge down into object `a`. - * @return {Object} This is object `a` but merged with `b`. - */ - function merge(a, b) { - // Loop over all values in b. If they are not found in a then set them - // If both values are objects then recursively merge them - for (var key in b) { - // Make sure the value is not in __proto__ or something like that - if (b.hasOwnProperty(key)) { - // If they are both objects then merge recursively - if (isObject(a[key]) && isObject(b[key])) { - merge(a[key], b[key]); - } - - // Otherwise just replace the base value - else { - a[key] = b[key]; - } - } - } - - // Return the merged object - return a; - } - - /** - * Returns a recursive clone of the passed object. - * So when you edit the original the clone will not change. - * Used in prototypical inheritance. - * It will not clone arrays. - * - * @param {Object} orig The original object to clone. - * @return {Object} The cloned version of orig that can be edited without changing the original. - */ - function clone(orig) { - // Initialise variables - var cl = {}; - var key; - - // Loop over all values in the object - // If the value is an object then clone recursively - // Otherwise just copy the value - for (key in orig) { - if (orig.hasOwnProperty(key)) { - cl[key] = isObject(orig[key]) ? clone(orig[key]) : orig[key]; - } - } - - // Return the clone - return cl; - } - - /** - * Inherits other functions prototype objects into the current function. - * - * @param {Function|Function[]} parent A function which should have it's prototype cloned and placed into the current functions prototype. If you pass an array of functions they will all be inherited from. - * @param {Function} [forceFn] Optional function to use as the current function which is inheriting the other prototypes. It will default to `this`. - * @return {Function} The current function to allow chaining. - */ - function inherit(parent, forceFn) { - // Initialise variables - var fn = forceFn || this; - var i; - - // If the parent variable is not a function then it must be an array - // So we have to loop over it and inherit each of them - // Remember to pass the current function instance! - if (typeof parent !== 'function') { - i = parent.length; - while (i--) { - inherit(parent[i], fn); - } - } - else { - // It is not an array, it is a plain function - // Merge it's prototype into this one - merge(fn.prototype, clone(parent.prototype)); + var heir = { + /** + * Causes your desired class to inherit from a source class. This uses + * prototypical inheritance so you can override methods without ruining + * the parent class. + * + * This will alter the actual destination class though, it does not + * create a new class. + * + * @param {Function} destination The target class for the inheritance. + * @param {Function} source Class to inherit from. + */ + inherit: function inherit(destination, source) { + destination.prototype = heir.createObject(source.prototype); + destination.prototype.constructor = destination; + }, + + /** + * Creates a new object with the source object nestled within its + * prototype chain. + * + * @param {Object} source Method to insert into the new object's prototype. + * @return {Object} An empty object with the source object in it's prototype chain. + */ + createObject: function createObject(source) { + var Host = function () {}; + Host.prototype = source; + return new Host(); } - - // Return the current function to allow chaining - return fn; - } - - // Expose the inherit function by placing it in the Function prototype - Function.prototype.inherit = inherit; - - // Create a nice little namespace to expose - var ns = { - isObject: isObject, - merge: merge, - clone: clone, - inherit: inherit }; - // And expose everything else either via AMD or a global object if (typeof define === 'function' && define.amd) { - define(function () { - return ns; - }); + define(heir); } else if (typeof module === 'object' && module.exports) { - module.exports = ns; + module.exports = heir; } else { - this.heir = ns; + this.heir = heir; } }.call(this)); \ No newline at end of file diff --git a/tests/tests.js b/tests/tests.js index b32d58d..015792d 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -2,7 +2,6 @@ /*global expect,heir,jasmine,describe,it*/ 'use strict'; - // Set up the Jasmine environment var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; @@ -14,233 +13,30 @@ return htmlReporter.specFilter(spec); }; - // Configure the tests - describe('heir.isObject', function() { - it('returns true on objects', function() { - expect(heir.isObject({})).toBe(true); - var test = {}; - expect(heir.isObject(test)).toBe(true); - }); - - it('returns false on a regex', function() { - expect(heir.isObject(/reg[ex]/i)).toBe(false); - var test = /reg[ex]/i; - expect(heir.isObject(test)).toBe(false); - }); - - it('returns false on arrays', function() { - expect(heir.isObject([])).toBe(false); - var test = []; - expect(heir.isObject(test)).toBe(false); - }); - - it('returns false on null', function() { - expect(heir.isObject(null)).toBe(false); - var test = null; - expect(heir.isObject(test)).toBe(false); - }); - - it('returns false on numbers', function() { - expect(heir.isObject(50)).toBe(false); - var test = 50; - expect(heir.isObject(test)).toBe(false); - }); - - it('returns false on strings', function() { - expect(heir.isObject('foo')).toBe(false); - var test = 'foo'; - expect(heir.isObject(test)).toBe(false); - }); - - it('returns false on booleans', function() { - expect(heir.isObject(true)).toBe(false); - var test = true; - expect(heir.isObject(test)).toBe(false); - }); - }); + describe('heir.createObject', function () { + it('returns a new empty object when passed one', function () { + var source = {}; + var result = heir.createObject(source); - describe('heir.merge', function() { - it('copies in values that did not exist', function() { - var chk = heir.merge({foo:true}, {bar: true}); - expect(chk).toEqual({foo:true, bar:true}); + expect(result).toEqual({}); + expect(result).not.toBe(source); }); - it('copies in values that did exist', function() { - var chk = heir.merge({foo:true, bar:true}, {foo:false}); - expect(chk).toEqual({foo:false, bar:true}); - }); - - it('merges objects recursively', function() { - var chk = heir.merge({ - nest: { - foo: true, - bar: true - } - }, { - nest: { - bar: false - } - }); - - expect(chk).toEqual({ - nest: { - foo: true, - bar: false - } - }); - }); - }); - - describe('heir.clone', function() { - it('clones objects', function() { - var orig = { - foo: true, - bar: false - }; - - var cl = heir.clone(orig); - - cl.bar = true; - - expect(orig).toEqual({ - foo: true, - bar: false - }); - - expect(cl).toEqual({ - foo: true, - bar: true - }); - }); - - it('clones objects recursively', function() { - var orig = { - foo: true, - bar: false, - baz: { - one: 100, - two: 300 - } + it('inserts the source object into the prototype', function () { + var source = { + foo: true }; + var result = heir.createObject(source); - var cl = heir.clone(orig); - - cl.bar = true; - cl.baz.two = 200; - cl.baz.three = 300; - - expect(orig).toEqual({ - foo: true, - bar: false, - baz: { - one: 100, - two: 300 - } - }); - - expect(cl).toEqual({ - foo: true, - bar: true, - baz: { - one: 100, - two: 200, - three: 300 - } - }); + expect(result).toEqual(source); + expect(result).not.toBe(source); + expect(result.hasOwnProperty('foo')).toBe(false); + expect(result.foo).toBe(true); }); }); - describe('heir.inherit', function() { - it('inherits a class', function() { - var Base = function(){}; - Base.prototype.foo = function() { - return '!foo!'; - }; - - var Base2 = function(){}; - Base2.prototype.baz = function() { - return '!baz!'; - }; - - var Sub = function(){}.inherit(Base); - Sub.prototype.bar = function() { - return '!bar!'; - }; - Sub.inherit(Base2); - - var b = new Base(); - var b2 = new Base2(); - var s = new Sub(); - - expect(b.bar).not.toBeDefined(); - expect(b2.bar).not.toBeDefined(); - expect(b.foo()).toEqual('!foo!'); - expect(b2.baz()).toEqual('!baz!'); - expect(s.foo()).toEqual('!foo!'); - expect(s.bar()).toEqual('!bar!'); - expect(s.baz()).toEqual('!baz!'); - }); - - it('inherits multiple classes', function() { - var Base = function(){}; - Base.prototype.foo = function() { - return '!foo!'; - }; - - var Base2 = function(){}; - Base2.prototype.baz = function() { - return '!baz!'; - }; - - var Sub = function(){}.inherit([Base, Base2]); - Sub.prototype.bar = function() { - return '!bar!'; - }; - - var b = new Base(); - var b2 = new Base2(); - var s = new Sub(); - - expect(b.bar).not.toBeDefined(); - expect(b2.bar).not.toBeDefined(); - expect(b.foo()).toEqual('!foo!'); - expect(b2.baz()).toEqual('!baz!'); - expect(s.foo()).toEqual('!foo!'); - expect(s.bar()).toEqual('!bar!'); - expect(s.baz()).toEqual('!baz!'); - }); - - it('does not have to be called through the prototype', function() { - var Base = function(){}; - Base.prototype.foo = function() { - return '!foo!'; - }; - - var Base2 = function(){}; - Base2.prototype.baz = function() { - return '!baz!'; - }; - - var Sub = function(){}; - heir.inherit([Base, Base2], Sub); - Sub.prototype.bar = function() { - return '!bar!'; - }; - - var b = new Base(); - var b2 = new Base2(); - var s = new Sub(); - - expect(b.bar).not.toBeDefined(); - expect(b2.bar).not.toBeDefined(); - expect(b.foo()).toEqual('!foo!'); - expect(b2.baz()).toEqual('!baz!'); - expect(s.foo()).toEqual('!foo!'); - expect(s.bar()).toEqual('!bar!'); - expect(s.baz()).toEqual('!baz!'); - }); + describe('heir.inherit', function () { }); - // Run Jasmine jasmineEnv.execute(); }.call(this)); \ No newline at end of file From b8afc34156020c09c40ba4f4814265b78dcc8c6c Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:38:37 +0100 Subject: [PATCH 02/23] Added more tests for heir.inherit. --- tests/tests.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/tests.js b/tests/tests.js index 015792d..9f894ce 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -36,6 +36,43 @@ }); describe('heir.inherit', function () { + it('causes a class to inherit a method', function () { + var Source = function () {}; + Source.prototype.foo = function () {}; + + var Destination = function () {}; + heir.inherit(Destination, Source); + Destination.prototype.bar = function() {}; + + var result = new Destination(); + + expect(Destination.prototype.hasOwnProperty('foo')).toBe(false); + expect(Destination.prototype.hasOwnProperty('bar')).toBe(true); + expect(result.foo).toBeDefined(); + expect(result.bar).toBeDefined(); + }); + + it('can have methods overridden', function () { + var Source = function () {}; + Source.prototype.foo = function () { + return 'Source#foo'; + }; + + var Destination = function () {}; + heir.inherit(Destination, Source); + Destination.prototype.foo = function() { + return [ + 'Destination#foo', + Source.prototype.foo.call(this) + ].join(', '); + }; + + var source = new Source(); + var destination = new Destination(); + + expect(source.foo()).toBe('Source#foo'); + expect(destination.foo()).toBe('Destination#foo, Source#foo'); + }); }); jasmineEnv.execute(); From 94be22f58c994753ea7fe1f70bb4699cfc1916a8 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:46:15 +0100 Subject: [PATCH 03/23] Added heir.mixin tests, need to write the function. --- tests/tests.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/tests.js b/tests/tests.js index 9f894ce..211efaa 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -75,5 +75,22 @@ }); }); + describe('heir.mixin', function () { + it('can mix methods into a class', function () { + var source = { + foo: function () {}, + bar: function () {} + }; + var Destination = function () {}; + heir.mixin(Destination, source); + var result = new Destination(); + + expect(result.foo).toBeDefined(); + expect(result.bar).toBeDefined(); + expect(Destination.prototype.hasOwnProperty('foo')).toBe(true); + expect(Destination.prototype.hasOwnProperty('bar')).toBe(true); + }); + }); + jasmineEnv.execute(); }.call(this)); \ No newline at end of file From e4482fcb7bbb4418c3a6c8955c68f8a588d27ce0 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:50:14 +0100 Subject: [PATCH 04/23] Added the mixin method source. --- heir.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/heir.js b/heir.js index 0ee8e6e..3b77762 100644 --- a/heir.js +++ b/heir.js @@ -36,6 +36,29 @@ var Host = function () {}; Host.prototype = source; return new Host(); + }, + + /** + * Mixes the specified object into your class. This can be used to add + * certain capabilities and helper methods to a class that is already + * inheriting from some other class. You can mix in as many object as + * you want, but only inherit from one. + * + * These values are mixed into the actual prototype object of your + * class, they are not added to the prototype chain like inherit. + * + * @param {Function} destination Class to mix the object into. + * @param {Object} source Object to mix into the class. + */ + mixin: function mixin(destination, source) { + var key; + var destinationPrototype = destination.prototype; + + for (key in source) { + if (source.hasOwnProperty(key)) { + destinationPrototype[key] = source[key]; + } + } } }; From 1d36cea7a1c922ffbcfc188d6ace2bff1c9c6477 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:51:03 +0100 Subject: [PATCH 05/23] heir.createObject now uses the native method if there. --- heir.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heir.js b/heir.js index 3b77762..b093757 100644 --- a/heir.js +++ b/heir.js @@ -32,7 +32,7 @@ * @param {Object} source Method to insert into the new object's prototype. * @return {Object} An empty object with the source object in it's prototype chain. */ - createObject: function createObject(source) { + createObject: Object.create || function createObject(source) { var Host = function () {}; Host.prototype = source; return new Host(); From e0eca4e21dd179366a048355264bfc4fc0d69e66 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 12:55:58 +0100 Subject: [PATCH 06/23] Tested the use of instanceof. --- tests/tests.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/tests.js b/tests/tests.js index 211efaa..a110bf5 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -73,6 +73,21 @@ expect(source.foo()).toBe('Source#foo'); expect(destination.foo()).toBe('Destination#foo, Source#foo'); }); + + it('is correct in the eyes of instanceof', function () { + var Source = function () {}; + var Destination = function () {}; + heir.inherit(Destination, Source); + + var source = new Source(); + var destination = new Destination(); + + expect(source instanceof Source).toBe(true); + expect(source instanceof Destination).toBe(false); + + expect(destination instanceof Source).toBe(true); + expect(destination instanceof Destination).toBe(true); + }); }); describe('heir.mixin', function () { From 7e23d399b9edb47fb9a4f9e58963f75908bf2eaa Mon Sep 17 00:00:00 2001 From: Nathaniel Higgins Date: Sun, 22 Sep 2013 13:14:58 +0100 Subject: [PATCH 07/23] Add a more general purpose "merge" method. If we have a "merge" method, it fully duplicates the functionality presented in https://github.com/Wolfy87/Heir/pull/4, but in a much nicer way. I also added `heir.hasOwn`, as a shortcut for `Object.prototype.hasOwnProperty`. The "why"s for that are explained in the method's DocBlock. --- heir.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/heir.js b/heir.js index b093757..66d8a91 100644 --- a/heir.js +++ b/heir.js @@ -51,14 +51,37 @@ * @param {Object} source Object to mix into the class. */ mixin: function mixin(destination, source) { + return heir.merge(destination.prototype, source); + }, + + /** + * This function merges a source into a destination. + * + * @param {Object} destination The destination for the merge + * @param {Object} source The source of the properties to merge + */ + merge: function merge(destination, source) { var key; - var destinationPrototype = destination.prototype; for (key in source) { - if (source.hasOwnProperty(key)) { - destinationPrototype[key] = source[key]; + if (heir.hasOwn(source, key)) { + destination[key] = source; } } + }, + + /** + * Shortcut for Object.prototype.hasOwnProperty. + * + * We should use Object.prototype.hasOwnPropety rather than + * object.hasOwnProperty as it could be overwritten. + * + * @param {Object} object The object to check + * @param {String|Number} key The key to check for. + * @return {Boolean} Does object have key as an own propety? + */ + hasOwn: function hasOwn(object, key) { + return Object.prototype.hasOwnProperty.call(object, key); } }; @@ -71,4 +94,4 @@ else { this.heir = heir; } -}.call(this)); \ No newline at end of file +}.call(this)); From ef3f15fb19f2f077d73345bad91a8007193600ab Mon Sep 17 00:00:00 2001 From: Nathaniel Higgins Date: Sun, 22 Sep 2013 13:19:23 +0100 Subject: [PATCH 08/23] Fix a little bit of layout --- heir.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/heir.js b/heir.js index 66d8a91..2330685 100644 --- a/heir.js +++ b/heir.js @@ -58,7 +58,7 @@ * This function merges a source into a destination. * * @param {Object} destination The destination for the merge - * @param {Object} source The source of the properties to merge + * @param {Object} source The source of the properties to merge */ merge: function merge(destination, source) { var key; @@ -76,9 +76,9 @@ * We should use Object.prototype.hasOwnPropety rather than * object.hasOwnProperty as it could be overwritten. * - * @param {Object} object The object to check + * @param {Object} object The object to check * @param {String|Number} key The key to check for. - * @return {Boolean} Does object have key as an own propety? + * @return {Boolean} Does object have key as an own propety? */ hasOwn: function hasOwn(object, key) { return Object.prototype.hasOwnProperty.call(object, key); From 5e1055d38469c1a72d795121899e68048ce33ac2 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:11:39 +0100 Subject: [PATCH 09/23] Styling tweaks for the merge pull request. --- heir.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/heir.js b/heir.js index 2330685..01d1c2c 100644 --- a/heir.js +++ b/heir.js @@ -55,10 +55,10 @@ }, /** - * This function merges a source into a destination. + * Merges one object into another, change the object in place. * - * @param {Object} destination The destination for the merge - * @param {Object} source The source of the properties to merge + * @param {Object} destination The destination for the merge. + * @param {Object} source The source of the properties to merge. */ merge: function merge(destination, source) { var key; @@ -71,14 +71,14 @@ }, /** - * Shortcut for Object.prototype.hasOwnProperty. + * Shortcut for `Object.prototype.hasOwnProperty`. * - * We should use Object.prototype.hasOwnPropety rather than - * object.hasOwnProperty as it could be overwritten. - * - * @param {Object} object The object to check - * @param {String|Number} key The key to check for. - * @return {Boolean} Does object have key as an own propety? + * Uses `Object.prototype.hasOwnPropety` rather than + * `object.hasOwnProperty` as it could be overwritten. + * + * @param {Object} object The object to check + * @param {String} key The key to check for. + * @return {Boolean} Does object have key as an own propety? */ hasOwn: function hasOwn(object, key) { return Object.prototype.hasOwnProperty.call(object, key); From 94ec775b3031b5f57d0a8b5ad87460af2b4f42f0 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:17:54 +0100 Subject: [PATCH 10/23] Tested heir.hasOwn. --- tests/tests.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/tests.js b/tests/tests.js index a110bf5..b5c355e 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -107,5 +107,26 @@ }); }); + describe('heir.hasOwn', function () { + it('returns true when it is it\'s own', function () { + var source = { + foo: true + }; + + expect(heir.hasOwn(source, 'foo')).toBe(true); + }); + + it('returns false when it is either undefined or up the prototype chain', function () { + var noProperty = {}; + var inChain = heir.createObject({ + foo: true + }); + + expect(heir.hasOwn(noProperty, 'foo')).toBe(false); + expect(inChain.foo).toBeDefined(); + expect(heir.hasOwn(inChain, 'foo')).toBe(false); + }); + }); + jasmineEnv.execute(); }.call(this)); \ No newline at end of file From ab95ad8f3a90527146c44b0c651f117f36735bc7 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:20:40 +0100 Subject: [PATCH 11/23] Tested merge and fixed an error. Merge was merging the entire source object into each destination value, not the individual key values. --- heir.js | 2 +- tests/tests.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/heir.js b/heir.js index 01d1c2c..641350a 100644 --- a/heir.js +++ b/heir.js @@ -65,7 +65,7 @@ for (key in source) { if (heir.hasOwn(source, key)) { - destination[key] = source; + destination[key] = source[key]; } } }, diff --git a/tests/tests.js b/tests/tests.js index b5c355e..c677bdc 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -128,5 +128,17 @@ }); }); + describe('heir.merge', function () { + it('merges one object into another', function () { + var a = {foo:true}; + var b = {bar:true}; + heir.merge(a, b); + + expect(a.foo).toBe(true); + expect(a.bar).toBe(true); + expect(b.foo).toBeUndefined(); + }); + }); + jasmineEnv.execute(); }.call(this)); \ No newline at end of file From a4f9fc8da9a634b7e2866cd026a3783847e2a1d2 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:31:38 +0100 Subject: [PATCH 12/23] Added this.super and tests. --- heir.js | 5 +++-- tests/tests.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/heir.js b/heir.js index 641350a..252686b 100644 --- a/heir.js +++ b/heir.js @@ -21,8 +21,9 @@ * @param {Function} source Class to inherit from. */ inherit: function inherit(destination, source) { - destination.prototype = heir.createObject(source.prototype); - destination.prototype.constructor = destination; + var proto = destination.prototype = heir.createObject(source.prototype); + proto.constructor = destination; + proto.super = source.prototype; }, /** diff --git a/tests/tests.js b/tests/tests.js index c677bdc..aed7ff4 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -88,6 +88,16 @@ expect(destination instanceof Source).toBe(true); expect(destination instanceof Destination).toBe(true); }); + + it('has a reference to the parent in this.super', function () { + var Source = function () {}; + var Destination = function () {}; + heir.inherit(Destination, Source); + + var result = new Destination(); + + expect(result.super).toBe(Source.prototype); + }); }); describe('heir.mixin', function () { From c28e8dcc50474d8193e720504061b2f079aae35b Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:37:59 +0100 Subject: [PATCH 13/23] Now using a nicer UMD method. --- heir.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/heir.js b/heir.js index 252686b..11297d7 100644 --- a/heir.js +++ b/heir.js @@ -4,7 +4,15 @@ * MIT license */ -(function () { +(function (name, root, factory) { + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root[name] = factory(); + } +}('heir', this, function () { /*global define,module*/ 'use strict'; @@ -86,13 +94,5 @@ } }; - if (typeof define === 'function' && define.amd) { - define(heir); - } - else if (typeof module === 'object' && module.exports) { - module.exports = heir; - } - else { - this.heir = heir; - } -}.call(this)); + return heir; +})); From 54cfee94a7889f70474241f4ebf6cf29440d924c Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:50:33 +0100 Subject: [PATCH 14/23] Updated the example in the readme. --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d18b1a2..ca38083 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,29 @@ This is a small script that adds the ability to inherit other functions prototyp Due to there not being any documentation yet, you may want to read my JSDoc comments in the source. They will tell you everything you need to know. Here is a quick example to get you going anyway. ```javascript -// Create the base class -var Base = function(){}; +// Create the base class. +function Base() {} -// Add a method -Base.prototype.foo = function() { - retrun '!foo!'; +// Add a method. +Base.prototype.foo = function () { + return 'Base#foo'; }; -// Create a sub class which inherits from base -var Sub = function(){}.inherit(Base); +// Create a sub class which inherits from base. +function Sub() {} +heir.inherit(Sub, Base); -// Create an instance of Sub and call it's method +// Change the original method. +Sub.prototype.foo = function () { + return [ + 'Sub#foo', + this.super.foo() + ].join(', '); +}; + +// Create an instance of Sub and call it's method. var s = new Sub(); -s.foo(); // Returns "!foo!" +s.foo(); // Returns "Sub#foo, Base#foo" ``` You can load this script into your browser using a normal script tag or AMD. You can also use node.js' `require` if you are running server side. From df2380641a61abeb5356832402191cfdd3461f8e Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:52:33 +0100 Subject: [PATCH 15/23] Readme issues link now uses the nice format. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca38083..6dfdfd4 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Tests are performed using Jasmine in the following browsers. When testing in the more modern browsers, not Internet Explorer basically, I run it through the very early versions, some midrange versions and the very latest ones too. I don't just do the latest version. -Heir will always be tested and working perfectly in all of them before a release. I will not release anything I think is riddled with bugs. However, if you do spot one, please [submit it as an issue](https://github.com/Wolfy87/Heir/issues) and I will get right on it. +Heir will always be tested and working perfectly in all of them before a release. I will not release anything I think is riddled with bugs. However, if you do spot one, please [submit it as an issue][issues] and I will get right on it. ## License (MIT) @@ -67,4 +67,5 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [npm]: https://npmjs.org/ -[bower]: http://bower.io/ \ No newline at end of file +[bower]: http://bower.io/ +[issues]: https://github.com/Wolfy87/Heir/issues \ No newline at end of file From e99487e30dfad51af3f445a0cb486f2427370657 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:53:53 +0100 Subject: [PATCH 16/23] Added mixin to the example. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6dfdfd4..1b84c6d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ Base.prototype.foo = function () { function Sub() {} heir.inherit(Sub, Base); +// Mix in some functionality enhancing objects. +heir.mixin(Sub, events); +heir.mixin(Sub, pooling); + // Change the original method. Sub.prototype.foo = function () { return [ From 29bfbbe747376160248b3a39192ad069df903bbc Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 14:54:48 +0100 Subject: [PATCH 17/23] Added some line breaks into the UMD section. --- heir.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/heir.js b/heir.js index 11297d7..8842067 100644 --- a/heir.js +++ b/heir.js @@ -7,9 +7,11 @@ (function (name, root, factory) { if (typeof define === 'function' && define.amd) { define(factory); - } else if (typeof exports === 'object') { + } + else if (typeof exports === 'object') { module.exports = factory(); - } else { + } + else { root[name] = factory(); } }('heir', this, function () { From 03a25cce00c618c5b0a25ed1a218734141be21f5 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 15:26:31 +0100 Subject: [PATCH 18/23] Readme super use now uses call. Increases clarity as to how you actually use it. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b84c6d..1dcd7d9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ heir.mixin(Sub, pooling); Sub.prototype.foo = function () { return [ 'Sub#foo', - this.super.foo() + this.super.foo.call(this) ].join(', '); }; From 6ab39739ba0b48af83c07c03ca4c132a4c3126a5 Mon Sep 17 00:00:00 2001 From: Nathaniel Higgins Date: Sun, 22 Sep 2013 15:47:23 +0100 Subject: [PATCH 19/23] Add an option to disable adding the super to the object --- heir.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/heir.js b/heir.js index 8842067..ee573e1 100644 --- a/heir.js +++ b/heir.js @@ -29,11 +29,15 @@ * * @param {Function} destination The target class for the inheritance. * @param {Function} source Class to inherit from. + * @param {Boolean} addSuper Should we add the super property to the prototype? Defaults to true. */ - inherit: function inherit(destination, source) { + inherit: function inherit(destination, source, addSuper) { var proto = destination.prototype = heir.createObject(source.prototype); proto.constructor = destination; - proto.super = source.prototype; + + if (addSuper || typeof addSuper === 'undefined') { + proto.super = source.prototype; + } }, /** From b4b09d273f15802b89a6517d9e2e0b874513bb1f Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 15:57:33 +0100 Subject: [PATCH 20/23] Added a test for the super flag. --- tests/tests.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/tests.js b/tests/tests.js index aed7ff4..1c492f6 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -98,6 +98,16 @@ expect(result.super).toBe(Source.prototype); }); + + it('can have the addition of this.super disabled', function () { + var Source = function () {}; + var Destination = function () {}; + heir.inherit(Destination, Source, false); + + var result = new Destination(); + + expect(result.super).toBeUndefined(); + }); }); describe('heir.mixin', function () { From c6d9f4d79cc633a2815ecc61bd1a9208b6cec463 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 16:33:05 +0100 Subject: [PATCH 21/23] Added a readme section about the v2 changes. --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1dcd7d9..c0dc33f 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,14 @@ s.foo(); // Returns "Sub#foo, Base#foo" You can load this script into your browser using a normal script tag or AMD. You can also use node.js' `require` if you are running server side. +## Changes from v1 + +The `inherit` method used to work by cloning and merging multiple prototypes into one. This meant things like `instanceof` didn't work and you could get into some weird scenarios [caused by multiple inheritance][mi]. + +The new `inherit` uses the built in prototypical inheritance to provide a much cleaner outcome, as shown in [this post about prototypical inheritance][pi]. The major change is that you can't inherit from multiple classes any more. + +If you still need to have multiple things shared between classes to avoid duplication, you can now use the `mixin` method to merge objects into your inheritance hierarchies where required. + ## Downloading You can obtain a copy by cloning this repository with git, installing through [npm][] or [bower][]. Heir is called `heir` within both package managers. @@ -72,4 +80,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI [npm]: https://npmjs.org/ [bower]: http://bower.io/ -[issues]: https://github.com/Wolfy87/Heir/issues \ No newline at end of file +[issues]: https://github.com/Wolfy87/Heir/issues +[mi]: http://stackoverflow.com/questions/225929/what-is-the-exact-problem-with-multiple-inheritance +[pi]: http://oli.me.uk/2013/06/01/prototypical-inheritance-done-right/ \ No newline at end of file From 067b89399d95de7b883a5607509a8648c229e845 Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 16:41:38 +0100 Subject: [PATCH 22/23] Bumped all of the version numbers. --- bower.json | 2 +- heir.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 19b4371..60c2506 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "heir", "description": "Makes prototypical inheritance easy and robust", - "version": "1.0.1", + "version": "2.0.0", "main": [ "./heir.js" ], diff --git a/heir.js b/heir.js index ee573e1..332e881 100644 --- a/heir.js +++ b/heir.js @@ -1,5 +1,5 @@ /** - * Heir v1.0.1 - http://git.io/F87mKg + * Heir v2.0.0 - http://git.io/F87mKg * Oliver Caldwell * MIT license */ diff --git a/package.json b/package.json index d6878b7..729fbc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "heir", - "version": "1.0.1", + "version": "2.0.0", "description": "Makes prototypical inheritance easy and robust", "license": "MIT", "main": "heir.js", From 72f2e8498fab180baf33d053e2e904ead3ce0ddb Mon Sep 17 00:00:00 2001 From: Oliver Caldwell Date: Sun, 22 Sep 2013 16:57:30 +0100 Subject: [PATCH 23/23] Renamed super to _super. It makes IE explode. --- README.md | 2 +- heir.js | 4 ++-- tests/tests.js | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c0dc33f..4c9efe0 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ heir.mixin(Sub, pooling); Sub.prototype.foo = function () { return [ 'Sub#foo', - this.super.foo.call(this) + this._super.foo.call(this) ].join(', '); }; diff --git a/heir.js b/heir.js index 332e881..170d83c 100644 --- a/heir.js +++ b/heir.js @@ -29,14 +29,14 @@ * * @param {Function} destination The target class for the inheritance. * @param {Function} source Class to inherit from. - * @param {Boolean} addSuper Should we add the super property to the prototype? Defaults to true. + * @param {Boolean} addSuper Should we add the _super property to the prototype? Defaults to true. */ inherit: function inherit(destination, source, addSuper) { var proto = destination.prototype = heir.createObject(source.prototype); proto.constructor = destination; if (addSuper || typeof addSuper === 'undefined') { - proto.super = source.prototype; + proto._super = source.prototype; } }, diff --git a/tests/tests.js b/tests/tests.js index 1c492f6..3cd9e08 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -89,24 +89,24 @@ expect(destination instanceof Destination).toBe(true); }); - it('has a reference to the parent in this.super', function () { + it('has a reference to the parent in this._super', function () { var Source = function () {}; var Destination = function () {}; heir.inherit(Destination, Source); var result = new Destination(); - expect(result.super).toBe(Source.prototype); + expect(result._super).toBe(Source.prototype); }); - it('can have the addition of this.super disabled', function () { + it('can have the addition of this._super disabled', function () { var Source = function () {}; var Destination = function () {}; heir.inherit(Destination, Source, false); var result = new Destination(); - expect(result.super).toBeUndefined(); + expect(result._super).toBeUndefined(); }); });