public
Last active

  • 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
// Here is a proposal for minimalist JavaScript classes, humbly offered.
 
// There are (at least) two different directions in which classes can be steered.
// If we go for a wholly new semantics and implementation, then fancier classical
// inheritance can be supported with parallel prototype chains for true inheritance
// of properties at both the class and instance level.
 
// If however, we keep current JavaScript prototype semantics, and add a form that
// can desugar to ES3, things must necessarily stay simpler. This is the direction
// I'm assuming here.
 
// If we want to have static class bodies (no executable code at the top level),
// then we would do well to reuse the known and loved JavaScript idiom for
// fixed lists of properties -- the object literal.
 
// First, basic usage from a real-world library (Three.js)
 
class Color {
 
constructor: function(hex) {
...
},
 
r: 1, g: 1, b: 1,
 
copy: function(color) {
...
},
 
setRGB: function(r, g, b) {
...
},
 
setHSV: function(h, s, v) {
...
}
 
}
 
 
// To create a class with its prototype chain set correctly:
 
class Fox extends Animal {
...
}
 
 
// There is no special syntax for setting class-level properties, as they are
// relatively rare. Just add them to the class object (constructor function):
 
Fox.CONSTANT = value;
 
 
// Note that the right-hand side of a class definition is just an expression,
// an object literal is not required. You can be fully dynamic when creating a
// class:
 
class Student objectContainingStudentProperties
 
// Or even:
 
class Protester merge(YoungAdult, WorkEthic, Idealism, {
student: true
})
 
// The point I'm trying to make being that the own properties of the right hand
// side, however they're derived, become the prototypal properties of the resulting
// class.
 
 
// Similarly, class definitions are themselves expressions, and anonymous classes
// are equally possible:
 
animals.push(class Fox {});
 
var subclass = function(parent) {
return class extends parent;
};
 
 
// 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 class 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:
 
class Monster {
 
constructor: (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 and their ilk. Personally, I'm of the view
// that all of these modifiers are deeply undesirable in a language as dynamic
// as JavaScript and won't be much used, if added ... but I also think that
// getters and setters should be deprecated and removed.
 
// If public / private / static / frozen / const must be a part of class syntax
// in JS.next, then they must be valid prefixes for object literals as well --
// and can easily be used to define classes with those properties under this
// proposal.
 
// There are no new semantics here, and these classes can easily be transpiled
// into ES3 if needed -- just simpler declaration of constructors with prototypal
// properties and correctly configured prototype chains.
 
// tl;dr
// Classes are a new expression with the form ([] means optional):
// class [name] [extends parent] [expression]

missing "function" keyword on line 107?

One thing I don't see here (but is implied to work) is that this supports native JavaScript setters and getters. JavaScript 1.5 introduced a new syntax for defining getters and setters in object literals, as documented here.

This example below already works today:

function Color() { return this; };

Color.prototype = {
  set rgb(r, g, b) {
    this.color = /* ... */;
  },

  set hsv(h, s, v) {
    this.color = /* ... */;
  }
};

...which can elegantly be used with Jeremy's proposal like so:

class Color {
  set rgb(r, g, b) {
    this.color = /* ... */;
  },
  /* ... */
}

Moving further forward, the syntax for private/public/etc can be made to look similar.

I really like class bodies as object literals. That's what I liked the most about Allen's original proposal.

If public / private / static / frozen / const must be a part of class syntax
in JS.next, then they must be valid prefixes for object literals as well

Exactly! That should be the spirit. That way objects will still be the focus on JS.

There is no special syntax for setting class-level properties, as they are
relatively rare. Just add them to the class object (constructor function):

Agreed. We use them, but not that much.

And with the new object literal extensions the Color class would look like

class Color {
  constructor(hex) {
    ...
  }
  r: 1, g: 1, b: 1,
  copy(color) {
    ...
  }
  setRGB(r, g, b) {
    ...
  }
}

I like it, especially the elegant approach to mixins:
animals.push(class Fox {});
I would just hope you could do this on ANY object.

EDIT: I misread. Thought it was Animal the class, not animals the array. This would be a sweet way to mixin functionality to other objects, including other classes.
Animal.push(class Fox {/* ...fox class functionality... */ });

@dherman: Thanks -- fixed.

@rstacruz: I'm personally fairly antithetical to most of them, but yes -- any modifier that JS defines to work on object literals should also work in class definitions. This includes get, set, and the potential public, private, static, frozen, const modifiers.

@geddesign, you should be able to use mixins the same way you would now: by adding things to the class's prototype. With underscore.js, that would be: _.extend(Foo.prototype, SwimmingAnimal);

Great gist!

With the tl;dr at the end:

// tl;dr
// Classes are a new expression with the form ([] means optional):
// class [name] [extends parent] [expression]

Surely at least one of those is required? Couldn't see an example where one would just use the class keyword with no name, extends or expression

@rstacruz right. I usually either use underscore's extend or follow the functional mixin pattern. But while we're adding classes I just thought it would be nice to have mixins built into the language. ES6 is all about the sugar :)

So the Name.create gensyms for private vars would be put into play through the constructor and would be uninheritable? Or would the class magically copy it over like the (oh so hideous) monocle-mustache operator?

I also think that getters and setters should be deprecated and removed.

In favor of Proxies?

So, how do you differentiate own/instance properties with prototype properties?

@rwldrn: Just like in ordinary JavaScript, per-instance properties can be initialized in the constructor and set and read in instance methods.

I like it, straight forward semantics based on those that already exist is a win

@ricardobeat -- @juandopazo is just talking about the method shorthand, where you can leave out the |: function| part. Which is pretty sweet, if you ask me.

Don't worry, most everyone seems to agree that most of the rest of the object literal extensions in that proposal are too ugly to include in ES6. Allen Wirfs-Brock has simply been experimenting with ways to make it possible to syntactically express property attributes that are already present in the semantics. It's hard to do without jumping the syntactic shark.

It's still been a worthwhile experiment, IMO. You have to try out these ideas to see how they look, and then you cull. All part of the process.

a couple suggestions, can we use shorter words like 'new' and : e.g:

class Fox : Animal {
  new: function(hex) {
    ...
  },
  r: 1, g: 1, b: 1,
  copy: function(color) {
    ...
  }
}

//merging:
class Protester : YoungAdult, WorkEthic, Idealism, {
  new: function(hex) {
    ...
  },
  student: true, 

  copy: function(color) {
    ...
  }
}

@mythz: |new| was proposed in earlier iterations of the class syntax discussion. One problem is that it means you can't have a property called "new" (effectively reserving yet another name in the object property namespace), and "constructor" is already used idiomatically in JS as the name of the property pointing to an object's constructor. I agree it's verbose, though.

An alternative is to make the |new| keyword have special meaning in a class body, but it seems unfortunate to have another special case in the syntax.

(FWIW, I'm agnostic about |extends| vs |:|.)

@dherman Personally I think reserving another name in namespace is a worthwhile trade-off for the amount of bandwidth and energy we would save by having only 3 vs 11 chars - Think of the trees! :)

As for idiomaticy, It could be argued 'new' is more appropriate and has better symmetry since it relates to calling the constructor function under the "new" context - i.e. the only way to invoke the "new" constructor is to instantiate your class using the "new" keyword var f = new Fox()

Although as an earlier proposal for it has already been dismissed, I can accept the consensus amongst experts that "constructor" is a better fit. Still don't agree with it tho :)

Honestly I thought JS as a compiler target was one a major objective, seems strange to be picking wordier, un-wrist-friendly options in this light. Bad for humans / noise pollution POV as well.

@mythz Nothing's final at this point, and I definitely appreciate your points! I personally haven't been all that comfortable with constructor because it's so verbose.

@dherman ha I know, just shorten constructor to const ;)

Correct me if I'm wrong, but isn't this essentially the same output as CoffeeScript classes?

@lookfirst similar behaviour - working out the desired output is what this is about.

@josscrowcroft unnamed classes can actually be useful, you might want to generate classes parametrically in a function:
function the_class_factory(param1, param2) { return class { method: function (...) { .. } } }

How is super supposed to work (especially with class Student objectContainingStudentProperties form), or is it unsupported?

@JacobOscarson - I see what you mean, but I was actually just curious as to whether you'd ever use the class statement without any of those three optionals - but I suppose it's assumed that at least one of the parameters following class is required (either name, extends or expression)..

@josscrowcroft I've run in to several places where I've created that kind of completely anonymous classes (in other languages, that is, see CoffeeScript example here, Python example here). It's of course possible to have dummy parameters for name, extend or expression, but I would find it neater if it was possible to create anonymous classes without using dummy symbols in my code.

Holy deja vu Batman! It's as if the gist was written to mirror the basic structure found in MooTools, I like it ;)

Correct me if I'm wrong, but isn't this essentially the same output as CoffeeScript classes?

It looks similar but Coffeescript classes have executable bodies. I take advantage of this when I'm doing yui3 programming and put an @ATTRS in the body to get a 'class' level variable.

How is super supposed to work (especially with class Student objectContainingStudentProperties form), or is it unsupported?

Wouldn't super(<arguments>) desugaring to this.[[Prototype]].<same_key>.call(this, <arguments>) do what we want? If <same_key> didn't exist you'd have to get an error.

Love it but would like to see:

constructor: function(name, health) {
    this.name = name;
    this.health = health;
  },

sugared to:

constructor: function(this.name, this.health) {

  },

and multiple constructors/constructor chaining is out?

This is wonderful. I hope it inspires other advocates of this type of simplicity in javascript.

I'm also very glad someone shares my views on getters/setters. I think they will permanently harm the language and agree that they should be deprecated.

Wouldn't super(<arguments>) desugaring to this.[[Prototype]].<same_key>.call(this, <arguments>) do what we want?

That'd recurse forever with two or more inheritance chains. super lookup can't just use current this. See https://mail.mozilla.org/pipermail/es-discuss/2011-June/015334.html.

Plz make contruct(?or)/new w/o args to prefer named initialization Point().{x: 1, y: 2} instead Point(1, 2)`. Its force users write more readable code.

constructor is ok! It's not like function which we write all the time. We write one constructor per class. And the fact is that we're already using lengthy names for it.

// MooTools
var Person = new Class({
  initialize: function(name){
    this.name = name;
  }
});

// Prototype
var Person = Class.create({
  initialize: function(name){
    this.name = name;
  }
});

// Dojo
dojo.declare("my.Person", null, {
  constructor: function(name){
    this.name = name;
  }
});

// YUI3
Y.Person = Y.Base.create('person', null, [], {}, {
  initializer: function (name) {
    this.name = name;
  }
});

@grayrest: I'm afraid that ends up with infinite recursion if the prototype's method calls super() again. The "Minimalist Classes" thread Jeremy started last night on es-discuss has a discussion of super() semantics: https://mail.mozilla.org/pipermail/es-discuss/2011-October/thread.html#17793

@Mpdreamz: We've discussed the constructor shorthand. I'm not sure how it interacts with destructuring, so we'd still have to figure that out. I can't shake a feeling that there's something gross about it, but I haven't put my finger on it.

@braddunbar: getters/setters are a done deal, already in ES5. I agree they are easy to abuse; they should be used with care.

@Mpdreamz: as for multiple constructors, isn't it enough just to define different functions? Either as top-level functions or as class properties?

For contrast, this is the class design that @arv, @samth and I sketched out at the last TC39 meeting:

https://gist.github.com/1330478

Same idea as here, except the body is only a literal, not an arbitrary expression. This is simpler and more friendly to super(), but you can still dynamically compute classes.

As someone who's generally not a fan of adding Classes to JS, I actually like this. Smells like JavaScript. +1

+1 i'm not a fan of inheritance-based programming in dynamic languages, in general, but if we're going to add it to the language as a first-class construct, I think this example makes a lot of sense.

Very nice work Jeremy. I agree with you that access modifiers seem to not sit well with the dynamic nature of Javascript. I would use this language feature of it existed.

A thousand times yes! This feels elegant and JavaScript-esque. I also appreciate that it doesn't throw prototypal inheritance under the bus.

Great implementation of classes in JS. :thumbsup:

Why not simply use Dart?

Why not simply troll elsewhere?

The problem with Dart, is that Google presents a false choice. Their premise is that JS is evolving too slowly, while V8 lags far behind Mozilla in implementing the kickass new language features found in more recent versions of ECMAScript. It is disingenuous to say things aren't moving fast enough when your JS engine is two dot revisions behind the implementation of your closest competitor. If Google first implemented proxies, custom iterators, generators, modules, traits, and a whole host of other stuff that is either in, or soon to land in, Mozilla's JS engine then perhaps I would listen to then when they talk about JS not having the features they need.

Google Dart analogy: Google is at a car dealership complaining that the stripped-down dragster it bought has no air conditioner and is threatening to trade it in for a different car to get one....all as the employee points behind him at a whole wall of ready-to-install air conditioners.

@csuwldcat: With all due respect, I don't really agree with this take. Google is not a monolith — there are plenty of people at Google who believe very much in JS and making it succeed, and we have colleagues and friends at Google, some active on TC39, who are working hard to implement Harmony features. They already have landed proxies and maps and sets in V8 (we haven't finished maps and sets but we're working on it; see https://bugzilla.mozilla.org/show_bug.cgi?id=697479 for progress), I believe they have made progress on block scoping and possibly private names as well.

The future of JS looks freaking awesome, and this is in no small part thanks to the recent progress in V8. To quote Brendan: "People are finally realizing that with V8 prototyping alongside SpiderMonkey, ES6 is happening."

That's great news Dave, I had no idea Google had proxies live in Chrome yet (easily a favorite new language feature of mine), I hope they continue at the speed you have indicated as it will begin to beg the question "Why Dart again?"

Why not just using popular coffeescript implementation ?

Very interesting stuff. I wonder if there's a way to split the difference. It looks like ES6 has the concept of StructType, which is a constructor that accepts an object literal defining how it should behave. So why not do the same for classes (or as I prefer to call them, types):

var Color = new Type({

  constructor: function(hex) {
    ...
  },

  r: 1, g: 1, b: 1,

  copy: function(color) {
    ...
  },

  setRGB: function(r, g, b) {
    ...
  },

  setHSV: function(h, s, v) {
    ...
  }

});

This fits into the paradigms being introduced in ES6, allows you to define a type easily with an object literal, and doesn't introduce new syntax. It would at least be a baby step towards the end goal of more easily defining custom types.

it's kinda sad that ES people can't come up with something minimal like this. It looks much nicer than the horrid proposal, so for that I would +1 it I guess, though I think classes are pretty restrictive, not the inheritance model I would want.

"but I also think that
// getters and setters should be deprecated and removed." - I agree 100%, proxies too

Looks nice!!

"Note what is left out: public / private / static / frozen / const properties and their ilk. Personally, I'm of the view that all of these modifiers are deeply undesirable in a language as dynamic as JavaScript and won't be much used, if added"

I'm of the more pessimistic view that they should be left out because they will be used everywhere if added :)

@visionmedia I'm curious, why the opposition to Proxy?

@rwldrn probably not the right thread for that. new gist? haha :D

@visionmedia - Sure, but you brought it up here 0_o

I'm really glad to see the community react so positively to classes as annotated object literals. I hope T39 listens to it :D

I like it. It is very JavaScript-like, and that is what we should be aiming for. The new class syntax should be nothing but shortcuts for what we today do manually. Anything else beyond that is just drifting away from true JS.

@juandopazo: you read es-discuss, so you should know that @allenwb and others have been pursuing class bodies as object literals. That didn't start here. Thanks to @jashkenas for rekindling it and using a gist (gist + email >> email), but your TC39 vs. the community spin is kinda lousy. Credit where due.

/be

@BrendanEich absolutely! In fact my first comment on this gist has a link to @allenwb 's first proposal. I meant no disrespect at all.

I like how the ES proposal drops the function statement on class methods.

@jiggliemon it's inconsistent, more grammar is not necessarily a better thing, it's easier for people catch on if you utilize what's already there. I dont personally se "function" as a problem, but that's the thing worth tackling in a consistent manner

Hey, I've got a hacked-up implementation of this proposal here. It also adds two new operators (strong binding unary and binary :), as well as multiline strings.

I'd love some thoughts!

I'll always remember this as the sad day when spammers found out about github. Report @StevenGerrard.

Pretty sure they're not going to get the same spam ROI from this
demographic though, morons.
On Mar 22, 2012 9:21 PM, "Joss Crowcroft" <
reply@reply.github.com>
wrote:

Hahaha!


Reply to this email directly or view it on GitHub:
https://gist.github.com/1329619

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.