Skip to content

Instantly share code, notes, and snippets.

@Gozala
Forked from jashkenas/minimalist-classes.js
Created November 10, 2011 18:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Gozala/1355701 to your computer and use it in GitHub Desktop.
Save Gozala/1355701 to your computer and use it in GitHub Desktop.
// Here is a proposal for minimalist JavaScript classes(?), that does not
// introduces any additional syntax to the language, instead it standardizes
// approach already used by majority of JS frameworks today.
// !!! What is a PROBLEM!!!
function Dog(name) {
// Classes are for creating instances, calling them without `new` changes
// behavior, which in majority cases you need to handle, so you end up with
// additional boilerplate.
if (!(this instanceof Dog)) return new Dog(name);
this.name = name;
};
// To define methods you need to make a dance with a special 'prototype'
// property of the constructor function. This is too much machinery exposed.
Dog.prototype.type = 'dog';
Dog.prototype.bark = function bark() {
return 'Ruff! Ruff!'
};
function Pet(name, breed) {
// Once again we do our little dance
if (!(this instanceof Pet)) return new Pet(name, breed);
Dog.call(this, name);
this.breed = breed;
}
// To subclass, you need to make another special dance with special
// 'prototype' properties.
Pet.prototype = Object.create(Dog.prototype);
// If you want correct instanceof behavior you need to make a dance with
// another special `constructor` property of the `prototype` object.
Object.defineProperty(Pet.prototype, 'contsructor', { value: Pet });
// Finally you can define some properties.
Pet.prototype.call = function(name) {
return this.name === name ? this.bark() : '';
};
// !!! Simple solution !!!
// Hiding machinery requiring dance with special `prototype` and `constructor`
// properties.
var Dog = Object.extend({
// initialize is called on already created instance, so there is no need to
// test if `new` was used.
initialize: function initialize(name) {
this.name = name;
},
bark: function() {
return 'Ruff! Ruff!';
}
});
// Inheritance (subclassing) is as easy, no machinery exposed, just extend
// the base class(?).
var Pet = Dog.extend({
initialize: function(breed, name) {
// super will hide `prototype` machinery here.
Dog.prototype.initialize.call(this, name);
this.breed = breed;
},
call: function(name) {
return this.name === name ? this.bark() : '';
}
});
var pet = Pet('Labrador', 'Benzy')
pet.call('doggy') // ''
pet.call('Benzy') // 'Ruff! Ruff!'
// In some programs recombining reusable pieces of code is a better option:
var RGB = {
red: function red() {
return parseInt(this.color.substr(0, 2), 16);
},
green: function green() {
return parseInt(this.color.substr(2, 2), 16);
},
blue: function blue() {
return parseInt(this.color.substr(4, 2), 16);
}
};
var CMYK = {
black: function black() {
var color = Math.max(Math.max(this.red(), this.green()), this.blue());
return (1 - color / 255).toFixed(4);
},
magenta: function magenta() {
var K = this.black();
return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
},
yellow: function yellow() {
var K = this.black();
return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
},
cyan: function cyan() {
var K = this.black();
return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
}
};
// Composing `Color` prototype out of reusable components:
var Color = Object.extend(RGB, CMYK, {
initialize: function initialize(hex) {
this.color = hex;
}
});
var pink = Color('FFC0CB')
// RGB
pink.red() // 255
pink.green() // 192
pink.blue() // 203
// CMYK
pink.magenta() // 0.2471
pink.yellow() // 0.2039
pink.cyan() // 0.0000
var Pixel = Color.extend({
initialize: function initialize(x, y, color) {
Color.initialize.call(this, color);
this.x = x;
this.y = y;
},
toString: function toString() {
return this.x + ':' + this.y + '@' + this.color;
}
});
// To create a class with its prototype chain set correctly:
var Fox = Animal.extend({
// properties
});
// Note that "Animal" here is a class object (Animal = Object.extend(...)) in
// its own right. Fox.prototype is set to an instance of Animal that has been
// constructed without calling its constructor function -- this is the
// usual two-step setting-up-a-prototype shuffle.
// There is no special syntax for setting class-level properties, as they are
// relatively rare and simple to add anyway (Just add them to the class object
// itself):
Fox.CONSTANT = value;
// Note that `extend` is just a function and no special forms are required.
// You can be fully dynamic when creating a class:
var Student = Object.extend(objectContainingStudentProperties);
// Also extend can take more then one set of properties, all the properties
// will become properties of the prototype, on conflicts right hand one wins:
var Protester = Object.extend(YoungAdult, WorkEthic, Idealism, {
student: true
});
// Similarly, anonymous classes are equally possible:
animals.push(Object.extend());
var subclass = function(parent) {
return parent.extend();
};
// Naturally, classes can be built up programmatically in this fashion.
var generateModelClass = function(columns) {
var definition = {};
columns.forEach(function(col) {
definition['get' + col] = function() {
return this[col];
};
definition['set' + col] = function(value) {
return this[col] = value;
};
});
return Model.extend(definition);
};
// Examples from classes proposal http://wiki.ecmascript.org/doku.php?id=harmony:classes
// inherit behavior from Mesh
var SkinnedMesh = THREE.Mesh.extend({
initialize: function(geometry, materials) {
// call the superclass initializer
THREE.Mesh.initialize.call(this, geometry, materials);
// initialize instance properties
this.identityMatrix = new THREE.Matrix4();
this.bones = [];
this.boneMatrices = [];
// ...
},
// define an overridden update() method
update: function(camera) {
// ...
// call base version of same method
THREE.Mesh.prototype.update.call(this);
}
})
var Monster = Object.extend({
initialize: function(name, health) {
this.name = name;
this.health = health;
},
attack: function(target) {
log("The monster attacks " + target);
},
isAlive: function() {
return this.health > 0;
},
setHealth: function(value) {
if (value < 0) {
throw new Error("Health must be non-negative.");
}
this.health = value;
},
numAttacks: 0,
attackMessage: "The monster hits you!"
});
// Adding "static" constructor properties is easy without special syntax.
Monster.allMonsters = [];
var Point = Object.extend({
initialize: function(x, y) {
this.getX = function() { return x; };
this.getY = function() { return y; };
},
toString: function() {
return '<' + this.getX() + ',' + this.getY() + '>';
}
});
// I think that's about the run of it. Note what is left out: public / private /
// static / frozen / const properties. If JS.next will have them, then they must
// be valid prefixes for object literals no magic required for classes.
// There is nothing new here, just standardizing what libraries already do
// today, in a similar manner as Function.prototype.bind was standardized for
// ES5.
//
// Since there is nothing new here legacy engines can be easily shimmed:
Object.defineProperty(Object, 'extend', {
value: function() {
var properties = {}
var statics = {}
Array.prototype.slice.call(arguments).forEach(function(source) {
Object.getOwnPropertyNames(source).forEach(function(name) {
properties[name] = Object.getOwnPropertyDescriptor(source, name)
})
})
Object.getOwnPropertyNames(this).forEach(function(name) {
if (!(name in Function.prototype))
statics[name] = Object.getOwnPropertyDescriptor(this, name)
}, this)
var prototype = statics.prototype = Object.create(this.prototype, properties)
var constructor = function () {
var instance = Object.create(prototype)
prototype.initialize.apply(instance, arguments)
return instance
}
Object.defineProperties(constructor, statics)
return constructor
}
})
// More robustness can also be easily enforced by a given frameworks:
function Robust() {
}
Robust.extend = function extend() {
var value = Object.extend.apply(Robust, arguments)
Object.freeze(value.prototype)
Object.freeze(value)
return value
}
Object.freeze(Robust.prototype)
Object.freeze(Robust)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment