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

less minimalism, richer leather

  • 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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
// A response to jashkenas's fine proposal for minimalist JavaScript classes.
 
// Harmony always stipulated classes as sugar, so indeed we are keeping current
// JavaScript prototype semantics, and classes would only add a syntactic form
// that can desugar to ES5. This is mostly the same assumption that Jeremy
// chose, but I've stipulated ES5 and used a few accepted ES.next extensions.
 
// Where I part company is on reusing the object literal. It is not the syntax
// most classy programmers expect, coming from other languages. It has annoying
// and alien overhead, namely colons and commas. For JS community members who
// don't want classes, either proposal is "bad", so let's focus on the class
// fans who will use this feature.
//
// Therefore I propose giving classes their own new kind of body syntax, which
// is neither an object literal nor a function body. The main advantage is that
// syntax for class elements can be tuned to usability, even as it desugars to
// kernel semantics drawn from ES5 extended by both the super and private names
// proposals.
 
// Not shown below is the super proposal at
//
// http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super
//
// which composes as expected when 'super' is used in methods defined in a
// class instead of in an object literal.
//
// In contrast, you will see hints of the private name objects proposal at
//
// http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects
//
// Also visible throughout: the method definition shorthand syntax proposed at
//
// http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#object_literal_property_shorthands
//
// For programmers who want classes as sugar for constructors and prototype, I
// hope this proposal wins.
//
// To be perfectly honest, I personally would be happy with Allen Wirfs-Brock's
// "Exemplars" approach and no classes. But for programmers coming from classy
// languages, I believe "minimalist classes" will tend to be too minimal to be
// usable out of the box. They'll want more.
//
// Brendan Eich, 1-Nov-2011
 
// First, basic usage from a real-world library (Three.js)
 
class Color {
constructor(hex) {
...
}
 
// Prototype data properties, introduced by var or const at class body level.
// Rationale: object literal syntax requires comma separation and has no way
// to define a const that's clearly in Harmony. Classy languages do not need
// no steenkin' commas, but we do want const in ES.next.
//
// An objection even I have made: we should not conflate property definition
// syntax with lexical binding declaration syntax. But the cat's out of the
// bag with 'var' in global code in ES1-5, and it's hard to beat "const" as
// the keyword for initialize-only data properties.
//
// One fair objection: this syntax may mislead people into thinking methods
// lexically close over prototype properties declared this way. A calculated
// risk, what can I say?
 
var r = 1, g = 1, b = 1;
 
copy(color) {
...
}
 
setRGB(r, g, b) {
...
}
 
setHSV(h, s, v) {
...
}
 
}
 
 
// To create a class with its prototype chain set correctly:
 
class Fox extends Animal {
...
 
// The special syntax for defining class-level properties and methods uses a
// prefix keyword. The 'static' keyword is reserved and somewhat hated (or at
// least considered a misnomer), but I went with it instead of 'class' to
// avoid confusion in overloading the c-word, and to be more future-friendly
// in case nested classes are wanted (right now I do not want them, since a
// class at class-body level would declare a prototype inner class and not a
// static inner class).
//
// Class-side properties inherit, just as constructor-side properties do with
// the <| proposal:
//
// http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#individual_extension_summary
 
static const CONSTANT = 42;
 
static classMethod() {
...
}
}
 
console.log(Fox.CONSTANT);
 
Fox.classMethod();
 
 
// Classes are expression forms as well as declaration forms. As with function
// expressions, the name is optional in a class expression.
 
const frozenClass = Object.freeze(class {...});
 
// Named class expressions are supported too. The name is bound only in the
// scope of the class body, as for named function expressions.
 
animals.push(class Fox {});
 
// An anonymous class expression with superclass specification, after Jeremy's
// gist, but with an explicit and required body.
 
var subclass = function(parent) {
return class extends parent {
...
};
};
 
 
// Unlike Jeremy's proposal, classes cannot be built up programmatically by
// abutting an expression to a class head. That's too dynamic, it doesn't work
// with the 'super' proposal. So any synthesis of a class body must use eval
// or equivalent. At this time, I do not propose adding a Class constructor
// analogous to Function, but I believe it could be added without issue.
 
 
// As in Jeremy's gist, here's the Monster class from the harmony: proposal
// (http://wiki.ecmascript.org/doku.php?id=harmony:classes#the_proposal_in_a_nutshell)
// ... sans unnecessary 'public' keyword prefixing!
 
class Monster {
 
constructor(name, health) {
this.name = name;
this.health = health;
}
 
attack(target) {
log("The monster attacks " + target);
}
 
isAlive() {
return this.health > 0;
}
 
setHealth(value) {
if (value < 0) {
throw new Error("Health must be non-negative.");
}
this.health = value;
}
 
var numAttacks = 0;
 
const attackMessage = "The monster hits you!";
 
}
 
// Now there's one more problem to address, which could be deferred, but which
// falls under "batteries included" to some (not all) of the cohorts of classy
// programmers using JS or coming to it fresh in the future. And that is the
// cost of this[kPrivateName] given const kPrivateName = Name.create(...) where
// module Name from "@name" has been declared.
//
// Instead I propose we support private kPrivateName, ...; as a special form
// only in class bodies (for now) that both creates a private name object and
// binds it to a lexical const binding that may be accessed on the right of @
// in methods defined in a class.
//
// For class-private instance variables, obj@foo is supported as well (with no
// LineTerminator to the left of @ in this case, or it's short for this[foo]).
// See the new sameName method for an example of infix and prefix @ in action.
//
// There is no const instance variable declaration. Non-writable instance vars
// (properties to most people) are quite rare. Use ES5's Object.defineProperty
// if you must. This avoids ugly fights about read before constant instance var
// initialization too.
 
class Monster {
 
private name, health;
 
constructor(name, health) {
@name = name;
@health = health;
}
 
sameName(other) {
return @name === other@name;
}
 
attack(target) {
log("The monster attacks " + target);
}
 
isAlive() {
return @health > 0;
}
 
setHealth(value) {
if (value < 0) {
throw new Error("Health must be non-negative.");
}
@health = value;
}
 
var numAttacks = 0;
 
const attackMessage = "The monster hits you!";
 
}
 
// As in the harmony:classes proposal, 'const class' freezes all of
// 1. the class binding, if named declaration rather than expression,
// 2. the class prototype induced by the const class,
// 3. the constructor method of the const class.
//
// 'const class' also seals instances constructed by the constructor, as if the
// last line of the constructor method were Object.seal(this), called using the
// original values of Object and Object.seal.
 
const class Frosty {
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.