nevis
v0.5.0
Published
Makes JavaScript more object-orientated
Downloads
132
Maintainers
Readme
888b 888 d8b
8888b 888 Y8P
88888b 888
888Y88b 888 .d88b. 888 888 888 .d8888b
888 Y88b888 d8P Y8b 888 888 888 88K
888 Y88888 88888888 Y88 88P 888 "Y8888b.
888 Y8888 Y8b. Y8bd8P 888 X88
888 Y888 "Y8888 Y88P 888 88888P'
Nevis brings more of the Object-Orientated Programming (OOP) model to JavaScript.
Install
Install using the package manager for your desired environment(s):
$ npm install --save nevis
# OR:
$ bower install --save nevis
You'll need to have at least Node.js and you'll only need Bower if you want to
install that way instead of using npm
. While equals should be compatible with all versions of
Node.js, it is only tested against version 4 and above.
If you want to simply download the file to be used in the browser you can find them below:
- Development Version (81kb - Source Map)
- Production Version (9.2kb - Source Map)
If you're only wanting support for inheritance, you can use the lite version instead:
- Development Version - Lite (7.5kb - Source Map)
- Production Version - Lite (981b - Source Map)
API
Inheritance
Nevis' primary function is to provide clean implementation of single inheritance.
Available in lite version
Nevis.extend([name][, constructor][, prototype][, statics])
Extends the constructor to which this method is associated with the prototype
and/or statics
provided.
If name
is provided, it will be used as the class name and can be accessed via a special class_
property on the
child constructor, otherwise the class name of the super constructor will be used instead. The class name may also be
used string representation for instances of the child constructor (via toString
), but this is not applicable to the
lite version of Nevis.
If constructor
is provided, it will be used as the constructor for the child, otherwise a simple constructor which
only calls the super constructor will be used instead.
The super constructor can be accessed via a special super_
property on the child constructor.
It is very flexible and can be used to extend classes:
var Base = Nevis.extend('Base', function(attributes) {
this.attributes = attributes || {};
}, {
getAttribute: function(name) {
return this.attributes[name];
},
setAttribute: function(name, value) {
this.attributes[name] = value;
}
});
var Person = Base.extend('Person', function(name, attributes) {
Person.super_.call(this, attributes);
this.name = name;
Person.people.push(this);
}, {
greet: function(name) {
return 'Hello ' + name + ', my name is ' + this.name;
}
}, {
people: []
});
var bob = new Person('Bob', { age: 58 });
bob.name;
//=> "Bob"
bob.greet('Suzie');
//=> "Hello Suzie, my name is Bob"
bob.getAttribute('age');
//=> 58
Person.people;
//=> [ "Bob" ]
Also, this can be used to extend external classes such as EventEmitter
:
var EventEmitter = require('events').EventEmitter;
var Nevis = require('nevis');
var Events = Nevis.extend('Events', function() {
EventEmitter.call(this);
}, EventEmitter.prototype, EventEmitter);
However, this last approach has the caveats of instanceof
not identifying this kind of inheritance and super_
will
only reference the constructor that is extended.
Equality
Nevis adds a means of testing whether an object is equal to another that's more advanced than just ==
or ===
.
Unavailable in lite version
Nevis.prototype.equals(obj)
Returns whether the instance is "equal to" the specified obj
.
This method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value
x
,x.equals(x)
should returntrue
. - It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
. - It is transitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
. - It is consistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified. - For any non-null reference value
x
,x.equals(null)
should returnfalse
.
The default implementation of this method is the most discriminating possible equivalence relation on objects; that is,
for any non-null reference values x
and y
, this method returns true
if, and only if, x
and y
are exactly equal
(x === y
has the value true
).
Please note that it is generally necessary to override the Nevis.prototype.hashCode
method whenever this method is
overridden, so as to maintain the general contract for the Nevis.prototype.hashCode
method, which states that equal
objects must have equal hash codes.
var Person = Nevis.extend('Person', function(name, age) {
this.name = name;
this.age = age;
}, {
greet: function(name) {
return 'Hello ' + name + ', my name is ' + this.name;
}
});
var bob = new Person('Bob', 58);
var suzie = new Person('Suzie', 30);
bob.equals(bob);
//=> true
bob.equals(new Person('Bob', 58));
//=> false
bob.equals(suzie);
//=> false
bob.equals(null);
//=> false
bob.equals(undefined);
//=> false
Continue reading for information on how to create a good equals for complex classes using Nevis.EqualsBuilder
.
Nevis also provides a null-safe static method for testing the equality of two values of any type.
Nevis.equals(value, other[, options])
Returns whether the specified value
is "equal to" the other
provided using the given options
.
Consequently, if both arguments are null
, true
is returned and if exactly one argument is null
, false
is
returned. Otherwise, this method implements an equivalence relation on non-null object references in the same way as
Nevis.prototype.equals
.
If neither value is null
and both are not exactly (strictly) equal, this method will first check whether value
has a
method named "equals" and, if so, return the result of calling that method with other
passed to it. If no "equals"
method exists on value
or if the ignoreEquals
option is enabled, it will attempt to test the equality internally
based on their type.
Plain objects are tested recursively for their properties and collections (e.g. arrays) are also tested recursively for their elements.
The options
parameter is entirely optional and supports the following:
| Option | Type | Default | Description |
| ----------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| filterProperty
| Function | N/A | A function to be called to filter properties for objects to determine whether they should be tested. Not called for method properties when ignoreMethods
is true
. |
| ignoreCase
| Boolean | false
| Whether to ignore case when testing equality for strings. |
| ignoreEquals
| Boolean | false
| Whether to ignore the equals
method on value
, when present |
| ignoreInherited
| Boolean | false
| Whether to ignore inherited properties when testing equality for objects. |
| ignoreMethods
| Boolean | false
| Whether to ignore method properties when testing equality for objects. |
var obj = {
foo: 'bar',
doSomething: function() {
return 123;
}
};
Nevis.equals(obj, obj);
//=> true
Nevis.equals(obj, {
foo: 'bar',
doSomething: function() {
return 321;
}
});
//=> false
Nevis.equals(obj, {
foo: 'bar',
doSomething: function() {
return 321;
}
}, { ignoreMethods: true });
//=> true
Nevis.equals(bob, bob);
//=> true
Nevis.equals(bob, new Person('Bob', 58));
//=> false
Nevis.equals(bob, suzie);
//=> false
Nevis.equals('foo', 'foo');
//=> true
Nevis.equals('foo', 'FOO');
//=> false
Nevis.equals('foo', 'FOO', { ignoreCase: true });
//=> true
Nevis.equals(NaN, NaN);
//=> true
Nevis.equals(null, null);
//=> true
Nevis.equals(undefined, undefined);
//=> true
Nevis.equals(null, undefined);
//=> false
Nevis.equals(bob, null);
//=> false
Nevis.equals(bob, undefined);
//=> false
Nevis provides a builder to support creating good equals for complex classes.
Unavailable in lite version
Nevis.EqualsBuilder()
The best way to describe it is by using an example that demonstrates it's API:
var Animal = Nevis.extend('Animal', function(species) {
this.species = species;
}, {
equals: function(obj) {
if (obj == null) {
return false;
}
return new Nevis.EqualsBuilder()
.append(this.species, obj.species)
.build();
}
});
var Human = Nevis.extend('Human', function(name, age) {
Human.super_.call(this, 'human');
this.name = name;
this.age = age;
}, {
equals: function(obj) {
if (obj == null) {
return false;
}
return new Nevis.EqualsBuilder()
.appendSuper(Human.super_.prototype.equals.call(this, obj))
.append(this.name, obj.name)
.append(this.age, obj.age)
.build();
}
});
var Lion = Nevis.extend('Lion', function(name, age) {
Lion.super_.call(this, 'lion');
this.name = name;
this.age = age;
}, {
equals: function(obj) {
if (obj == null) {
return false;
}
return new Nevis.EqualsBuilder()
.appendSuper(Lion.super_.prototype.equals.call(this, obj))
.append(this.name, obj.name)
.append(this.age, obj.age)
.build();
}
});
var humanBob = new Human('Bob', 58);
var humanSuzie = new Human('Suzie', 30);
var lionBob = new Lion('Bob', 58);
humanBob.equals(humanBob);
//=> true
humanBob.equals(new Human('Bob', 58));
//=> true
humanBob.equals(lionBob);
//=> false
humanBob.equals(humanSuzie);
//=> false
humanSuzie.name = 'Bob';
humanSuzie.age = 58;
humanBob.equals(humanSuzie);
//=> true
humanBob.equals(null);
//=> false
humanBob.equals(undefined);
//=> false
When using Nevis.EqualsBuilder
in an equals
method, it's recommended to use Nevis.HashCodeBuilder
in the
hashCode
method on the same object.
Hash Codes
Nevis introduces JavaScript to the Java concept of hash codes.
Unavailable in lite version
Nevis.prototype.hashCode()
Returns the hash code for the instance. This method is supported for the benefit of hash tables.
The general contract of hashCode
is:
- Whenever it is invoked on the same instance more than once during an execution of an application, the
hashCode
method must consistently return the same number, provided no information used to generate the hash code on the instance is modified. This number need not remain consistent from one execution of an application to another execution of the same application. - If two instances are equal, that calling the
hashCode
method on each of the two instances must produce the same number result. - It is not required that if two instances are unequal, that calling the
hashCode
method on each of the two instances must produce distinct number results. However, the programmer should be aware that producing distinct number results for unequal instances may improve the performance of hash tables.
The default implementation of this method will attempt to generate the hash code based on all of the fields on the
instance. Please note that it is generally necessary to override the Nevis.prototype.equals
method whenever this
method is overridden, so as to maintain the above contract where equal objects must have equal hash codes.
var Person = Nevis.extend('Person', function(name) {
this.name = name;
}, {
greet: function(name) {
return 'Hello ' + name + ', my name is ' + this.name;
}
});
var bob = new Person('Bob');
var suzie = new Person('Suzie');
bob.hashCode();
//=> -469006311
suzie.hashCode();
//=> -388699942
bob.hashCode() === new Person('Bob').hashCode();
//=> true
Continue reading for information on how to generate hash codes for complex classes using Nevis.HashCodeBuilder
.
Nevis also provides a null-safe static method for generating a hash code for any value.
Nevis.hashCode(value[, options])
Returns a hash code for the specified value
using the options
provided. This method is supported for the benefit of
hash tables and it has the same general contract as Nevis.prototype.hashCode
.
If value
is null
, this method will always return zero. Otherwise, it will check whether value
has a method named
"hashCode" and, if so, return the result of calling that method. If no "hashCode" method exists on value
or if the
ignoreHashCode
option is enabled, it will attempt to generate the hash code internally based on its type.
Plain objects are hashed recursively for their properties and collections (e.g. arrays) are also hashed recursively for their elements.
The options
parameter is entirely optional and supports the following:
| Option | Type | Default | Description |
| ----------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| allowCache
| Boolean | true
| Whether to allow hash codes generated for certain immutable types to be cached for faster re-generation. |
| filterProperty
| Function | N/A | A function to be called to filter properties for objects to determine whether they should be included. Not called for method properties when ignoreMethods
is true
. |
| ignoreHashCode
| Boolean | false
| Whether to ignore the hashCode
method on value
, when present |
| ignoreInherited
| Boolean | false
| Whether to ignore inherited properties when generating hash codes for objects. |
| ignoreMethods
| Boolean | false
| Whether to ignore method properties when generating hash codes for objects. |
var obj = {
foo: 'bar',
doSomething: function() {
return 123;
}
};
Nevis.hashCode(obj);
//=> 1343675198
Nevis.hashCode(obj, { ignoreMethods: true });
//=> 61653
Nevis.hashCode(bob);
//=> -469006311
Nevis.hashCode('foo');
//=> 101574
Nevis.hashCode(null);
//=> 0
Nevis.hashCode(undefined);
//=> 0
Nevis provides a builder to support generating hash codes for complex classes.
Unavailable in lite version
Nevis.HashCodeBuilder([initial][, multipler])
Ideally the initial
value and multiplier
should be different for each class, however, this is not vital. Prime
numbers are preferred, especially for multiplier
.
If specified, both initial
and multiplier
must be odd numbers.
The best way to describe it is by using an example that demonstrates it's API:
var Animal = Nevis.extend('Animal', function(species) {
this.species = species;
}, {
hashCode: function() {
return new Nevis.HashCodeBuilder()
.append(this.species)
.build();
}
});
var Human = Nevis.extend('Human', function(name, age) {
Human.super_.call(this, 'human');
this.name = name;
this.age = age;
}, {
hashCode: function() {
return new Nevis.HashCodeBuilder()
.appendSuper(Human.super_.prototype.hashCode.call(this))
.append(this.name)
.append(this.age)
.build();
}
});
var Lion = Nevis.extend('Lion', function(name, age) {
Lion.super_.call(this, 'lion');
this.name = name;
this.age = age;
}, {
hashCode: function() {
return new Nevis.HashCodeBuilder()
.appendSuper(Lion.super_.prototype.hashCode.call(this))
.append(this.name)
.append(this.age)
.build();
}
});
var humanBob = new Human('Bob', 58);
var humanSuzie = new Human('Suzie', 30);
var lionBob = new Lion('Bob', 58);
humanBob.hashCode();
//=> 1795252862788
humanBob.hashCode() === new Human('Bob', 58).hashCode();
//=> true
lionBob.hashCode();
//=> -79783715387
humanSuzie.hashCode();
//=> 1908159460360
humanSuzie.name = 'Bob';
humanSuzie.age = 58;
humanBob.hashCode() === humanSuzie.hashCode();
//=> true
When using Nevis.HashCodeBuilder
in a hashCode
method, it's recommended to use Nevis.EqualsBuilder
in the equals
method on the same object.
String Representations
Nevis builds on top of JavaScript's toString
method to make it easier to use.
Unavailable in lite version
Nevis.prototype.toString()
Returns a string representation of the instance.
In general, this method returns a string that "textually represents" the instance. The result should be a concise but informative representation that is easy for a person to read.
The default implementation of this method will return a string consisting of the instance's class name, the at-sign
character (@
), and the hexadecimal representation of the hash code of the instance.
var RandomHolder = Nevis.extend('RandomHolder', function() {
this.value = Math.random() * 100;
})
var random = new RandomHolder();
random.toString();
//=> "RandomHolder@f9fa743"
var UnnamedObject = Nevis.extend({
foo: 'bar',
fu: 'baz'
});
var unnamed = new UnnamedObject();
unnamed.toString();
//=> "Nevis@-3fff8d24"
Nevis also provides a null-safe static method for getting string representations of any value.
Nevis.toString(value)
Returns the result of calling the toString
method on the specified value
when it is non-null.
If value is null
or undefined
, this method will return "null"
or "undefined"
respectively.
Nevis.toString(random);
//=> "RandomHolder@f9fa743"
Nevis.toString(123);
//=> "123"
Nevis.toString(null);
//=> "null"
Nevis.toString(undefined);
//=> "undefined"
Why Nevis?
Because we love Scotland and it is named after Ben Nevis since we felt like we conquered a mountain when creating this library.
Migrating from Oopsy
If you've been using Oopsy (the former name for Nevis), then you should find migrating to Nevis really easy. All you really need to do is find and replace "Oopsy" with "Nevis" and you'll have all of the old functionality and more.
If you don't want all of the additional functionality and only care about inheritance, you can use the lite version of Nevis.
Bugs
If you have any problems with Nevis or would like to see changes currently in development you can do so here.
Contributors
If you want to contribute, you're a legend! Information on how you can do so can be found in CONTRIBUTING.md. We want your suggestions and pull requests!
A list of Nevis contributors can be found in AUTHORS.md.
License
See LICENSE.md for more information on our MIT license.