simile
v0.0.5
Published
A small library which attempts to push prototypal inheritance to its natural conclusions in JavaScript. For ECMAScript 5.
Downloads
4
Readme
Simile
A small library which attempts to push prototypal inheritance to its natural conclusions in JavaScript (for ECMAScript 5).
This library provides a few basic functions which are oriented toward making prototypal inheritence simple and straight-forward.
Getting Started
Node
Installation
npm install simile
Then...
var simile = require('simile'),
like = simile.like,
beget = simile.beget;
// ...
Browser
Basic
Download simile.js
and serve it in a <script>
tag.
<script type="text/javascript" src="path/to/simile.js"></script>
<script type="text/javascript">
var like = simile.like,
beget = simile.beget;
</script>
Inside another script, you can use secrets.create()
to create a secret coupler (see below).
AMD
It's also possible to import simile as an AMD module.
require([ 'path/to/simile' ], function(simile) {
var like = simile.like,
beget = simile.beget;
// ...
});
Use
Inheritance (like
)
To create an object use like
.
var Pizza = like();
// Pizza is an object which has no prototype.
Object.getPrototypeOf(Pizza); // => null
To create an object which inherits from another object, use like
again.
var CheesePizza = like(Pizza);
// CheesePizza inherits from Pizza
The like
function accepts a second optional argument, a map of properties to add to the new object.
var PepperoniPizza = like(Pizza, {
toppings: [ 'pepperoni' ]
});
PepperoniPizza.toppings; // => [ 'pepperoni' ]
var MediumPepperoniPizza = like(PepperoniPizza, {
diameter: frozen('22cm')
});
MediumPepperoniPizza.diameter; // => '22cm'
MediumPepperoniPizza.toppings; // => [ 'pepperoni' ]
These properties are non-enumerable.
MediumPepperoniPizza.slices = 8;
for(var key in MediumPepperoniPizza) {
console.log(key);
}
// Only logs 'slices'. The other properties ('diameter', 'toppings') are not logged because
// they are non-enumerable.
These properties are, however, writable and configurable (by default).
MediumPepperoniPizza.diameter = '20cm';
delete MediumPepperoniPizza.toppings;
MediumPepperoniPizza.diameter; // => '20cm'
MediumPepperoniPizza.toppings; // => undefined
Properties inherit a false writable or configurable state.
FrozenPizza = like(PepperoniPizza, Object.freeze({
thaw: function() { console.log('thawing!'); }
}));
FrozenPizza.thaw = 1; // Error: `thaw` is non-writable
delete FrozenPizza.thaw; // Error: `thaw` is non-configurable
like
is like Object.create
, except it has an easier, cleaner
syntax with reasonable defaults for the property descriptors.
var John = like(Mike, {
firstName: 'John'
});
John.getName(); // => 'John Campbell'
Like Object.create
, like
can be used on null
to create an object with no inheritance.
var x = like(null);
'hasOwnProperty' in x; // => false
// x does not inherit from Object (or anything)
sealed
and frozen
A property can be set to be non-configurable or non-writable using sealed
and frozen
. The former makes a property non-configurable, while the latter makes a property both non-configurable and non-writable.
var Canine = like(),
Fox = like(Canine, {
color: sealed('red'),
trait: frozen('sneaky')
});
// `color` is writable
Fox.color = 'gray';
// But it is not configurable
Object.defineProperty(Fox, 'color', { enumerable: true }); // Error
// And `trait` is neither writable nor configurable
Fox.trait = 'lazy'; // Error
Object.defineProperty(Fox, 'trait', { enumerable: true }); // Error
beget
beget
is like
+ init
. It calls like
on the first argument and passes any other arguments to an object's init
method (if present).
var Person = like(null, {
init: function(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
},
getName: function() {
return this.firstName + ' ' + this.lastName;
}
});
var Mike = beget(Person, 'Mike', 'Campbell');
Mike.getName(); // => 'Mike Campbell'
extend
extend
can be used to extend the properties of an object.
var Santa = like();
extend(Santa, {
speak: function() {
return 'Ho ho ho!';
}
});
Santa.speak(); // => 'Ho ho ho!'
Properties added with extend
are non-enumerable.
mixin
mixin
can be used to mix one object into another. It differs from extend
in two ways: (1) properties remain enumerable if they are enumerable on the mixin, and (2) inherited properties are mixed in (up to a common parent).
var Santa = like();
mixin(Santa, {
speak: function() {
return 'Ho ho ho!';
}
});
var descriptor = Object.getOwnPropertyDescriptor(Santa, 'speak');
descriptor.enumerable; // => true
descriptor.writable; // => true
descriptor.configurable; // => true
var Holidayer = like(null, {
shout: function() {
return 'Merry Christmas!';
}
});
var Elf = like(Holidayer, {
makeToys: function() {
return 'Fa la la!';
}
});
mixin(Santa, Elf);
Santa.shout(); // => 'Merry Christmas!'
Santa.makeToys(); // => 'Fa la la!'
inherits
The inherits
function can be used to check inheritance
(instanceof
will not work because you're not checking against a constructor).
inherits(PepperoniPizza, Pizza); // => true
inherits(MediumPepperoniPizza, Pizza); // => true
inherits(PepperoniPizza, Santa); // => false
Private Properties
Secrets or WeakMaps can be used alongside beget to associate private state with objects.
var Purse = (function() {
var $ = createSecret();
return like(null, {
init: function(balance) {
if (Object(this) !== this)
throw new TypeError('Object expected');
$(this).balance = balance | 0;
},
deposit: function deposit(from, amount) {
if (!('balance' in $(this)))
throw new TypeError('Deposit must be called on a Purse.');
if (!('balance' in $(from))
throw new TypeError('Another Purse is required to make a deposit.');
$(from).balance -= amount;
$(this).balance += amount;
},
get balance() {
return $(this).balance;
}
});
})();
var sally = beget(Purse, 100),
jane = beget(Purse, 250);
sally.deposit(jane, 50);
console.log(
sally.balance, // => 150
jane.balance // => 200
);
Example
var Vehicle = like(null, {
init: function(name) {
this.name = name;
},
speed: 0,
acceleration: 10,
start: function() {
this.speed = this.acceleration;
console.log(this.name, 'started', this.speed);
},
stop: function() {
this.speed = 0;
console.log(this.name, 'stopped', this.speed);
},
accelerate: function() {
this.speed += this.acceleration;
console.log(this.name, this.speed);
}
});
// MiniVan inherits all of Vehicle's properties
var MiniVan = like(Vehicle, {
acceleration: 6
});
// Racecar also inherits all of Vehicle's properties, but it overrides `init`.
var Racecar = like(Vehicle, {
init: function(name) {
Vehicle.init.call(this, name);
this.acceleration = Math.floor(Math.random() * 20 + 40);
}
});
// peacockVan inherits from MiniVan
var peacockVan = beget(MiniVan, 'peacock');
peacockVan.start(); // => peacock started 6
peacockVan.accelerate(); // => peacock 12
peacockVan.accelerate(); // => peacock 18
peacockVan.stop(); // => peacock stopped 0
// wallaceCar inherits from Racecar
var wallaceCar = beget(Racecar, 'wallace');
// andyCar also inherits from Racecar
var andyCar = beget(Racecar, 'andy');
wallaceCar.start(); // => wallace started [random number]
andyCar.start(); // => andy started [random number]
wallaceCar.accelerate(); // => wallace [random number]
andyCar.accelerate(); // => andy [random number]
wallaceCar.accelerate(); // => wallace [random number]
andyCar.accelerate(); // => andy [random number]
wallaceCar.stop(); // => wallace [random number]
andyCar.stop(); // => andy [random number]