I've watched the video from this question on class-free OOP several times now, and I'm having trouble applying this to a real world example.
Crockford's new constructor pattern looks like this:
function constructor(spec) {
let {member} = spec,
{other} = other_constructor(spec),
method = function () {
// accesses member, other, method, spec
};
return Object.freeze({
method,
other,
});
}
Where spec
is an options hash and the resulting object exposes methods which close over all of the internal members. Ignoring the destructuring (as this can be done in long-form in present-day JS,) how do I apply this pattern in a real world example?
I currently have a library with a base class Module(model, id)
where model
is some bootstrapping data.
// constructor, original flavor
function Module(model, id) {
this.id = id;
this.model = model;
}
I then have several flavors of modules which inherit from this parent Module
. Under Crockford's pattern, I would instead have this as a "constructor":
// constructor, Crockford's Cool Ranch
function module(spec) {
let id = spec.id,
model = spec.model;
return Object.freeze({});
}
How do I use Crockford's pattern (which doesn't seem to use inheritance at all, but rather composing an object from multiple sources) to share this basic construction among multiple flavors of modules?
I know that id
and model
will become local variables in the "constructor" of each module; I am essentially asking how to avoid repeating model = spec.model
for each flavor of module using Crockford's pattern.
What Crockford calls “class-free inheritance” is actually still prototypal inheritance. Actually, there are two types of prototypal inheritance mechanisms:
This is how I traditionally write object-oriented JavaScript code:
var Aircraft = defclass({
constructor: function (model, speed) {
this.model = model;
this.speed = speed;
},
describeAircraft: function () {
alert("The " + this.model + " flies at " + this.speed + " speed.");
}
});
var FighterAircraft = extend(Aircraft, {
constructor: function (model, speed, radar) {
Aircraft.call(this, model, speed);
this.radar = radar;
},
describeFighterAircraft: function () {
this.describeAircraft();
alert("It has a " + this.radar + " radar signature.");
}
});
var superFlanker = new FighterAircraft("Super Flanker", "Mach 2.25", "low");
superFlanker.describeFighterAircraft();
<script>
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
function extend(constructor, properties) {
var prototype = Object.create(constructor.prototype);
for (var name in properties) prototype[name] = properties[name];
return defclass(prototype);
}
</script>
This is how Crockford advocates to write object-oriented JavaScript code:
var superFlanker = FighterAircraft({
model: "Super Flanker",
speed: "Mach 2.25",
radar: "low"
});
superFlanker.describeFighterAircraft();
<script>
function Aircraft(spec) {
var model = spec.model;
var speed = spec.speed;
function describeAircraft() {
alert("The " + model + " flies at " + speed + " speed.");
}
return Object.freeze({
model: model,
speed: speed,
describeAircraft: describeAircraft
});
}
function FighterAircraft(spec) {
var aircraft = Aircraft(spec);
var model = spec.model;
var speed = spec.speed;
var radar = spec.radar;
function describeFighterAircraft() {
aircraft.describeAircraft();
alert("It has a " + radar + " radar signature.");
}
return Object.freeze({
model: model,
speed: speed,
radar: radar,
describeFighterAircraft: describeFighterAircraft
});
}
</script>
Crockford's method of concatenative prototypal inheritance has a lot of repetition. An alternative is:
var aircraft = mixin({
describeAircraft: function () {
alert("The " + this.model + " flies at " + this.speed + " speed.");
}
});
var fighterAircraft = mixin(aircraft, {
describeFighterAircraft: function () {
this.describeAircraft();
alert("It has a " + this.radar + " radar signature.");
}
});
var superFlanker = fighterAircraft({
model: "Super Flanker",
speed: "Mach 2.25",
radar: "low"
});
superFlanker.describeFighterAircraft();
<script>
function mixin() {
var length = arguments.length;
var index = 0;
while (index < length) {
var properties = arguments[index++];
for (var name in properties)
constructor[name] = properties[name];
}
return Object.freeze(constructor);
function constructor(object) {
for (var name in constructor)
object[name] = constructor[name];
return Object.freeze(object);
}
}
</script>
this
Yes, you can use mixins without using this
. However, I don't see why you would want to:
var aircraft = mixin({
describeAircraft: function (aircraft) {
alert("The " + aircraft.model + " flies at " +
aircraft.speed + " speed.");
}
});
var fighterAircraft = mixin(aircraft, {
describeFighterAircraft: function (fighterAircraft) {
fighterAircraft.describeAircraft();
alert("It has a " + fighterAircraft.radar + " radar signature.");
}
});
var superFlanker = fighterAircraft({
model: "Super Flanker",
speed: "Mach 2.25",
radar: "low"
});
superFlanker.describeFighterAircraft();
<script>
function mixin() {
var length = arguments.length;
var index = 0;
while (index < length) {
var properties = arguments[index++];
for (var name in properties)
constructor[name] = properties[name];
}
return Object.freeze(constructor);
function constructor(object) {
for (var name in constructor) {
var value = constructor[name];
object[name] = typeof value === "function" ?
value.bind(null, object) : value;
}
return Object.freeze(object);
}
}
</script>
There are many reasons to prefer composition over inheritance:
The only disadvantage that I can think of is that if the prototype is modified then the change will not be reflected on its instances. However, there's no good reason to change the prototype anyway. Hence, my mixins are all frozen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With