JavaScript Objects (as a Data Type and as a Class)

Object Definitions

Object Prototypes

The prototype is a property available with all JavaScript objects. It allows you to add new properties and methods to strings.

Syntax

object.prototype.name = value

Use the prototype property to add a new property to all objects of a given type:

function employee(name, jobtitle, born) {
  this.name = name;
  this.jobtitle = jobtitle;
  this.born = born;
}
employee.prototype.salary = 2000;

const fred = new employee("Fred Flintstone", "Caveman", 1970);

Inheritance

JavaScript's prototype system makes it possible to create a new class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the length getter.

In object-oriented programming terms, this is called inheritance. The new class inherits properties and behavior from the old class.

class LengthList extends List {
#length;

  constructor(value, rest) {
    super(value, rest);
    this.#length = super.length;
  }

  get length() {
    return this.#length;
  }
}

console.log(LengthList.fromArray([1, 2, 3]).length);
// → 3

The use of the word extends indicates that this class shouldn't be directly based on the default Object prototype but on some other class. This is called the superclass. The derived class is the subclass.

To initialize a LengthList instance, the constructor calls the constructor of its superclass through the super keyword. This is necessary because if this new object is to behave (roughly) like a List, it is going to need the instance properties that lists have.

The constructor then stores the list's length in a private property (#length). If we had written this.length there, the class's own getter would have been called, which doesn't work yet, since #length hasn't been filled in yet. We can use super.something to call methods and getters on the superclass's prototype, which is often useful.

The instanceof Operator

It is occasionally useful to know whether an object was derived from a specific class. For this, JavaScript provides a binary operator called instanceof.

console.log(new LengthList(1, null) instanceof LengthList);
// → true
console.log(new LengthList(2, null) instanceof List);
// → true
console.log(new List(3, null) instanceof LengthList);
// → false
console.log([1] instanceof Array);
// → true

The operator will see through inherited types, so a LengthList is an instance of List. The operator can also be applied to standard constructors like Array.

Almost every object is an instance of Object.

Object Methods

// Copies properties from a source object to a target object
Object.assign(target, source)

// Creates an object from an existing object
Object.create(object)

// Returns an array of the key/value pairs of an object
Object.entries(object)

// Creates an object from a list of keys/values
Object.fromEntries()

// Returns an array of the keys of an object
Object.keys(object)

// Returns an array of the property values of an object
Object.values(object)

// Groups object elements according to a function
Object.groupBy(object, callback)

Object.assign()

The Object.assign() method copies properties from one or more source objects to a target object.

// Create Target Object
const person1 = {
  firstName: "John",
  lastName: "Doe",
  age: 50,
  eyeColor: "blue"
};

// Create Source Object
const person2 = {firstName: "Anne",lastName: "Smith"};

// Assign Source to Target
Object.assign(person1, person2);

Object.keys(OBJECT)

Returns an array of the keys to the properties as strings for objects and as natural numbers or indexes for arrays.

Object.entries(OBJECT)

ECMAScript 2017 added the Object.entries(OBJECT) method to objects.

Object.entries(OBJECT) returns an array of the key/value pairs in an object:

Example

const person = {
  firstName : "John",
  lastName : "Doe",
  age : 50,
  eyeColor : "blue"
};

let text = Object.entries(person);

Object.entries(OBJECT) makes it simple to use objects in loops:

Example

const fruits = {Bananas:300, Oranges:200, Apples:500};

let text = "";
for (let [fruit, value] of Object.entries(fruits)) {
  text += fruit + ": " + value + "<br>";
}

Object.entries(OBJECT) also makes it simple to convert objects to maps:

Example

const fruits = {Bananas:300, Oranges:200, Apples:500};

const myMap = new Map(Object.entries(fruits));

Object.entries(OBJECT) is supported in all modern browsers since March 2017:

Object.fromEntries()

The fromEntries() method creates an object from a list of key/value pairs.

Example

const fruits = [
  ["apples", 300],
  ["pears", 900],
  ["bananas", 500]
];

const myObj = Object.fromEntries(fruits);

Object.values(OBJECT)

Object.values(OBJECT) is similar to Object.entries(OBJECT), but returns a single dimension array of the object values:

Example

const person = {
  firstName : "John",
  lastName : "Doe",
  age : 50,
  eyeColor : "blue"
};

let text = Object.values(person);

Object.values(OBJECT) is supported in all modern browsers since March 2017:

Object.groupBy()

ES2024 added the Object.groupBy() method to JavaScript.

The Object.groupBy() method groups elements of an object according to string values returned from a callback function.

The Object.groupBy() method does not change the original object.

Example

// Create an Array
const fruits = [
  {name:"apples", quantity:300},
  {name:"bananas", quantity:500},
  {name:"oranges", quantity:200},
  {name:"kiwi", quantity:150}
];

// Callback function to Group Elements
function myCallback({ quantity }) {
  return quantity > 200 ? "ok" : "low";
}

// Group by Quantity
const result = Object.groupBy(fruits, myCallback);

Object.with(key, value)

ES2023 added the Array with() method as a safe way to update elements in an array without altering the original array.

Example

const months = ["Januar", "Februar", "Mar", "April"];
const myMonths = months.with(2, "March");

Spread (...) and Rest

The ... operator expands an array into individual elements. This can be used join arrays/objects, as in

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const arr3 = [...arr1, ...arr2];

In the example above, ...arr1 expands arr1 into single elements, ...arr2 expands arr2 into single elements, and arr3 is constructed using ...arr1 and ...arr2.


The rest operator (...) allows us to destruct an array and collect the leftovers:

let a, rest;
const arr1 = [1,2,3,4,5,6,7,8];

[a, ...rest] = arr1;

Another example:

let a, b, rest;
const arr1 = [1,2,3,4,5,6,7,8];

[a, b, ...rest] = arr1;

Getters, Setters, and Statics

Interfaces often contain plain properties, not just methods. For example, Map objects have a size property that tells you how many keys are stored in them.

It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called getters and are defined by writing get in front of the method name in an object expression or class declaration.

let varyingSize = {
  get size() {
    return Math.floor(Math.random() * 100);
  }
};

console.log(varyingSize.size);
// → 73
console.log(varyingSize.size);
// → 49

Whenever someone reads from this object's size property, the associated method is called. You can do a similar thing when a property is written to, using a setter, which is preceded by keyword set.

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }
  set fahrenheit(value) {
    this.celsius = (value - 32) / 1.8;
  }

  static fromFahrenheit(value) {
    return new Temperature((value - 32) / 1.8);
  }
}

let temp = new Temperature(22);
console.log(temp.fahrenheit);
// → 71.6
temp.fahrenheit = 86;
console.log(temp.celsius);
// → 30

The Temperature class allows you to read and write the temperature in either degrees Celsius or degrees Fahrenheit, but internally it stores only Celsius and automatically converts to and from Celsius in the fahrenheit getter and setter.

Sometimes you want to attach some properties directly to your constructor function rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances.

Inside a class declaration, methods or properties that have static written before their name are stored on the constructor. For example, the Temperature class allows you to write Temperature.fromFahrenheit(100) to create a temperature using degrees Fahrenheit.

let boil = Temperature.fromFahrenheit(212);
  console.log(boil.celsius);
// → 100

Stringifying

Use JSON.stringify(OBJECT)


          

Next, you can store your thus generated string in local storage, then retrieve it

const myObj = {name: "John", age: 31, city: "New York"};
const myJSON = JSON.stringify(myObj);
localStorage.setItem("testJSON", myJSON);

// Retrieving data:
let text = localStorage.getItem("testJSON");
let obj = JSON.parse(text);
document.getElementById("demo").innerHTML = obj.name;

Object Properties

Checking for Properties: Existence, undefined-ness, null...

Object.hasOwn(OBJ,PROPERTY) and Object.hasOwnProperty(OBJ,PROPERTY)

If you want to know if an object physically contains a property (and it is not coming from somewhere up on the prototype chain) then object.hasOwnProperty is the way to go. All modern browsers support it.

If what you're looking for is if an object has a property on it that is iterable (when you iterate over the properties of the object, it will appear) then doing: prop in object will give you your desired effect.


            

const object1 = {
  prop: 'exists'
};

console.log(Object.hasOwn(object1, 'prop'));
// expected output: true

Comparing to undefined*

...

const example = {prop = "exists"};
console.log(example.prop !== undefined);     // => true

...

Using operator in

Operator in returns true for direct or inherited properties.

An example:

const example = {prop = "exists"};

"prop" in example; // true
"toString" in example; // true
"hasOwnProperty" in example; // true

...

Overriding Derived Properties

When you add a property to an object, whether it is present in the prototype or not, the property is added to the object itself. If there was already a property with the same name in the prototype, this property will no longer affect the object, as it is now hidden behind the object's own property.

Private Properties

It is common for classes to define some properties and methods for internal use that are not part of their interface. These are called private properties, as opposed to public ones, which are part of the object's external interface.

To declare a private method, put a # sign in front of its name. Such methods can be called only from inside the class declaration that defines them.

Object Variable this: a Reference to Itself

In JavaScript, the this keyword refers to an object.

The this keyword refers to different objects depending on how it is used:

Object Protection*

The Iterator Interface

The object given to a for/of loop is expected to be iterable. This means it has a method named with the Symbol.iterator symbol (a symbol value defined by the language, stored as a property of the Symbol() function).

When called, that method should return an object that provides a second interface, iterator. This is the actual thing that iterates. It has a next() method that returns the next result. That result should be an object with a value property that provides the next value, if there is one, and a done property, which should be true when there are no more results and false otherwise.

Note that the next, value, and done property names are plain strings, not symbols. Only Symbol.iterator, which is likely to be added to a lot of different objects, is an actual symbol.

We can directly use this interface ourselves, say, on a literal string:

let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
console.log(okIterator.next());
// → {value: "K", done: false}
console.log(okIterator.next());
// → {value: undefined, done: true}