public
Created — forked from jashkenas/minimalist-classes.js

  • Download Gist
minimalist-classes.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
// 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)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.