Let's build a JavaScript class syntax from first principles. For the purpose of this exercise, let's assume that the purpose of the class syntax is to add much-needed sugar to common JavaScript idioms.
Let's start with how JavaScript "classes" work today:
// this is a constructor
Person = function() {
this // `this` is a new instance of Person
}
// define a list of properties that exist on all instances of Person
Person.prototype = {
hello: function(text) {
this // `this` is the current instance of Person
console.log(text)
}
}
Let's add some syntax to JavaScript to represent this idiom:
class Person {
function constructor() {
// `this` is a new instance of Person
}
function hello(text) {
// `this` is the current instance of Person
console.log(text)
}
}
The class is simply the constructor function with the remaining functions assigned to its prototype. If no constructor function is supplied, an empty function is used.
For a dash of sugar, we can allow the word function
to be left out:
class Person {
constructor() {
// `this` is a new instance of Person
}
hello(text) {
// `this` is the current instance of Person
console.log(text)
}
}
Now let's look at inheritance:
// this is a constructor
Man = function() {
Person.apply(this, arguments) // invoke the superclass constructor
this // `this` is a new instance of Man
}
// subclass Person
Man.prototype = Object.create(Person);
// create a new method called fullName
Man.prototype.fullName = function() {
return "Mr. " + this.firstName + ' ' + this.lastName;
}
// subclass `hello` and invoke the superclass
Man.prototype.hello = function(text) {
Person.prototype.hello.call(this, this.fullName() + " says: " + text);
}
Let's add some syntax to JavaScript to represent this idiom:
// same as Man.prototype = Object.create(Person)
class Man extends Person {
// using ES.next rest arguments syntax
constructor(...args) {
super(...args) // same as Person.apply(this, arguments)
}
fullName() {
return "Mr. " + this.firstName + ' ' + this.lastName;
}
hello(text) {
// same as Person.prototype.hello.apply(this, [...args])
super(this.fullName() + " says: " + text);
}
}
The super
keyword simply invokes a function of the same name on the direct superclass of the current object's constructor, passing along any arguments passed.
Finally, people sometimes add properties directly to the prototype:
Man.prototype.salutation = "Mr.";
Let's make that possible inside of the class body:
class Man extends Person {
salutation = "Mr.";
}
This syntax alone would be a vast improvement to existing JavaScript syntax, and would be worthy of inclusion without additional improvements.
That said, let's take the opportunity to make two additional improvements while we're at it.
A common problem with defining properties on a prototype comes when you define a property to be an object, like an Array:
Man.prototype.children = [];
Let's say that instead of evaluating the right hand side of an assignment immediately, and sharing it across all instances, it is evaluated for each new instance:
class Man extends Person {
children = [];
}
new Man().children === new Man().children // false
When creating a new instance, in addition to invoking the class' constructor, we evaluate the right hand side of each declared property and assign it as a property of the new object.
Existing JavaScript libraries that implement classes perform various actions during the process of creating a new class.
Let's allow classes to define a hook that should be called after they are extended.
Person.extended = function(child) {
// child is the subclass of Person
// child.prototype would exist at this point
}
This will allow additional class semantics to be defined by libraries and toolkits.
Let's try adding a very simple class syntax to JavaScript and see where that takes us.
+1
...really like the idea of function declarations being used to assign class properties. Would it be possible to set private members inside the class (with getters and setters, obv)? Something like: