World without the "new" keyword.

And simpler "prose-like" syntax with Object.create().

*This example is updated for ES6 classes and TypeScript.

First off, and factually, Javascript is a prototypal language, not class-based. Its true nature is expressed in the prototypial form below, which you may come to see that is very simple, prose-like, yet powerful.

TLDR;

const Person = { name: 'Anonymous', // person has a name greet: function() { console.log(`Hi, I am ${this.name}.`} } const jack = Object.create(Person) // jack is a person jack.name = 'Jack' // and has a name 'Jack' jack.greet() // outputs "Hi, I am Jack."

This absolves the sometimes convoluted constructor pattern. A new object inherits from the old one, but is able to have its own properties. If we attempt to obtain a member from the new object ( #greet() ) which the new object jack lacks, the old object Person will supply the member.

In Douglas Crockford's words: "Objects inherit from objects. What could be more object-oriented than that?"

You don't need constructors, no new instantiation (read why you shouldn't use new ), no super , no self-made __construct . You simply create Objects and then extend or morph them.

This pattern also offers immutability (partial or full), and getters/setters.

TypeScript

The TypeScript equivalent looks the same:

interface Person { name: string, greet: Function } const Person = { name: 'Anonymous', greet: function(): void { console.log(`Hi, I am ${this.name}.` } } const jack: Person = Object.create(Person) jack.name = 'Jack' jack.greet()

Prose-like syntax: Person protoype

const Person = { //attributes firstName : 'Anonymous', lastName: 'Anonymous', birthYear : 0, type : 'human', //methods name() { return this.firstName + ' ' + this.lastName }, greet() { console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' ) }, age() { // age is a function of birth time. } } const person = Object.create(Person). // that's it!

Clean and clear. It's simplicity does not compromise features. Read on.

Creating an descendant/copy of Person

Note: The correct terms are prototypes , and their descendants/copies . There are no classes , and no need for instances .

const Skywalker = Object.create(Person) Skywalker.lastName = 'Skywalker' const anakin = Object.create(Skywalker) anakin.firstName = 'Anakin' anakin.birthYear = '442 BBY' anakin.gender = 'male' // you can attach new properties. anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.' Person.isPrototypeOf(Skywalker) // outputs true Person.isPrototypeOf(anakin) // outputs true Skywalker.isPrototypeOf(anakin) // outputs true

If you feel less safe throwing away the constructors in-lieu of direct assignments, fair point. One common way is to attach a #create method:

Skywalker.create = function(firstName, gender, birthYear) { let skywalker = Object.create(Skywalker) Object.assign(skywalker, { firstName, birthYear, gender, lastName: 'Skywalker', type: 'human' }) return skywalker } const anakin = Skywalker.create('Anakin', 'male', '442 BBY')

Branching the Person prototype to Robot

When you branch the Robot descendant from Person prototype, you do not affect Skywalker and anakin :

// create a `Robot` prototype by extending the `Person` prototype: const Robot = Object.create(Person) Robot.type = 'robot'

Attach methods unique to Robot

Robot.machineGreet = function() { /*some function to convert strings to binary */ } // Mutating the `Robot` object doesn't affect `Person` prototype and its descendants anakin.machineGreet() // error Person.isPrototypeOf(Robot) // outputs true Robot.isPrototypeOf(Skywalker) // outputs false

In TypeScript you would also need to extend the Person interface:

interface Robot extends Person { machineGreet: Function } const Robot: Robot = Object.create(Person) Robot.machineGreet = function(): void { console.log(101010) }

And You Can Have Mixins -- Because.. is Darth Vader a human or robot?

const darthVader = Object.create(anakin) // for brevity, property assignments are skipped because you get the point by now. Object.assign(darthVader, Robot)

Darth Vader gets the methods of Robot :

darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..." darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...

Along with other odd things:

console.log(darthVader.type) // outputs robot. Robot.isPrototypeOf(darthVader) // returns false. Person.isPrototypeOf(darthVader) // returns true.

Which elegantly reflects the "real-life" subjectivity:

"He's more machine now than man, twisted and evil." - Obi-Wan Kenobi

"I know there is good in you." - Luke Skywalker

Compare to the pre-ES6 "classical" equivalent:

function Person (firstName, lastName, birthYear, type) { this.firstName = firstName this.lastName = lastName this.birthYear = birthYear this.type = type } // attaching methods Person.prototype.name = function() { return firstName + ' ' + lastName } Person.prototype.greet = function() { ... } Person.prototype.age = function() { ... } function Skywalker(firstName, birthYear) { Person.apply(this, [firstName, 'Skywalker', birthYear, 'human']) } // confusing re-pointing... Skywalker.prototype = Person.prototype Skywalker.prototype.constructor = Skywalker const anakin = new Skywalker('Anakin', '442 BBY') // #isPrototypeOf won't work Person.isPrototypeOf(anakin) // returns false Skywalker.isPrototypeOf(anakin) // returns false

If you want to increase code readability, you have to go for ES6 classes, which has increased in adoptation and browser compatibility (or a non-concern with babel):

ES6 Classes

class Person { constructor(firstName, lastName, birthYear, type) { this.firstName = firstName this.lastName = lastName this.birthYear = birthYear this.type = type } name() { return this.firstName + ' ' + this.lastName } greet() { console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' ) } } class Skywalker extends Person { constructor(firstName, birthYear) { super(firstName, 'Skywalker', birthYear, 'human') } } const anakin = new Skywalker('Anakin', '442 BBY') // prototype chain inheritance checking is partially fixed. Person.isPrototypeOf(anakin) // returns false! Skywalker.isPrototypeOf(anakin) // returns true

Admittedly, some of these problems are eradicated by the ES6 classes. When I wrote my answer five years ago, ES6 classes were budding, and has now matured for use.

But underneath the hood of ES6 classes is the obscured true prototypial nature of Javascript. So I was naturally disappointed of its implementation.

Nonetheless, that's not to say ES6 classes are bad. It provides a lot of new features and standardised a manner that is reasonably readable. Though it really should have not used the operator class and new to confuse the whole issue.

Further reading

Writability, Configurability and Free Getters and Setters!

For free getters and setters, or extra configuration, you can use Object.create()'s second argument a.k.a propertiesObject. It is also available in #Object.defineProperty, and #Object.defineProperties.

To illustrate its usefulness, suppose we want all Robot to be strictly made of metal (via writable: false ), and standardise powerConsumption values (via getters and setters).

const Robot = Object.create(Person, { // define your property attributes madeOf: { value: "metal", writable: false, configurable: false, enumerable: true }, // getters and setters powerConsumption: { get() { return this._powerConsumption }, set(value) { if (value.indexOf('MWh')) return this._powerConsumption = value.replace('M', ',000k') this._powerConsumption = value throw new Error('Power consumption format not recognised.') } } }) const newRobot = Object.create(Robot) newRobot.powerConsumption = '5MWh' console.log(newRobot.powerConsumption) // outputs 5,000kWh

And all prototypes of Robot cannot be madeOf something else: