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)

@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