- collection of key-value pairs, called “properties”, properties are like variables, object is like a container for multiple variables
- every data type in JS is either an object, e.g. arrays, functions, objects, or behaves like an object, e.g. strings, numbers, booleans
-> needs to understand objects to understand JS
- inheritance is the distinct feature of objects in comparison to other data types
-> can build powerful data structures
Basics
- property names can be of data type
String
or Symbol
- property values can be of any data type including object, function, array, etc.
-> can build nested data structures of arbitrary complexity
- properties are called methods if the property value is a function
- objects are only mutable data type, properties can be modified after creation, reference stays the same, i.e.
const a = {}
and a.name = "Peter"
is valid
- objects are addressed / passed ❓“by reference”, not “by value” like primitive data types, i.e. if
a
is an object and let b = a
, then b
refers to the same identical object as a
, not only a copy
- objects are compared “by reference”, not “by value” like primitive data types, i.e.
let a = {}
, let b = {}
, then a === b
is false, because a
and b
are not referencing to the same object
Object literals
Creating object literals
{make: "Honda",
model: "Civic",
year: 1998,
honk: function() {console.log("Hooonk");},
beep() {console.log("Beeep");},
blub: alreadyExistingFunction
}
- comma seperated key-value pairs, like in arrays
- if key is not valid identifier, must be enclosed in quotes, e.g.
{"1number": 42}
- can use reserved keyword as key without quotes since ES5 (except internally used keyword
__proto__
)
- can use computed property values, i.e. variables as values
const value1 = "Honda";
const car1 = {make: value1,
model: "Civic",
year: 1998};
- can use computed property names, i.e. variables as keys, also for ES6 method shorthand, since ES6
const key1 = "make";
const key2 = "honk";
const car1 = {[key1]: "Honda",
model: "Civic",
year: 1998,
[key2]() {console.log("Beeep");}
};
- property value shorthand if property names should be the same as the variable name (with ES6)
const make = "Honda";
const model = "Civic";
const year = 1998;
const car1 = {make,
model,
year};
- object properties are locally scoped in object, not accessible from outside, (except if object is global object)
- computed property names get converted to string if they aren’t already, (uses
Object.prototype.toString()
method)
- property names must be unique, or the last will overwrite the previous, in strict mode will throw error
- can use spread operator to copy own enumerable properties from other object, see ellipsis operator
Accessing and changing properties
- dot notation:
car1.make
, car1.honk()
- can only use if key is valid identifier and not computed property name, else must use bracket notation
- bracket notation:
car1[make]
, car1[honk]()
- can use with variable or any string in quotes as key, e.g.
obj["1number"]
- return
undefined
if property doesn’t exist
- can chain if value itself is object
- set property values by assignment, doesn’t matter what variable type the object reference is, e.g.
const
car1.year = 1999;
car1.honk = function() {console.log("Move b****, get out da way!")};
- if sets property that doesn’t exists a new property is created in this object
-> very powerful, can add and remove properties on the fly
- delete operator to delete own property, see delete operator
Optional chaining
- optional property accessing and function calls (with ES2020)
- returns
undefined
if reference to left is nullish, instead of throwing TypeError
that can’t read property of undefined
- doesn’t need to guard chain anymore using
&&
, non-nullish falsy values false
, 0
, NaN
, ""
are allowed unlike with &&
🎉
- works with dot and bracket notation, i.e. with arrays use bracket notation
const p = {};
const q = p.first.second;
const q = p.first && p.first.second;
const q = p.first?.second;
const p = {};
const q = p["first"]["second"];
const q = p["first"] && p["first"]["second"];
const q = p["first"]?.["second"];
- short-circuit evaluation, doesn’t evaluate further to right if returns early
- can stack multiple optional chaining operators in a property accessor sequence, also mixing dot and bracket notation
- beware: don’t use if value is guaranteed to not be nullish, would hide bugs since doesn’t throw anymore
- only checks if value to left is nullish, not whole chain
const p = {};
const q = p.first.second?.third;
const q = p?.first.second;
- works with method calls as well
const p = {};
const q = p.first();
const q = p.first?.();
Getters and Setters
const car1 = {...
owners: ["Bob", "Sarah", "James"],
set currentOwner(name) {owners += name;},
get currentOwner() {return owners[owners.length - 1];}
}
console.log(car1.currentOwner);
car1.currentOwner = "Zoe";
console.log(car1.currentOwner);
- bind object’s property to a function call
- behaves like any other property, can be accesses, set, or deleted, but is itself not a property, e.g. there is no property
currentOwner
in above example, it’s just getters and setters
- can make property seem like it stores dynamically computed value
beware: using getters and setters one can’t trust anymore that an object’s properties stay the same if they haven’t been modified, since now they can be modified internally through different “properties”, use cautiously ❗️
- set: takes exactly one argument, no return statement
- get: takes exactly zero arguments, has return statement
- can not have a normal property with the same name as get / set
- use
Object.defineProperty()
to define getters and setters for existing object
Property attributes
- define if a property is writable, enumerable and deletable
- by default a property is writable, enumerable and deletable, but not necessarily the built-in ones ❗️
- attributes differ for data property (static, normal value) or accessor property (dynamic, getters and setters)
Descriptor object
- used to specify attributes
- both data and accessor descriptor objects can contain the following properties
enumerable
: boolean, property shows up during enumeration, e.g. in for..in
or Object.keys()
configurable
: boolean, property can be deleted and data property can be changed to accessor or vice versa
- data property descriptors can also contain the following properties
value
: any type, value of property
writeable
: boolean, property value can be modified
- accessor property descriptors can also contain the following properties
get
: function, getter for property
set
: function, setter for property
Setting property attributes
Object.defineProperty()
: defines new property on an object or modifies existing one, can specify property descriptor, returns object
- for new property,
enumerable
, configurable
and writeable
default to false
if not specified with a property descriptor, i.e. property is read-only, non-enumerable and non-deletable
- for new property,
value
, get
and set
default to undefined
if not specified with a property descriptor, i.e. property value is undefined
- for existing property, attributes not specified with a property descriptor default to their existing value
- a descriptor containing only
enumerable
and configurable
is treated as data descriptor
const p = {};
Object.defineProperty(p, "name", {
value: "Peter",
writeable: true,
enumerable: true,
configurable: true
})
Object.keys(p);
Object.defineProperty(p, "name", {
enumerable: false
})
Object.keys(p);
let n = 42;
const p = {};
Object.defineProperty(p, "age", {
get() { return n; },
set(age) { n = age; },
enumerable: true,
configurable: true
})
writable
once set to false
can not be reset to true
, integrity is guaranteed
beware: resetting it fails silently even if property is non-configurable ⚠️ ⚠️ ⚠️
enumerable
once set to false
can be reset to true
, but resetting it fails on non-configurable property fails with TypeError: Cannot redefine property
configurable
once set to false
can not be reset to true
, integrity is guaranteed, resetting it fails with TypeError: Cannot redefine property
- in sloppy mode writing to a non-writable property or deleting a non-configurable property doesn’t throw an error, use strict mode instead ⚠️
Constructors
- regular function, but designed to be called with
new
keyword
- convention is to name with upper CamelCase
Terminology
- object type: abtract notion of type of object, e.g. cars, trucks, bikes
- constructor: blueprint for object type, like a custom object type, e.g.
Car
constructor creates car objects
- instance: object created using a particular constructor, e.g.
car1
is instance of Car
this
keyword
this
is a dynamically bound reference to an object, like a variable
- object is determined by the context of the function call, not by how the function was defined ⚠️
- allows to reuse one function in different contexts, makes it flexible, share one function on infinitely many instances, but often source of confusion
- outside of any function in global context
this
is the global object
- in a function
this
is dependend on the call site, by the following precedence:- in a constructor, i.e. called by
new
operator, this
refers to the object being constructed
- in a function called using
Function.prototype.call
/apply()
, or created using Function.prototype.bind()
, this
refers to the object specified as first argument to those methods
- in a method,
this
is the object the method is called on, doesn’t matter where function was defined, outside, on prototype chain, etc. ❗️
- in a function called without any of the above,
this
is the global object in non-strict mode or undefined
in strict mode
- beware: in an event handler,
this
is the object the event handler is attached to, is not necessarily the same object the event targeted, see Events
- beware:
this
depends only on how function was called, not how it was defined, e.g. if a method is passed as value and then called independent from object, it’s this
will be back to default ⚠️
function sayHi() {
console.log("Hi, my make is " + this.make);
};
sayHi();
car1.sayHi = sayHi;
car1.sayHi();
const func = car1.sayHi
func();
function execute(callback) {
callback();
}
execute(car1.sayHi);
- use
Function.prototype.call
/apply()
to call any function with this
being specified, or create entirely new function using Function.prototype.bind()
with this
being fixed
func.call(car1);
func.apply(car1);
func();
const func2 = car1.sayHi.bind(car1);
func2();
new
operator
- operates on a function, but used specifically with constructor or class, e.g.
new Constructor();
- usually assigns it to a variable
- steps of the
new
operator:- creates a new empty object
- sets its
[[prototype]]
property to the constructor’s prototype
property (see later)
- calls the constructor with
this
being the new object
- returns
this
(if the function doesn’t return it’s own object)
Constructor function
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.honk = function() {console.log("Hooonk");};
this.beep = function() {console.log("Beeep");};
this.blub = alreadyExistingFunction;
}
new Car("Honda", "Civic", 1998)
- constructor is simply a function that sets object properties of object in
this
, as if setting any other object’s properties, just object has weird name “this”
- when called via
new
operator, it exactly sets all the properties for the newly created object
- constructor has no return statement, otherwise calling it with
new
doesn’t make sense, the newly created object would not be used (see step 4. in steps of the new
operator), wouldn’t be a constructor
- use dot notation, because the property names are not computed, i.e. only the assigned
make
becomes the argument variables value, not the make
in this.make
- object created with constructor is identical to object literal equivalent, except when using constructor also inheritance is set up (see later)
-> use constructor if wants to create many objects that inherit, object literal if only needs single generic
- syntax is different from object literal, semicolons instead of commas, equal signs instead of colons
- beware: forgetting
new
operator when calling constructor makes it modify the properties of the global object in non-strict mode without any error ⚠️ always use in strict mode, or better use classes or Object.create()
Static properties
- properties of constructor itself, since it’s like any other object can add properties to it
- not inherited to any instances, since constructor itself is not part of the instances’ prototype chain, only the constructor’s
prototype
property
- usually uses for methods instead of properties since they stay the same for all instances
- in a static method,
this
refers to the constructor, since the static method is called as method of the constructor, i.e. this
is not an object instance ❗️
Car.repairPrice = 420;
Car.repair = function(car) {
console.log("Repair done. " + car.make + " " + car.model + " looks like new.");
console.log("Cost " + this.repairPrice);
};
Car.repair(car1);
console.log(car1.repairPrice);
Inheritance
Idea
- deriving new objects from existing objects
- need to specify only what’s different, inherit what’s the same, i.e. not one huge constructor for all types of objects and neighter one full constructor for every single type of object, e.g.
SportsCar
from Car
from Vehicle
- allows for code reuse, better scalability, maintainability, readability, etc.
Prototypal Inheritance
- JS uses a prototype-based inheritance model, as opposed to the traditional class-based inheritance model, single mainstream programming language to date that does this
- in class-based languages objects are instances of class blueprints, classes can inherit from other classes, objects can not be changed
- JS is class-free, objects are the only player, objects inherit directly from other objects, objects themselves are “prototypes” from which other objects can inherit
- inheritance of objects is done via links between them, are not copies of class blueprints, if one changes the other changes as well, instead of copying behavior down the inheritance chain delegates it up the chain
- should call it “delegation” instead of “inheritance” since doesn’t copy but borrow properties, or differential inheritance because object contains only what’s different
- prototype-based system is superset of class-based system, i.e. can implement classes in prototype-based system but not the other way around
- offers more flexibility than traditional class-based inheritance, can freely add / remove properties to individual objects without having to modify whole class, allows to modify prototype objects at run-time to change inherited properties, allows to change whole inheritance chain at run-time
- does not support multiple inheritance, i.e. every object inherits only from exactly one other object
Pseudoclassical pattern
- JS doesn’t embrace its prototypal nature that objects inherit directly from other objects, instead it tries to look class oriented
- implements constructor functions to look like classes and the
new
operator so can “create” objects from them
- but constructor doesn’t create instance, are both just equally objects that are linked
- with actuall
class
syntax makes even more confusing
The [[prototype]]
property
- each object has a hidden internal property called
[[prototype]]
, which is set to another object, the object’s “prototype” object, hidden link
- since objects are addressed by reference, any changes made to this prototype object will show up in the
[[prototype]]
property
- since the prototypes are objects themselves, they have their own
[[prototype]]
properties, linking to another prototype object, this chain is called a “prototype chain”, the end of the chain is always the null
object, null
is the last prototype / commin ancestor to all objects
- any properties added to a prototype object will be available to all objects below in the prototype chain
- when accessing an object’s property, JS first searches the own properties of the object, if no matching property is found it goes up the prototype chain searching through every prototype object until the first match, if it reaches
null
it returns undefined
, i.e. doesn’t need to specify where in the prototype chain the property is, can use property as if it were own property of object
-> Prototypal inheritance is actually more “delegation” than inheritance, since properties are only referenced and not copied
-> for performance reason don’t let JS search long prototype chains
- since JS returns the first match when searching the prototype chain for a property, later properties “overshadow” earlier ones with the same name
car1.color;
Car.prototype.color = "black";
car1.color;
car1.color = "red";
car1.color;
delete car1.color;
car1.color;
delete car1.color;
car1.color;
- there can be various prototype chains, all ending at
null
, see later
- problem 1: by default the
[[prototype]]
properties of the objects are not chained to each other, would need to set it for each single object
- problem 2: changing the
[[prototype]]
property of an object is very inefficient operation, should not be done manually
- solution: constructors or
Object.create()
, see later
The prototype
property
- functions are objects and have like every other object a
[[prototype]]
property linking to a prototype object
- but they are special, because they have a second property called
prototype
which is set to yet another prototype object, can be the same prototype object as the [[prototype]]
property, e.g. like the Function
constructor has is, see later
- this other prototype object is part of a prototype chain as well
- this prototype object also has a property called
constructor
set to the function object for which it is a prototype, i.e. creating a circular reference
- when creating an object using a constructor, the
new
operator sets the [[prototype]]
property of the new object to the prototype
property of the constructor, i.e. exactly this second prototype object, (see step 2. in steps of the new
operator)
- so all instances of a constructor get this same object as their
[[prototype]]
, any changes made to it are instantly available to all instances, need only to add / change / remove properties in this single object accessible through constructor’s prototype
property
-> enables inheritance
- can access constructor from every instance through the
constructor
property of the constructor’s prototype
property which is inherited to every instance, e.g. useful if doesn’t know constructor’s name but has a instance available ❗️
const car2 = new Car("Toyota", "Corolla", 1995);
const car2 = new car1.constructor();
- by default a function’s
prototype
property is linked to the prototype
property of Object
which is linked to null
, see later how can change prototype
property of constructors for multi-level inheritance
- object literals have their
[[prototype]]
property directly set to Object.prototype
Summary
- every object has exactly one
[[prototype]]
property (orange arrow)
- every function object has an additional
prototype
property (pink arrow)
Object.prototype
is a common prototype of every typical object, (see later how can create different prototype chains using Object.create()
)
- beware: don’t simply add custom properties to
Object.prototype
since it pollutes the entire global prototype chain, instead create a single constructor for all your objects to inherit from and use its prototype
propery to add desired properties
- a function object has by default
Function.prototype
as [[prototype]]
(blue objects)
- an object literal has by default
Object.prototype
as [[prototype]]
- “own properties” are properties of object itself, not from prototype chain
- all functions have the additional
prototype
property even though they might not be used as constructors at all, any instances of a constructor function get their [[prototype]]
property linked to that prototype
property
- the prototype object confusingly has no own name and is only accessible through the constructor’s
prototype
property, e.g. Car.prototype
- calling the constructor function “constructor” is a bad name since it doesn’t “construct” the object, it just provides access to its prototype and can already fill the instance with some properties, the
new
operator does the actual construction
- → pseudoclassical pattern obscures the prototypal inheritance by introducing a detour via constructor functions
Accessing [[prototype]]
property
- the
[[prototype]]
property of an object is internal, can not be accessed like a normal property
Object.get
/set.PrototypeOf()
: prefered way
Object.prototype.__proto__
: legacy, is actually a getter / setter property that references instance via this
binding
- modifying the
[[prototype]]
of an object is a very slow operation, avoid if possible, best way of changing it is to leave it and create new object with desired prototype using Object.create()
(see later)
- can access
prototype
properties of constructor without knowing its name by walking up the inheritance chain, can modify or delete properties
Object.getPrototypeOf(car1).sayHi = function() {}
car1.sayHi();
delete Object.getPrototypeOf(car1).sayHi
car1.sayHi();
Inheriting properties
- a constructor’s
prototype
properties are available on every object instance of descendant object type, e.g. everything in Object.prototype
is available in every object (as long as it’s part of the standard prototype chain, see Object.create()
later how to create different prototype chains)
- add properties to custom constructor’s prototype property, because
- are available to all instances like own properties, but beware property shadowing
- only way if wants to easily modify / add / remove later for all instances
- exist only once in prototype object instead of being copied to every instance
Car.prototype.sayHi = function() {console.log("Hi, my make is " + this.make);};
car1.sayHi();
car2.sayHi();
- usually adds only properties to object instances and all methods to constructor’s
prototype
object, since methods usually don’t change for each object instance, and even later members in inheritance chain can access them
- don’t pollute default prototype chains by adding to built-in constructor’s
prototype
properties, e.g. Object.prototype
, instead create a single constructor for all your objects to inherit from and use its prototype
propery to add desired properties
- properties added to constructor object itself instead of it’s
prototype
property won’t inherit to instances
- remember:
this
in a method refers to the calling object, i.e. especially not the prototype object in which a method is defined and inherited from, e.g. like in car1.sayHi()
above
Data types as objects
- all data types in JS either are or behave like objects
- for each data type exists a constructor, holds relevant properties in its
prototype
property for all instances to inherit, e.g. Object
, Function
, Array
, String
, Number
, Boolean
- data type constructors
Array
, String
, Number
, Boolean
are like Car
in above diagram, have their prototype
properties linked to Object.prototype
and their [[prototype]]
properties to Function.prototype
- objects, functions, and arrays are objects, even if created using literals, no difference if created literally or using data type constructor
- strings, numbers, and booleans are primitives if created literally, not objects, different from data type constructors which create objects
- beware: don’t create primitives using constructors, don’t work as expected, use literals instead, also don’t create functions using contructor see Functions ⚠️
"Hi" === new String("Hi")
Boolean(false) === true
- when using strings, numbers, or booleans like an object, e.g. accessing a method, JS silently creates a temporary “wrapper object” to perform the desired operation on, i.e. makes primitives behave like objects
"Hello World!".toUpperCase()
" boat \n".trim()
3.141592654.toFixed(2)
3.141592654.toPrecision(3)
- wrapper objects are only one time use, are discarded immediately after use, i.e. can’t use like objects to store data
const str = "Hello";
str.data = "abc";
console.log(str.data);
- data type constructors implement a
valueOf()
method in their prototype
property, to overshadow Object.valueOf()
, returns the primitive value of the wrapper instance, e.g. Boolean(false).valueOf()
gives primitive false value, (new Number (42)).valueOf()
gives primitive 42
- data type constructors implement a
toString()
method in their prototype
property, to overshadow Object.prototype.toString()
, returns value as string, useful for all data types except for objects, returns useless ”[object type]”, can use JSON.stringify
instead to stringify object
Multi-level inheritance
Idea
- link
prototype
properties of multiple constructors in row, see diagram below
- in child constructor could make use of parent constructor to set properties
- set child constructor’s
prototype
property to parent constructor’s prototype
property to build inheritance chain
- instead of detour via constructor functions can link up individual objects directly using
Object.create()
, closer to real prototypal inheritance, but focus here on constructors
Properties
function SportsCar(..., zeroTo60) {
Car.call(this, ...);
this.zeroTo60 = zeroTo60;
}
const sportsCar1 = new SportsCar(..., 3.8);
console.log(car1.zeroTo60);
console.log(sportsCar1.zeroTo60);
- in child constructor call parent constructor to set up existing properties, e.g.
make
, model
and year
from Car
- need to use
Function.prototype.call
/apply()
to specify correct this
in parent constructor, otherwise this
would be back to default, would modify the global object in non-strict mode ⚠️
- set any additional own properties for this object type, e.g. a
zeroTo60
property just for sports cars
- until now prototype chain is not set up yet, has just set properties of object instance, e.g.
SportsCar.prototype
links still to Object.prototype
- beware: using global variables in parent constructor, parent constructor will also be called any time a child object instance is created, e.g. incrementing an id counter ❗️
- child objects don’t necessarily need to have same properties as parent objects, could skip above and just set own properties, i.e. if object types don’t have same properties but should still inherit methods from each other
Prototype chain
SportsCar.prototype = Object.create(Car.prototype);
SportsCar.prototype.constructor = SportsCar;
- set child constructor’s
prototype
property to link to its parent constructor’s prototype
property
- use
Object.create()
to create new empty object with parent constructor’s prototype
property as [[prototype]]
, use it as prototype
property of the child constructor
- don’t just set to parent constructor’s
prototype
properties, would share same prototype, no inheritance chain, makes no sense, e.g. SportsCar.prototype = Car.prototype
⚠️
- don’t just set it a new instance of parent constructor, would have all instance properties, inherited to all child objects, e.g.
SportsCar.prototype = new Car()
⚠️
- don’t just manually change the
[[prototype]]
property of the child constructor’s prototype
property to point to the parent constructor’s prototype
property, very inefficient, e.g. Object.setPrototypeOf(SportsCar.prototype) = Car.prototype
⚠️
- need to correct the
constructor
property within the child constructor’s new prototype
property to point back to it, (would’t be necessary if directly changed prototype, but won’t ever do that)
- JS does not support multiple inheritance because any object can have only exactly one
[[prototype]]
property
function SportsCar(..., zeroTo60) {
Car.call(this, ...);
this.zeroTo60 = zeroTo60;
}
SportsCar.prototype = Object.create(Car.prototype);
SportsCar.prototype.constructor = SportsCar;
SportsCar.prototype.scream = function() {console.log("Brummm");};
const sportsCar1 = new SportsCar(..., 3.8);
sportsCar1.honk();
sportsCar1.scream();
- accessing properties may go up the prototype chain, but
this
points back at instance, is rooted at call site, can reuse methods no matter where they are on the prototype chain, i.e. this-aware methods on prototype chain work as if they were on instance
function Car(...) {
}
Car.prototype.sell = function() {
console.log(this.sellingPrice != null ? `You can buy me for ${this.sellingPrice}€.` : "This car is not sold.");
}
function UsedCar(..., sellingPrice) {
Car.call(this, ...);
this.sellingPrice = sellingPrice;
}
UsedCar.prototype = Object.create(Car.prototype);
UsedCar.prototype.constructor = UsedCar;
const car1 = new Car(...);
car1.sell();
const car2 = new UsedCar(..., 420);
car2.sell();
Classes
- mostly syntactical sugar to make prototype-based inheritance look more class-oriented (with ES6)
- beware: they still work differently than in class-oriented languages ⚠️
- automate some tasks like setting up multi-level inheritance chain, bundle code together, cleaner than verbose constructor function syntax 🎉
- are just special constructor functions
- not hoisted ❗️
- class body is in strict mode by default ❗️
- error if called without
new
, i.e. no accidental modification of global oject 🎉
- rarely uses constructor functions anymore in favor of classes, but needed to understand them to understand how classes work
Class constructor
- can use like normal constructor
constructor
method is called with arguments of class
- if no
constructor
method is specified, it defaults to an empty function
- don’t confuse
constructor
method with constructor
property in classe’s prototype
property ❗️
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.honk = function() {console.log("Hooonk");};
this.beep = function() {console.log("Beeep");};
this.blub = alreadyExistingFunction;
}
}
new Car("Honda", "Civic", 1998)
Instance methods
- methods in the class other than
constructor
are directly put into the instance’s prototype
property
- no need to add them separately to constructor’s
prototype
property afterwards dangling behind in code 🎉
- use only ES6 method shorthand notation
- no comma between class methods
- any assigned variables will be interpreted as instance properties as if they were in constructor, see Instance fields, i.e. assigned functions including function expressions and arrow functions are put on instance itself ❗
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
honk() {console.log("Hooonk");}
beep() {console.log("Beeep");}
blub = alreadyExistingFunction
}
const car1 = new Car("Honda", "Civic", 1998);
car1.honk();
- can make private - not visible outside class - by prepending name with
#
symbol (with ES2022)
- private methods are separate from non-private ones, can have private and non-private with same name, otherwise would break encapsulation
- private methods are scoped to containing class, i.e. subclass can have different method with name of private method in parent class
class Person {
sayHi() {
console.log("Hello world!");
}
#sayHi() {
console.log("Wello horld!");
}
}
Instance fields
- properties declared outside of constructor (with ES2022)
- behave as if they were inside constructor, i.e. get created for every instance, are not on prototype
- to look more like classical syntax
- also called “class fields”
class Car {
constructor(make, model, year) {
}
type = "Car";
}
class Car {
constructor(make, model, year) {
this.type = "Car";
}
}
- can make private - not visible outside class - by prepending name with
#
symbol, must declare it outside of constructor
- private fields are separate from non-private ones, can have private and non-private with same name, otherwise would break encapsulation
- private fields are scoped to containing class, i.e. subclass can have different field with name of private field in parent class
- private fields are only accessible using dot notation, not bracket notation, i.e. also not computed property keys ❗️
class Person {
#name = "Batman";
#age;
constructor(name, age) {
this.name = name;
this.#age = age;
}
}
Static methods
- methods of class object itself, instead of instance
- no need to add them separately to constructor object afterwards dangling behind in code 🎉
- can make private - not visible outside class - by prepending name with
#
symbol (with ES2022)
class Car {
constructor(make, model, year) {
}
static repair(car) {
console.log("Repair done. " + car.make + " " + car.model + " looks like new.");
}
}
const car1 = new Car("Honda", "Civic", 1998);
Car.repair(car1);
Static fields
- properties of class object itself, instead of instance (with ES2022)
- no need to add them separately to constructor object afterwards dangling behind in code 🎉
- can make private - not visible outside class - by prepending name with
#
symbol
class Car {
constructor(make, model, year) {
}
static repairPrice = 420;
static repair(car) {
console.log("Repair done. " + car.make + " " + car.model + " looks like new.");
console.log("Cost " + this.repairPrice + "€");
}
}
const car1 = new Car("Honda", "Civic", 1998);
Car.repair(car1);
Multi-level inheritance
- use
extends
to let child class instances inherit properties of parent class instances
- automatically sets up prototype chain, no need to set the constructor’s
prototype
property manually anymore 🎉
- parent class constructor is called via
super
keyword, automatically passes the correct this
, no need to specify it with Function.prototype.apply
/call()
anymore 🎉
class SportsCar extends Car {
constructor(..., zeroTo60) {
super(...);
this.zeroTo60 = zeroTo60;
}
scream() {console.log("Brummm");}
}
const sportsCar1 = new SportsCar(..., 3.8);
sportsCar1.honk();
sportsCar1.scream();
- if no constructor is specified in the child class it uses an empty constructor with a super call
constructor(...args) {
super(...args);
}
- to use
this
in the constructor of a derived class, super()
must have been called before ❗️
- since
super()
is called first, can overwrite any properties set by parent class, and overshadow any methods of parent classes prototype
see Constructors to remember how setting properties and methods work ❗️
class Named {
constructor() {
this.name = "foobar";
}
sayHi() {
console.log("Hello World!");
}
}
class Person extends Named {
constructor(name) {
super();
this.name = name;
}
sayHi() {
console.log(`Hi, my name is ${this.name}.`);
}
}
const q = new Named();
q.sayHi();
const p = new Person("Peter");
p.sayHi();
Mixins
- a class containing methods to be used by other classes without having to be parent class of those other classes
- used for sharing the same tools across different classes
- since JS doesn’t support multiple inheritance, needs to use mixin as parent class, becomes part of inheritance chain ❗️
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, my name is ${this.name}. I have ${this.hat ? 'a' : 'no'} hat and ${this.shoe ? 'a' : 'no'} shoe.`);
}
}
function Hat(classToExtend) {
return class extends classToExtend {
constructor(...args) {
super(...args);
this.hat = true;
}
}
}
function Shoe(classToExtend) {
return class extends classToExtend {
constructor(...args) {
super(...args);
this.shoe = true;
}
}
}
const PersonWithHat = Hat(Person);
const PersonWithShoe = Shoe(Person);
const PersonWithHatAndShoe = Shoe(Hat(Person));
const p = new Person("Peter");
p.sayHi();
const q = new PersonWithHat("Jim");
q.sayHi();
const r = new PersonWithShoe("Lisa");
r.sayHi();
const s = new PersonWithHatAndShoe("Sarah");
s.sayHi();
Built-in constructors
- there are many built-in object types, the data type wrappers,
Math
, Date
, RegExp
, JSON
, Intl
, error object types, etc.
- they can construct instances, have built-in own properties, or inherit properties to all their descendants via their
prototype
property
- Browser APIs add many more object types
Object
object type
Constructor
Object()
: object constructor
- equivalent to object literal
Properties
Object.create()
: creates new object with argument as [[prototype]]
, only prototype properties on custom prototype chain are available, i.e. if argument is null
creates empty prototype chain, collision free from otherwise built-in properties like “constructor” ❗️
Object.getOwnPropertyNames()
: returns array with own property names including non-enumerable ones, but not Symbols
Object.keys()
: returns array with own enumerable string-keyed property names as strings, can use any array methods on it like iteration 🎉, not in Object.prototype
because of fear of collision with existing code
Object.values()
: returns array with own enumerable string-keyed property values, complement to Object.keys()
(with ES2017)
Object.entries()
: returns array with own enumerable string-keyed property [key, value]
pairs, complement to Object.keys()
and Object.values()
(with ES2017)
Object.get
/setPrototypeOf()
: get / set [[prototype]]
property of an object, slow, don’t use ❗️instead create new object with desired [[prototype]]
using Object.create()
Object.defineProperty
/ies()
: add property/ies to object, can specify property descriptor, returns object
Object.getOwnPropertyDescriptor()
: returns property descriptor for own property
Object.getOwnPropertyDescriptors()
: returns property descriptors for all own properties (with ES2017)
Object.getOwnPropertyNames()
: returns names of own properties including non-enumerable ones, except of symbol properties
Object.getOwnPropertySymbols()
: returns symbols of own properties including non-enumerable ones
Object.preventExtensions()
: disallows adding new properties, once non-extensible an object can not be made extensible again
beware: does not prevent deletion of properties, also can be “added” via prototype chain ❗️
Object.seal()
: makes object non-extensible and all properties non-configurable, i.e. can not add or remove properties anymore, once sealed an object can not be unsealed again
Object.freeze()
: seals object and makes all properties non-writable, i.e. properties are immutable, once freezed an object can not be unfreezed again
beware: only shallow freeze, nested objects can still be mutated ❗️
Object.isExtensible()
: checks if object is extensible
Object.isSealed()
: checks if object is sealed
Object.isFrozen()
: checks if object is frozen
Prototype properties
Object.prototype.constructor
: reference to constructor function of object, available on every object, overshadowed in constructor’s prototype
property
Object.prototype.__proto__
: reference to [[prototype]]
, deprecated, use Object.get
/setPrototypeOf()
instead, 🚫
Object.prototype.hasOwnProperty()
: true if object has given own property, else false
Object.prototype.isPrototypeOf()
: checks if given object is part of the object’s prototype chain, (instanceof operator checks if Constructor.prototype
is part of the object’s prototype chain)
Object.prototype.toString()
: string representing an object, not useful since only ”[object type]”, can use JSON.stringify
instead to stringify object, or overshadow it on constructor’s prototype
Object.prototype.valueOf()
: primitive value of an object, useful for primitive data type wrappers, overshadowed anyways
Date
object type
- dates are stored as milliseconds since 01.01.1970, 00:00:00 UTC (“Unix epoch”)
- create date object using
new Date(...)
- many inherited properties and methods available to convert dates
Date()
, i.e. like forgetting new
, returns current date and time as string, different behavior than normal constructor who would modify global object ❗️
Date.now()
returns number of milliseconds since epoch as string
RegExp
object type
- regular expression constructor takes string, can use dynamically,
- second parameter is string containing flags
g
, i
and m
- needs to string escape backslash and quotes ⚠️
- use regular expression literal whenever possible, enclosed in forward slashes, flags can be appended
- RegEx has no support for internationalisation, needs to pick Unicode characters yourself
- use multiple capturing groups and select only desired ones, instead of using complex positive / negative lookahead / lookbehind
Resources