Comparing version 1.0.1 to 2.0.0
{ | ||
"name": "heir", | ||
"description": "Makes prototypical inheritance easy and robust", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"main": [ | ||
@@ -6,0 +6,0 @@ "./heir.js" |
203
heir.js
/** | ||
* Heir v1.0.1 - http://git.io/F87mKg | ||
* Heir v2.0.0 - http://git.io/F87mKg | ||
* Oliver Caldwell | ||
@@ -7,128 +7,99 @@ * 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'; | ||
/** | ||
* 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; | ||
} | ||
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. | ||
* @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; | ||
} | ||
}, | ||
/** | ||
* 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]); | ||
} | ||
/** | ||
* 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: Object.create || function createObject(source) { | ||
var Host = function () {}; | ||
Host.prototype = source; | ||
return new Host(); | ||
}, | ||
// Otherwise just replace the base value | ||
else { | ||
a[key] = b[key]; | ||
} | ||
} | ||
} | ||
/** | ||
* 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) { | ||
return heir.merge(destination.prototype, source); | ||
}, | ||
// Return the merged object | ||
return a; | ||
} | ||
/** | ||
* 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. | ||
*/ | ||
merge: function merge(destination, source) { | ||
var key; | ||
/** | ||
* 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]; | ||
for (key in source) { | ||
if (heir.hasOwn(source, key)) { | ||
destination[key] = source[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); | ||
} | ||
/** | ||
* Shortcut for `Object.prototype.hasOwnProperty`. | ||
* | ||
* 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); | ||
} | ||
else { | ||
// It is not an array, it is a plain function | ||
// Merge it's prototype into this one | ||
merge(fn.prototype, clone(parent.prototype)); | ||
} | ||
// 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; | ||
}); | ||
} | ||
else if (typeof module === 'object' && module.exports) { | ||
module.exports = ns; | ||
} | ||
else { | ||
this.heir = ns; | ||
} | ||
}.call(this)); | ||
return heir; | ||
})); |
{ | ||
"name": "heir", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "Makes prototypical inheritance easy and robust", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -8,16 +8,29 @@ # Heir | ||
```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 | ||
// Mix in some functionality enhancing objects. | ||
heir.mixin(Sub, events); | ||
heir.mixin(Sub, pooling); | ||
// Change the original method. | ||
Sub.prototype.foo = function () { | ||
return [ | ||
'Sub#foo', | ||
this._super.foo.call(this) | ||
].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" | ||
``` | ||
@@ -27,2 +40,10 @@ | ||
## 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 | ||
@@ -50,3 +71,3 @@ | ||
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. | ||
@@ -64,2 +85,5 @@ ## License (MIT) | ||
[npm]: https://npmjs.org/ | ||
[bower]: http://bower.io/ | ||
[bower]: http://bower.io/ | ||
[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/ |
@@ -5,3 +5,2 @@ (function () { | ||
// Set up the Jasmine environment | ||
var jasmineEnv = jasmine.getEnv(); | ||
@@ -18,231 +17,150 @@ jasmineEnv.updateInterval = 1000; | ||
// 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); | ||
}); | ||
describe('heir.createObject', function () { | ||
it('returns a new empty object when passed one', function () { | ||
var source = {}; | ||
var result = heir.createObject(source); | ||
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); | ||
expect(result).toEqual({}); | ||
expect(result).not.toBe(source); | ||
}); | ||
it('returns false on arrays', function() { | ||
expect(heir.isObject([])).toBe(false); | ||
var test = []; | ||
expect(heir.isObject(test)).toBe(false); | ||
}); | ||
it('inserts the source object into the prototype', function () { | ||
var source = { | ||
foo: true | ||
}; | ||
var result = heir.createObject(source); | ||
it('returns false on null', function() { | ||
expect(heir.isObject(null)).toBe(false); | ||
var test = null; | ||
expect(heir.isObject(test)).toBe(false); | ||
expect(result).toEqual(source); | ||
expect(result).not.toBe(source); | ||
expect(result.hasOwnProperty('foo')).toBe(false); | ||
expect(result.foo).toBe(true); | ||
}); | ||
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.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}); | ||
}); | ||
describe('heir.inherit', function () { | ||
it('causes a class to inherit a method', function () { | ||
var Source = function () {}; | ||
Source.prototype.foo = function () {}; | ||
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}); | ||
}); | ||
var Destination = function () {}; | ||
heir.inherit(Destination, Source); | ||
Destination.prototype.bar = function() {}; | ||
it('merges objects recursively', function() { | ||
var chk = heir.merge({ | ||
nest: { | ||
foo: true, | ||
bar: true | ||
} | ||
}, { | ||
nest: { | ||
bar: false | ||
} | ||
}); | ||
var result = new Destination(); | ||
expect(chk).toEqual({ | ||
nest: { | ||
foo: true, | ||
bar: false | ||
} | ||
}); | ||
expect(Destination.prototype.hasOwnProperty('foo')).toBe(false); | ||
expect(Destination.prototype.hasOwnProperty('bar')).toBe(true); | ||
expect(result.foo).toBeDefined(); | ||
expect(result.bar).toBeDefined(); | ||
}); | ||
}); | ||
describe('heir.clone', function() { | ||
it('clones objects', function() { | ||
var orig = { | ||
foo: true, | ||
bar: false | ||
it('can have methods overridden', function () { | ||
var Source = function () {}; | ||
Source.prototype.foo = function () { | ||
return 'Source#foo'; | ||
}; | ||
var cl = heir.clone(orig); | ||
var Destination = function () {}; | ||
heir.inherit(Destination, Source); | ||
Destination.prototype.foo = function() { | ||
return [ | ||
'Destination#foo', | ||
Source.prototype.foo.call(this) | ||
].join(', '); | ||
}; | ||
cl.bar = true; | ||
var source = new Source(); | ||
var destination = new Destination(); | ||
expect(orig).toEqual({ | ||
foo: true, | ||
bar: false | ||
}); | ||
expect(cl).toEqual({ | ||
foo: true, | ||
bar: true | ||
}); | ||
expect(source.foo()).toBe('Source#foo'); | ||
expect(destination.foo()).toBe('Destination#foo, Source#foo'); | ||
}); | ||
it('clones objects recursively', function() { | ||
var orig = { | ||
foo: true, | ||
bar: false, | ||
baz: { | ||
one: 100, | ||
two: 300 | ||
} | ||
}; | ||
it('is correct in the eyes of instanceof', function () { | ||
var Source = function () {}; | ||
var Destination = function () {}; | ||
heir.inherit(Destination, Source); | ||
var cl = heir.clone(orig); | ||
var source = new Source(); | ||
var destination = new Destination(); | ||
cl.bar = true; | ||
cl.baz.two = 200; | ||
cl.baz.three = 300; | ||
expect(source instanceof Source).toBe(true); | ||
expect(source instanceof Destination).toBe(false); | ||
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(destination instanceof Source).toBe(true); | ||
expect(destination instanceof Destination).toBe(true); | ||
}); | ||
}); | ||
describe('heir.inherit', function() { | ||
it('inherits a class', function() { | ||
var Base = function(){}; | ||
Base.prototype.foo = function() { | ||
return '!foo!'; | ||
}; | ||
it('has a reference to the parent in this._super', function () { | ||
var Source = function () {}; | ||
var Destination = function () {}; | ||
heir.inherit(Destination, Source); | ||
var Base2 = function(){}; | ||
Base2.prototype.baz = function() { | ||
return '!baz!'; | ||
}; | ||
var result = new Destination(); | ||
var Sub = function(){}.inherit(Base); | ||
Sub.prototype.bar = function() { | ||
return '!bar!'; | ||
}; | ||
Sub.inherit(Base2); | ||
expect(result._super).toBe(Source.prototype); | ||
}); | ||
var b = new Base(); | ||
var b2 = new Base2(); | ||
var s = new Sub(); | ||
it('can have the addition of this._super disabled', function () { | ||
var Source = function () {}; | ||
var Destination = function () {}; | ||
heir.inherit(Destination, Source, false); | ||
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!'); | ||
var result = new Destination(); | ||
expect(result._super).toBeUndefined(); | ||
}); | ||
}); | ||
it('inherits multiple classes', function() { | ||
var Base = function(){}; | ||
Base.prototype.foo = function() { | ||
return '!foo!'; | ||
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(); | ||
var Base2 = function(){}; | ||
Base2.prototype.baz = function() { | ||
return '!baz!'; | ||
}; | ||
expect(result.foo).toBeDefined(); | ||
expect(result.bar).toBeDefined(); | ||
expect(Destination.prototype.hasOwnProperty('foo')).toBe(true); | ||
expect(Destination.prototype.hasOwnProperty('bar')).toBe(true); | ||
}); | ||
}); | ||
var Sub = function(){}.inherit([Base, Base2]); | ||
Sub.prototype.bar = function() { | ||
return '!bar!'; | ||
describe('heir.hasOwn', function () { | ||
it('returns true when it is it\'s own', function () { | ||
var source = { | ||
foo: true | ||
}; | ||
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!'); | ||
expect(heir.hasOwn(source, 'foo')).toBe(true); | ||
}); | ||
it('does not have to be called through the prototype', function() { | ||
var Base = function(){}; | ||
Base.prototype.foo = function() { | ||
return '!foo!'; | ||
}; | ||
it('returns false when it is either undefined or up the prototype chain', function () { | ||
var noProperty = {}; | ||
var inChain = heir.createObject({ | ||
foo: true | ||
}); | ||
var Base2 = function(){}; | ||
Base2.prototype.baz = function() { | ||
return '!baz!'; | ||
}; | ||
expect(heir.hasOwn(noProperty, 'foo')).toBe(false); | ||
expect(inChain.foo).toBeDefined(); | ||
expect(heir.hasOwn(inChain, 'foo')).toBe(false); | ||
}); | ||
}); | ||
var Sub = function(){}; | ||
heir.inherit([Base, Base2], Sub); | ||
Sub.prototype.bar = function() { | ||
return '!bar!'; | ||
}; | ||
describe('heir.merge', function () { | ||
it('merges one object into another', function () { | ||
var a = {foo:true}; | ||
var b = {bar:true}; | ||
heir.merge(a, b); | ||
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!'); | ||
expect(a.foo).toBe(true); | ||
expect(a.bar).toBe(true); | ||
expect(b.foo).toBeUndefined(); | ||
}); | ||
}); | ||
// Run Jasmine | ||
jasmineEnv.execute(); | ||
}.call(this)); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
89511
8
85
251
1