Classes ES6 Flashcards
What is a class
?
Classes are basically a compact syntax for setting up prototype chains.
Note that we don’t need classes to create objects. We can also do so via object literals. That’s why the singleton pattern isn’t needed in JavaScript and classes are used less than in many other languages that have them.
“The essentials of classes” (exploringjs.com). Retrieved November 11, 2024.
What is the class body
?
The body of a class
is the part that is in curly braces {}
. This is where you define class members, such as methods or constructor.
The body of a class is executed in strict mode even without the "use strict"
directive.
A class element can be characterized by three aspects:
- Kind: Getter, setter, method, or field
- Location: Static or instance
- Visibility: Public or private
“Class body” (MDN Web Docs). Retrieved November 11, 2024.
What is the class constructor
?
The constructor
method is a special method for creating and initializing an object created with a class. There can only be one special method with the name “constructor” in a class — a SyntaxError
is thrown if the class contains more than one occurrence of a constructor method.
A constructor can use the super
keyword to call the constructor of the super class.
You can create instance properties inside the constructor:
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } }
Alternatively, if your instance properties’ values do not depend on the constructor’s arguments, you can define them as class fields.
“Class body” (MDN Web Docs). Retrieved November 11, 2024.
What is a static initialization block?
Static initialization blocks are declared within a class
. It contains statements to be evaluated during class initialization. This permits more flexible initialization logic than static
properties, such as using try...catch
or setting multiple fields from a single value. Initialization is performed in the context of the current class declaration, with access to private state, which allows the class to share information of its private properties with other classes or functions declared in the same scope
Syntax
class ClassWithSIB { static { // … } }
Without static initialization blocks, complex static initialization might be achieved by calling a static method after the class declaration:
class MyClass { static init() { // Access to private static fields is allowed here } } MyClass.init();
However, this approach exposes an implementation detail (the init()
method) to the user of the class
. On the other hand, any initialization logic declared outside the class does not have access to private static fields. Static
initialization blocks allow arbitrary initialization logic to be declared within the class and executed during class evaluation.
A class can have any number of static {}
initialization blocks in its class body. These are evaluated, along with any interleaved static field initializers, in the order they are declared. Any static initialization of a super class is performed first, before that of its sub classes.
The scope of the variables declared inside the static block is local to the block. This includes var
, function
, const
, and let
declarations. var
declarations will not be hoisted out of the static block.
“Static initialization blocks - JavaScript | MDN” (MDN Web Docs). Retrieved November 12, 2024.
What are static
methods and fields?
The static
keyword defines a static method or field for a class. Static properties (fields and methods) are defined on the class itself instead of each instance. Static methods are often used to create utility functions for an application, whereas static fields are useful for caches, fixed-configuration, or any other data that doesn’t need to be replicated across instances.
class Point { constructor(x, y) { this.x = x; this.y = y; } static displayName = "Point"; static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); } } const p1 = new Point(5, 5); const p2 = new Point(10, 10); p1.displayName; // undefined p1.distance; // undefined p2.displayName; // undefined p2.distance; // undefined console.log(Point.displayName); // "Point" console.log(Point.distance(p1, p2)); // 7.0710678118654755
“Static methods and fields” (MDN Web Docs). Retrieved November 12, 2024.
What are class
methods?
Methods are defined on the prototype of each class instance and are shared by all instances. Methods can be plain functions, async functions, generator functions, or async generator functions.
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } // Getter get area() { return this.calcArea(); } // Method calcArea() { return this.height * this.width; } *getSides() { yield this.height; yield this.width; yield this.height; yield this.width; } } const square = new Rectangle(10, 10); console.log(square.area); // 100 console.log([...square.getSides()]); // [10, 10, 10, 10]
They are not required but expected to use this
inside the method body. Otherwise the method should become static
or a helper function that lives outside the class
definition.
“Class body” (MDN Web Docs). Retrieved November 18, 2024.
What are public class
field declarations?
Publick class
fields are writable, enumerable, and configurable properties. As such, unlike their private counterparts, they participate in prototype inheritance.
Syntax
class ClassWithField { instanceField; instanceFieldWithInitializer = "instance field"; static staticField; static staticFieldWithInitializer = "static field"; }
There are some additional syntax restrictions:
- The name of a static property (field or method) cannot be
prototype
. - The name of a class field (static or instance) cannot be
constructor
.
Field initializers can refer to field values above it, but not below it. All instance and static methods are added beforehand and can be accessed, although calling them may not behave as expected if they refer to fields below the one being initialized.
class C { a = 1; b = this.c; c = this.a + 1; d = this.c + 1; } const instance = new C(); console.log(instance.d); // 3 console.log(instance.b); // undefined
“Public class fields - JavaScript | MDN” (MDN Web Docs). Retrieved November 18, 2024.
What are class
private properties?
Private properties are counterparts of the regular class
properties which are public, including class fields, class methods, etc. Private properties get created by using a hash #
prefix and cannot be legally referenced outside of the class. The privacy encapsulation of these class properties is enforced by JavaScript itself. The only way to access a private property is via dot notation, and you can only do so within the class that defines the private property.
Private properties were not native to the language before this syntax existed. In prototypal inheritance, its behavior may be emulated with WeakMap
objects or closures, but they can’t compare to the #
syntax in terms of ergonomics.
Syntax
class ClassWithPrivate { #privateField; #privateFieldWithInitializer = 42; #privateMethod() { // … } static #privateStaticField; static #privateStaticFieldWithInitializer = 42; static #privateStaticMethod() { // … } }
There are some additional syntax restrictions:
- All private identifiers declared within a class must be unique. The namespace is shared between static and instance properties. The only exception is when the two declarations define a getter-setter pair.
- The private identifier cannot be
#constructor
. - It is a syntax error to refer to
# names
from outside of the class. It is also a syntax error to refer to private properties that were not declared in the class body, or to attempt to remove declared properties with delete.
“Private properties - JavaScript | MDN” (MDN Web Docs). Retrieved November 18, 2024.
List all the possible private properties of a class
Most class properties have their private counterparts:
- Private fields
- Private methods
- Private static fields
- Private static methods
- Private getters
- Private setters
- Private static getters
- Private static setters
These features are collectively called private properties. However, constructors cannot be private in JavaScript. To prevent classes from being constructed outside of the class, you have to use a private flag.
“Private properties - JavaScript | MDN” (MDN Web Docs). Retrieved November 27, 2024.
How can you simulate a private constructor?
Many other languages include the capability to mark a constructor as private, which prevents the class
from being instantiated outside of the class
itself — you can only use static factory methods that create instances, or not be able to create instances at all. JavaScript does not have a native way to do this, but it can be accomplished by using a private static flag
.
class PrivateConstructor { static #isInternalConstructing = false; constructor() { if (!PrivateConstructor.#isInternalConstructing) { throw new TypeError("PrivateConstructor is not constructable"); } PrivateConstructor.#isInternalConstructing = false; // More initialization logic } static create() { PrivateConstructor.#isInternalConstructing = true; const instance = new PrivateConstructor(); return instance; } } new PrivateConstructor(); // TypeError: PrivateConstructor is not constructable PrivateConstructor.create(); // PrivateConstructor {}
“Simulating private constructors” (MDN Web Docs). Retrieved November 27, 2024.
How are class private
properties declared?
With # names (pronounced “hash names”), which are identifiers prefixed with #
. The hash prefix is an inherent part of the property name — you can draw relationship with the old underscore prefix convention _privateField
— but it’s not an ordinary string property, so you can’t dynamically access it with the bracket notation.
It is a syntax error to refer to # names
from outside of the class. It is also a syntax error to refer to private properties that were not declared in the class body, or to attempt to remove declared properties with delete
.
class ClassWithPrivateField { #privateField; constructor() { delete this.#privateField; // Syntax error this.#undeclaredField = 42; // Syntax error } } const instance = new ClassWithPrivateField(); instance.#privateField; // Syntax error
JavaScript, being a dynamic language, is able to perform this compile-time check because of the special hash identifier syntax, making it different from normal properties on the syntax level.
Note: Code run in the Chrome console can access private properties outside the class. This is a DevTools-only relaxation of the JavaScript syntax restriction.
“Private properties - JavaScript | MDN” (MDN Web Docs). Retrieved November 27, 2024.
How can you inherit from a class?
The extends
keyword is used in class declarations or class expressions to create a class that is a child of another class.
“extends - JavaScript | MDN” (MDN Web Docs). Retrieved December 19, 2024.
Is it possible for a class
to extend a buil-in object?
Yes. The extends keyword can be used to subclass custom classes as well as built-in objects.
“extends - JavaScript | MDN” (MDN Web Docs). Retrieved December 20, 2024.
What constructors can be called with the new
operator?
Any constructor that can be called with new
and has the prototype
property can be the candidate for the parent class. The two conditions must both hold — for example, bound functions
and Proxy
can be constructed, but they don’t have a prototype property, so they cannot be subclassed.
“extends - JavaScript | MDN” (MDN Web Docs). Retrieved December 20, 2024.
What does extends
do to the prototype of the ChildClass
extends
sets the prototype
for both ChildClass
and ChildClass.prototype.
a) When extends
clause is absent:
- Prototype of ChildClass
becomes Function.prototype
- Prototype of ChildClass.prototype
becomes Object.prototype
b) When extends null
:
- Prototype of ChildClass
becomes Function.prototype
- Prototype of ChildClass.prototype
becomes null
c) When extends ParentClass
:
- Prototype of ChildClass
becomes ParentClass
- Prototype of ChildClass.prototype
becomes ParentClass.prototype
class ParentClass {} class ChildClass extends ParentClass {} // Allows inheritance of static properties Object.getPrototypeOf(ChildClass) === ParentClass; // Allows inheritance of instance properties Object.getPrototypeOf(ChildClass.prototype) === ParentClass.prototype;
extends clause absent | Function.prototype
| Object.prototype
|
What is a mixin?
A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.
Gilad Bracha and William Cook, Mixin-based Inheritance
How are traditional JavaScript Mixins done?
Mixin libraries like Cocktail, traits.js, and patterns described in many blog posts (like one of the latest to hit Hacker News: Using ES7 Decorators as Mixins), generally work by modifying objects in place, copying in properties from mixin objects and overwriting existing properties.
This is often implemented via a function similar to this:
function mixin(source, target) { for (var prop in source) { if (source.hasOwnProperty(prop)) { target[prop] = source[prop]; } } }
A version of this has even made it into JavaScript as Object.assign
.
mixin()
is usually then called on a prototype:
mixin(MyMixin, MyClass.prototype);
and now MyClass
has all the properties defined in MyMixin
.
"”Real” Mixins with JavaScript Classes” (Justin Fagnani). Retrieved January 23, 2025.
What is the problem with traditional mixins?
- Prototypes are directly mutated - This is a problem if the prototype is used anywhere else that the mixed-in properties are not wanted.
- Slower objects - Mixins that add state can create slower objects in VMs that try to understand the shape of objects at allocation time.
- Super doesn’t work.
- Incorrect precedence - By overwriting properties, mixin methods take precedence over those in the subclass. Not allowing the subclass to override methods on the mixin.
- Composition is compromised - Mixins often need to delegate to other mixins or objects on the prototype chain, but there’s no natural way to do this with traditional mixins. Since functions are copied onto objects, naive implementations overwrite existing methods.
"”Real” Mixins with JavaScript Classes” (Justin Fagnani). Retrieved January 23, 2025.
How can you create a mixin as a subclass factory?
To create mixins as “subclass factories, parameterized by the superclass” we rely on two features of JavaScript classes:
-
class
can be used as an expression as well as a statement. As an expression it returns a new class each time it’s evaluated. (sort of like a factory!) - The
extends
clause accepts arbitrary expressions that return classes or constructors.
The key is that classes in JavaScript are first-class: they are values that can be passed to and returned from functions.
All we need to define a mixin is a function that accepts a superclass and creates a new subclass from it, like this:
let MyMixin = (superclass) => class extends superclass { foo() { console.log('foo from MyMixin'); } };
Then we can use it in an extends clause like this:
class MyClass extends MyMixin(MyBaseClass) { /* ... */ }
And MyClass
now has a foo
method via mixin inheritance:
let c = new MyClass(); c.foo(); // prints "foo from MyMixin"
Incredibly simple, and incredibly powerful! By just combining function application and class expressions we get a complete mixin solution, that generalizes well too.
Applying multiple mixins works as expected:
class MyClass extends Mixin1(Mixin2(MyBaseClass)) { /* ... */ }
Mixins can easily inherit from other mixins by passing the superclass along:
let Mixin2 = (superclass) => class extends Mixin1(superclass) { /* Add or override methods here */ }
And you can use normal function composition to compose mixins:
let CompoundMixin = (superclass) => Mixin2(Mixin3(superclass));
"”Real” Mixins with JavaScript Classes” (Justin Fagnani). Retrieved February 3, 2025.
What are the benefits of creating mixins as subclass factories?
- Mixins are added to the prototype chain.
- Mixins are applied without modifying existing objects.
- Mixins do no magic, and don’t define new semantics on top of the core language.
-
super.foo
property access works within mixins and subclasses. -
super()
calls work in constructors. - Mixins are able to extend other mixins.
-
instanceof
works. - Mixin definitions do not require library support - they can be written in a universal style.
"”Real” Mixins with JavaScript Classes” (Justin Fagnani). Retrieved February 3, 2025.
Propose a better syntax to create mixins as subclass factories?
let mix = (superclass) => new MixinBuilder(superclass); class MixinBuilder { constructor(superclass) { this.superclass = superclass; } with(...mixins) { return mixins.reduce((c, mixin) => mixin(c), this.superclass); } }
It can then be use the following way:
class MyClass extends mix(MyBaseClass).with(Mixin1, Mixin2) { /* ... */ }
"”Real” Mixins with JavaScript Classes” (Justin Fagnani). Retrieved February 5, 2025.