Skip to content

Instantly share code, notes, and snippets.

@mpj
Last active September 21, 2024 15:03
Show Gist options
  • Save mpj/17d8d73275bca303e8d2 to your computer and use it in GitHub Desktop.
Save mpj/17d8d73275bca303e8d2 to your computer and use it in GitHub Desktop.

The future is here: Classless object-oriented programming in JavaScript.

Douglas Crockford, author of JavaScript: The Good parts, recently gave a talk called The Better Parts, where he demonstrates how he creates objects in JavaScript nowadays. He doesn't call his approach anything, but I will refer to it as Crockford Classless.

Crockford Classless is completely free of class, new, this, prototype and even Crockfords own invention Object.create.

I think it's really, really sleek, and this is what it looks like:

function dog(spec) {

  var { name, breed } = spec,
      { say }   = talker({ name }),
      bark = function () {

        if ( breed === 'chiuaua' ) {
          say( 'Yiff!' );
        } else if ( breed === 'labrador' ) {
          say('Rwoooooffff!');
        }

      };

  return Object.freeze({
    bark,
    breed
  });

}

function talker(spec) {

  { name } = spec;
  var say = function(sound) {
    console.log(name, "said:", sound)
  }

  return Object.freeze({
    say
  });

}

var buttercup = dog({ name: 'Buttercup', breed: 'chiuaua' });

Specification object instead of argument list

Specification objects to constructor calls are easier to read, write, and change:

databaseConnection('localhost', null, true, 8000, 7000)
// vs
databaseConnection({
  host: 'localhost',
  port: 8000
  timeout: 7000
})

The new destructuring feature in ES6 makes specification objects a lot less verbose than they were in ES5:

var { host, port, timeout } = spec;
// vs
var host = spec.host,
    port = spec.port,
    timeout = spec.timeout;

No new or this

Constructors in Crockford Classless aren't called with new, which means they have no special magic behavior - they are just completely normal functions that happen to create objects.

That means that there is no longer a magic this variable in constructors, just normal variables. We get rid of this scoping confusion (and workarounds like .bind(this) and self=this).

Since there is no new keyword that we can accidentally forget to add, we can do away with the manual-uppercasing-of-constructor-names convention, and any this-checks in the top of constructor functions.

Composition over inheritance

TODO: Make the example more composey

Crockford Classless doesn't use inheritance. Instead, the constructor simply composes it's object by calling other constructors and mixing their properties into a new object.

Using inheritance is, unfortunately, still the default mode of most programmers, using composition only rarely, even though some of the best say that it should be the other way around.

No prototype

It surprises noone that Crockford considers it to be a bad thing that ES6 introduces the class keyword. What is more suprising is that he also has lost belief in prototypal inheritance. He says that the prototypal model was a confused step in the right direction towards classless object-oriented programming. Prototypal inheritance has it's heart in the right place, but is very hard to wrap your head around, especially in JavaScript, which mixes in the new keyword, a badly implemented half-implementation of classes in a misguided attempt to make the language more approachable to people used to programming with classes.

TODO: Write a about performance problems of prototype chain

Performance

TODO change this to memory consumption, as prototype chain omission arguably is good for perf

Crockford Classless is gorgeous, but there is one tradeoff: Performance. Every object will be slightly more expensive than it's prototypal equivalent. However, remember that premature optimization is the root of all evil. If you have to create millions of objects and keep them in memory, Crockford Classless is not for you (is JavaScript for you, in that case?). If not, your time spent optimizing is almost certainly better spent on reducing DOM manipulation and HTTP requests.

TODO: write about object.freeze

Why no use class?

blabalbal - using classes encourages classification and classification encourages taxonomies. And no, you don't have to do classification.

To clarify, I do not consider classes to be bad. What I do consider bad is that we use it as the default way of doing object orientation.

  1. Classes promote inheritance, which is often bad An object-orient model can either nudge you towards inheritance or composition. While classes (at least in JavaScript) support composition, they tend to nudge the developer towards inheritance. Inheritance is sometimes the right choice, but composition is generally the better choice.

  2. Classes promote upfront design The thing with classes is that it strongly encourages you to classify your system. To say what every kind of object is, to assign it a type. To consider what type of object your functions accepts. ** rigidity - in some cases appropriate, in many cases it's limiting. Sometimes, you really just need to check if an object has an emit method, you don't need to implement an IEventEmitter interface.

** complicated taxonomy - ** hard to predict future - you almost always get it wrong ** interoperability - when you start doing type checking, you're locking yourself in. https://news.ycombinator.com/item?id=7241638 (31:30)

@gabor-m
Copy link

gabor-m commented Sep 30, 2014

And what do you think about freezing the object before you return it?

@sergej-s
Copy link

Thank you for the post! I'd like to join gazbor question about object freezing and immutability. How I should work with frozen objects?

@StevenACoffman
Copy link

I found this StackOverflow referencing this gist to be helpful in unpacking this a bit. I'm still working on all the implications. Also, the the Object.freeze function does the following:

  • Makes the object non-extensible, so that new properties cannot be added to it.
  • Sets the configurable attribute to false for all properties of the object. When - configurable is false, the property attributes cannot be changed and the property cannot be deleted.
  • Sets the writable attribute to false for all data properties of the object. When writable is false, the data property value cannot be changed.

@StevenACoffman
Copy link

The crockford classless technique specifically does not use something like constantize() so it leaves open the possibility of mutable internal data (or state). It is a class free form of object oriented programming that encourages composition over inheritance. It uses the revealing module pattern to return a frozen object.

I think I was able to correctly apply it in this Circular Buffer, even with demonstrating composition.

@akrueger
Copy link

function talker(spec) {

  var  { name } = spec;
         say = function(sound) {
            console.log(name, "said:", sound)
}

I had to move the var declaration up to the top of the function for this to run.

@ericelliott
Copy link

ericelliott commented Jul 16, 2016

"Crockford Classless" is just a constructor function that does not use prototype delegation or this. See JavaScript Factory Functions vs Constructor Functions vs Class.

@StevenACoffman The revealing module pattern is obsolete. Use real modules, instead. Stop calling all closures "revealing module pattern". Not the same.

@StevenACoffman
Copy link

StevenACoffman commented Aug 9, 2016

@ericelliot I found that people seemed to call this by many names, and I was trying to collect references in the literature. If "Crockford Classless" is in any way different from the "Revealing Module Pattern" in the book by @addyosmani, plus some es6 sugar, I don't see it. I do see that es6 module syntax has made that name somewhat confusing. Closures are of course useful for lots of things that don't fit that pattern and aren't called by any of these names. Thanks for the article link.

@ptcc
Copy link

ptcc commented Jun 2, 2017

@ericelliot could you please explain why is "Crockford Classless" a constructor function and not a factory? It seems a factory to me... (no new and explicit return of a new object)

@ahmehri
Copy link

ahmehri commented Aug 24, 2017

And how should we manage the state of the created object in this case? For instance, in the above gist, the buttercup object has a state which is composed of the dog name, chiuaua. What if we want to modify this name, how should we proceed?

@grandgeorg
Copy link

I think there is an error in line 24, it has be:

var { name } = spec;

https://jsfiddle.net/df4gvyhs/

@ryanlaws
Copy link

ryanlaws commented Aug 24, 2019

@ahmehri You could certainly provide a setter. This forces you to consider what to expose and explicitly expose it, where classes expose everything on their this by default.

@JoshuaSkootsky
Copy link

Thank you for this gist. It is very clear and does a good job of explaining the idea.

@JoshuaSkootsky
Copy link

JoshuaSkootsky commented Feb 18, 2020

@grandgeorg

I think there is an error in line 24, it has be:

var { name } = spec;

https://jsfiddle.net/df4gvyhs/

I agree
Agree. When I wrote the code out, and got it to work, it had to be like this:

function talker(spec) {
  let {name} = spec;
  var say = function(sound) {
    console.log(name, 'said:', sound);
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment