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 standardizes
// approach already used by majority of JS frameworks today.
// First, basic usage from a real-world library (Three.js)
// Note that `initializer` method is intentionally called `initialize` instead
// of `constructor`, which is return value of the `.extend`. This avoids any
// confusions in cases where initializer method is shared between multiple
// classes or when it's prototype property is already set or is immutable.
var Color = Object.extend({
initialize: function(hex) {
hex = hex || 'ffffff'
this.r = parseInt(hex.substr(0, 2), 16)
this.g = parseInt(hex.substr(2, 2), 16)
this.b = parseInt(hex.substr(4, 2), 16)
},
r: 1, g: 1, b: 1,
copy: function() {
var color = Color()
color.r = this.r
color.g = this.g
color.b = this.b
return color
},
setRGB: function(r, g, b) {
this.r = r
this.g = g
this.b = b
},
setHSV: function(h, s, v) {
// TODO
}
})
// Usage of `new` is optional, class functions always create instances of it's
// own. For a different (rare) behavior one could always do:
// function MyColor() { ... }
// MyColor.prototype = Color.prototype
var c1 = 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);
};
// Finally, the Monster class from the current nutshell proposal
// (http://wiki.ecmascript.org/doku.php?id=harmony:classes#the_proposal_in_a_nutshell)
// ... sans unnecessary restrictions:
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!"
})
// 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