Book of Coding


Encapsulate state and behavior in objects

JavaScript is a programming language that also supports the object-oriented programming paradigm.


1. Introduction to object-oriented programming

Software systems can become quite complex and extensive. Object-oriented programming aims to reduce some of the complexity of software systems by attempting to describe the system using various objects. Objects have a state and a behavior. The state of an object is described by its property and by the links to other objects. The behavior of an object is defined by the method.

Preview

The state of the ShoppingCart object includes the individual items that are in the respective shopping cart and represents this via the items property. The methods of ShoppingCart represent the behavior of the respective shopping cart: New items can be added via addItem(), items are removed via removeItem() and the shopping cart is emptied via clear().


2. Create objects via the literal notation

There are various ways to create objects in JavaScript. One of these is the literal notation:

Complete Code - Examples/Part_114/main.js...

 const item = {
    name: 'Nike Dunk Low Retro',
    product: 'Shoes',
    price: 119.99,
    size: '9',
    printDescription: function () {
       console.log(`${this.product}: ${this.name}`);
    }
 }
 console.log(item.name);     // "Nike Dunk Low Retro"
 console.log(item.product);  // "Shoes"
 console.log(item.price);    // 119.99
 console.log(item.size);     // "9"
 item.printDescription();    // "Shoes: Nike Dunk Low Retro"
                        

The keyword this used within the printDescription() method represents the object on which the method is executed.


3. Create objects via Object.entries()

Since ES2019, it has also been possible to create objects based on arrays using the Object.entries() method. The individual elements in the array must again be arrays with two elements each: the first element defines the name of the object property, and the second element defines the value that this property should contain:

Complete Code - Examples/Part_115/main.js...

 const array = [
   ['name', 'Nike Dunk Low Retro'],
   ['price', 119.99],
   ['product', 'Shoes'],
   ['size', '9'],
   ['printDescription', function () {
    console.log(`${this.product}: ${this.name}`);
   }]
 ];

 const item = Object.fromEntries(array);
 console.log(item.name);     // "Nike Dunk Low Retro"
 console.log(item.price);    // 119.99
 console.log(item.product);  // "Shoes"
 console.log(item.size);     // "9"
 item.printDescription();    // "Shoes: Nike Dunk Low Retro"
                        

4. Create objects via constructor functions

Another way to create objects is via the constructor function. In terms of structure, they are nothing more than normal functions. But what makes a function a constructor function is to call it with a preceding new. The function then creates a new object and returns it.

Complete Code - Examples/Part_116/main.js...

 function Item(name, price, product, size) {
   this.name = name;
   this.price = price;
   this.product = product;
   this.size = size;
   this.printDescription = function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }
                        

Here it is not necessary to use a return within the function. Instead, the newly created object is returned implicitly, which can then be accessed within the constructor function using the keyword this.

To call the function shown as a constructor function, the keyword new is placed in front of it. Arguments can also be passed to a constructor function. These arguments are then available within the function via the parameter variables, as is usual with "normal" functions. A constructor function can be used several times to create different object instances. This does not apply to literal notation, however, where only individual objects are ever created.

Complete Code - Examples/Part_117/main.js...

 const item = new Item(
   'Nike Dunk Low Retro',
   'Shoes',
   119.99,
   '9'
 )

 console.log(item.name);         // "Nike Dunk Low Retro"
 console.log(item.product);      // "Shoes"
 console.log(item.price);        // 119.99
 console.log(item.size);         // "9"
 item.printDescription();        // "Shoes: Nike Dunk Low Retro"

 const item2 = new Item(
   'Nike Air Force 1',
   'Shoes',
   95.99,
   '9'
 )

 console.log(item2.name);        // "Nike Air Force 1"
 console.log(item2.product);     // "Shoes"
 console.log(item2.price);       // 95.99
 console.log(item2.size);        // "9"
 item2.printDescription();       // "Nike Air Force 1"
                        

Notation for constructor functions

Constructor functions follow the Upper-CamelCase notation, i.e. they begin with a capital letter, e.g. Item. If a function name consists of several words, each word begins with a capital letter.


Prototypes

Each constructor function internally manages a so-called prototype, i.e. an object that serves as the basis for the objects to be created via the constructor function. This prototype is stored in the function in the prototype property. If an object is now created with such a constructor function, the object is therefore based on the prototype stored there. After the object has been created, the prototype can be determined either via the __proto__ property of the object or via the Object.getPrototypOf() method. The constructor property can also be used to determine which constructor function was used to create an object instance.

Complete Code - Examples/Part_118/main.js...

 const item = new Item(
   'Nike Dunk Low Retro',
   'Shoes',
   119.99,
   '9'
 )

 console.log(Item.prototype);              // Item {}
 console.log(item.__proto__);              // Item {}
 console.log(Object.getPrototypeOf(item)); // Item {}
 console.log(item.constructor);            // function Item(...)
                            

If an object is created using a constructor function, the typeof operator returns the value object for this object. The instanceof operator, on the other hand, can be used to check whether an object was created using a specific constructor function.

Complete Code - Examples/Part_119/main.js...

 console.log(typeof item);           // object
 console.log(item instanceof Item);  // true
                            

5. Create objects using classes

With version ES6, so-called classes were introduced in JavaScript. In software development, classes are a kind of blueprint for objects. Within a class, you define the properties and methods that the objects to be created should have.

Classes are defined using the keyword "class". The name of the class is noted after the keyword, as well as the class body in curly brackets {}. Within the class body, you define the methods that are to be available for object instances of the class. The methods with the name constructor() play a special role here, as they are called implicitly when a new object instance of the corresponding class is created.

Complete Code - Examples/Part_120/main.js...

 class Item {
   constructor(name, price, product, size) {
     this.name = name;
     this.price = price;
     this.product = product;
     this.size = size;
 }

 printDescription() {
    console.log(`${this.product}: ${this.name}`);
    }
 }
                        

This example shows the definition of such a class. The content of the constructor method corresponds exactly to the content of the constructor function shown in Part_116. The constructor() method also does not require a return, but implicitly returns the corresponding object instance. And just as with constructor functions, the keyword this refers to the object instance that is created by calling the method.

To create an object instance of a class, proceed in the same way as with constructor functions by using the keyword new. new Item() creates a new object instance of the class Item. This implicitly calls the constructor() method of the class:

Complete Code - Examples/Part_121/main.js...

 const item = new Item(
   'Nike Dunk Low Retro',
   'Shoes',
   119.99,
   '9'
 )

 console.log(item.name);         // "Nike Dunk Low Retro"
 console.log(item.product);      // "Shoes"
 console.log(item.price);        // 119.99
 console.log(item.size);         // "9"
 item.printDescription();        // "Shoes: Nike Dunk Low Retro"

 const item2 = new Item(
   'Nike Air Force 1',
   'Shoes',
   95.99,
   '9'
 )

 console.log(item2.name);        // "Nike Air Force 1"
 console.log(item2.product);     // "Shoes"
 console.log(item2.price);       // 95.99
 console.log(item2.size);        // "9"
 item2.printDescription();       // "Nike Air Force 1"
                        

The new ES2015 class syntax is merely a syntactic alternative to the use of constructor functions. Classes also have a prototype property, which contains the base object, i.e. the prototype, on the basis of which instances of the class are created. Instances of the class also have the two properties __proto__ and constructor. The former references the prototype of the instance, the latter the class via which the instance was created.

Complete Code - Examples/Part_122/main.js...

 class Item {
   constructor(name, price, product, size) {
   ...
  }
  ...
 }

 const item = new Item(
   'Nike Dunk Low Retro',
   'Shoes',
   119.99,
   '9'
 )

 console.log(Item.prototype);              // Item {}
 console.log(item.__proto__);              // Item {}
 console.log(Object.getPrototypeOf(item)); // Item {}
 console.log(item.constructor);            // function class Item(...)
                        

typeof vs. instanceof

If an object is created using a class, the typeof operator returns the value object for this object. The instanceof operator, on the other hand, can be used to check whether the object was created using the respective class.

Complete Code - Examples/Part_123/main.js...

 class Item {
   constructor(name, price, product, size) {
     this.name = name;
     this.price = price;
     this.product = product;
     this.size = size;
 }

 printDescription() {
   console.log(`${this.product}: ${this.name}`);
   }
 }

 const item = new Item(
   'Nike Dunk Low Retro',
   'Shoes',
   119.99,
   '9'
 )

 console.log(typeof item);               // object
 console.log(item instanceof Item);      // true
                            

6. Create objects using the Object.create() function

Another way to create objects is via the helper method Object.create(). This method expects the prototype of the object to be created as the first parameter and a configuration object as the second parameter, which can be used to configure the properties and methods of the object. The properties of this configuration object represent the names of the properties of the object to be created (name, price, product, size). An object is stored as the value in each case, which can be used to define the so-called property attributes (value, writable, enumerable, configurable, set, get) in addition to the value.

Complete Code - Examples/Part_124/main.js...

 const item = Object.create(Object.prototype, {
   name: {
     value: 'Nike Dunk Low Retro'
   },
   price: {
     value: 119.99
   },
   product: {
     value: 'Shoes'
   },
   size: {
     value: '9'
   },
   printDescription: {
     value: function() {
       console.log(`${this.product}: ${this.name}`);
     }
   }
 });
 console.log(item.name);         // "Nike Dunk Low Retro"
 console.log(item.price);        // 119.99
 console.log(item.product);      // "Shoes"
 console.log(item.size);         // "9"
 item.printDescription();        // "Shoes: Nike Dunk Low Retro"
                        

Property attributes

The property attributes can be used to configure individual object properties. In addition to the 'value' property, which can be used to define the value of the property, there are other property attributes.


  • writable: Specifies via a Boolean value whether the respective property can be overwritten, i.e. whether a new value may be assigned after initialization. By default, this attribute has the value false.
  • enumerable: Specifies via a Boolean value whether the respective property is enumerable, i.e. whether the property is added when iterating over the properties of the corresponding object. By default, this attribute has the value false.
  • configurable: Specifies via a Boolean value whether the property attribute itself can be changed for the respective property, i.e. whether the property can be configured retrospectively via the attributes. By default, this attribute has the value false.
  • set: This specifies which function is called when the property is accessed by writing, can only be used for so-called access properties.
  • get: This specifies which function is called when the property is accessed read-only, can only be used for access properties.

Complete Code - Examples/Part_125/main.js...

 const item = Object.create(Object.prototype, {
   name: {
     value: 'Nike Dunk Low Retro',
     writable: false,
     configurable: true,
     enumerable: true
   },
   price: {
     value: 119.99,
     writable: true,
     configurable: true,
     enumerable: true
   },
   product: {
     value: 'Shoes',
     writable: false,
     configurable: true,
     enumerable: true
   },
   size: {
     value: '9',
     writable: false,
     configurable: true,
     enumerable: false       // The "size" property is not output for iteration.
   },
   printDescription: {
     value: function() {
       console.log(`${this.product}: ${this.name}`);
     }
   }
 });
 for(let property in item) {
   console.log(property);    // Output: "name", "price", "product"
 }
 item.name = 'Nike Air Force 1';
 console.log(item.name);     /* "Nike Dunk Low Retro", because the 
                             property "name" is not "writeable" */
                        
 item.price = 95.99;
 console.log(item.price);    /* "95.99", because for the property "price" 
                            the "writable" attribute has the value "true" */
                            

Here, the properties for writable, configurable and enumerable are assigned different values. The properties for which writable has been set to false cannot be assigned new values after the object has been created. The price property is the exception here, as it is the only property whose value can be reset. The properties for which the enumerable attribute has been set to true are output during iteration via the object property. The size property is not output.

To access the property attributes of a property or method, the Object.getOwnProbertyDescriptor() method is available. This method expects the name of the property/method for which the attributes are to be determined as the first parameter. The method returns an object with the known properties: value, writable, enumerable, configurable, set, get.

Complete Code - Examples/Part_126/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: '9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }
 const propertyDescriptor = Object.getOwnPropertyDescriptor(item, 'name');
 console.log(propertyDescriptor.enumerable);       // true
 console.log(propertyDescriptor.configurable);     // true
 console.log(propertyDescriptor.writable);         // true
 console.log(propertyDescriptor.value);            // "Nike Dunk Low Retro"
                            

What is the best way to create an object?
  • For simple objects and of which only one instance is required, the object literal notation is used.
  • If several object instances of an object type are to be created, it is advisable to use the new class syntax rather than the constructor function. Constructor functions are now only used in exceptional cases.
  • The Object.create() method can be used if the class syntax is not available or if property attributes are used when creating an object.

7. Access properties and call methods

In order to access object properties or call object methods, the so-called dot notation is generally used:

Complete Code - Examples/Part_127/main.js...

 const itemName = item.name;
 item.printDescription();
                        

Here, the name property of the item object is accessed and the printDescription() method of the same object is called.

As an alternative to the dot notation, properties and methods can also be accessed by writing the name of the property or method in square brackets {} after the name of the object. To access the object method printDescription() using this notation, only the name of the method is written in the brackets:

Complete Code - Examples/Part_128/main.js...

 const itemName = item['name'];
 item['printDescription']();
                        

In principle, dot notation or bracket notation can be used to access the properties and methods of an object. In some cases, however, only the bracket notation can be used. This is always the case if the name of the respective property or method contains the minus sign. If these names were used in combination with dot notation, Interpreter would detect a syntax error because the minus sign is interpreted as a subtraction operator:

Complete Code - Examples/Part_129/main.js...

 const person = {
   'first-name': 'Rick',
   'last-name': 'Sample'
 }
 // console.log(person.first-name);  // Syntax error
 // console.log(person.last-name);   // Syntax error
 console.log(person['first-name']);  // "Rick"
 console.log(person['last-name']);   // "Sample"

 const firstName = 'first-name';
 const lastName = 'last-name';

 console.log(person[firstName]);  // "Rick"
 console.log(person[lastName]);   // "Sample"

 const name = 'name';
 const prefixFirstName = 'first-';
 const prefixLastName = 'last-';

 console.log(person[prefixFirstName + name]);  // "Rick"
 console.log(person[prefixLastName + name]);   // "Sample"
                        

Even if the name of the property or method is in a variable or is assembled at runtime, you are dependent on the bracket notation.


Setter and Getter

Instead of accessing the properties directly, special methods are used in object-oriented programming to set new properties or return the values of properties. These types of methods are called setter methods (which set a new value to a property) and getter methods (which return the value of a property). The advantage is that it is a good way to validate values that are to be assigned to a property, i.e. to check their validity. There are the keywords set for the setter methods and get for getter methods. These keywords can be used in combination with the object literal notation, with constructor functions, with classes and in combination with the Object.create() method. To avoid naming conflicts between properties and methods, properties often start with an underscore _.

  • Setter and getter when using the object literal notation:
Complete Code - Examples/Part_130/main.js...

 const item = {
   _name: 'Nike Dunk Low Retro',
   _price: 119.99,
   _product: 'Shoes',
   _size: '9',
   set name(newName) {
     if(typeof newName === 'string') {
       console.log('Set new name');
       this._name = newName;
     } else {
       throw new TypeError('Name must be a character string.')
     }
   },
   get name() {
     console.log('Return name');
     return this._name;
   }
   /* Analog for the other properties */
 }
 console.log(item.name); // "Return name"
 // "Nike Dunk Low Retro"
 item.name = 'Nike Dunk Low Retro - Sold out';
 // "Set new name"
                            

Data encapsulation

The technical term for only allowing access to the properties of an object via setters and getters is data encapsulation. This protects the properties from direct access from outside (in the example above, the properties are accessible even without the use of setters and getters and are therefore not protected from direct access).

  • Setters and getters when using constructor functions:
Complete Code - Examples/Part_131/main.js...

 function Item(name, price, product, size) {
   this._name = name;
   this._price = price;
   this._product = product;
   this._size = size;
 }
 Item.prototype = {
   set name(newName) {
     if(typeof newName === 'string') {
       console.log('Set new name');
       this._name = newName;
     } else {
       throw new TypeError('Name must be a character string.')
     }
   },
   get name() {
     console.log('Return name');
     return this._name;
   }
   /* Analog for the other properties */
 };
 const item = new Item(
   'Nike Dunk Low Retro',
   119.99,
   'Shoes',
   '9'
 )
 console.log(item.name); // "Return name"
 // "Nike Dunk Low Retro"
 item.name = 'Nike Dunk Low Retro - Sold out';
 // "Set new name"
                                

  • Getter and setter when using classes
Complete Code - Examples/Part_132/main.js...

 class Item {
   constructor(name, price, product, size) {
     this._name = name;
     this._price = price;
     this._product = product;
     this._size = size;
   }
   set name(newName) {
     if(typeof newName === 'string') {
       console.log('Set new name');
       this._name = newName;
     } else {
       throw new TypeError('Name must be a character string.')
     }
   }
   get name() {
     console.log('Return name');
     return this._name;
   }
   /* Analog for the other properties */
 }
 const item = new Item(
   'Nike Dunk Low Retro',
   119.99,
   'Shoes',
   '9'
 )
 console.log(item.name); // "Return name"
 // "Nike Dunk Low Retro"
 item.name = 'Nike Dunk Low Retro - Sold out';
 // "Set new name"
                                

  • Getter and setter when using Object.create()
Complete Code - Examples/Part_133/main.js...

 const item = Object.create(Object.prototype, {
   name: {
     set: function(newName) {
       if (typeof newName === 'string') {
         console.log('Set new name');
         this._name = newName;
       } else {
         throw new TypeError('Name must be a character string.')
       }
     },
     get: function() {
         console.log('Return name');
         return this._name;
     }
     /* Analog for the other properties */
   }
 });
 // "Set new name"
 item.name = 'Nike Dunk Low Retro - Sold out';
 // "Return name"
 console.log(item.name);
 // Output:
 // "Nike Dunk Low Retro - Sold out"
                                

Setter and getter methods do not necessarily have to be defined for a property. It is also possible to specify only one setter method or only one getter method.


Data properties and access properties

Data properties are object properties that can be accessed without setters and getters. Access properties, on the other hand, do not contain any data themselves, but provide setter and getter methods that access or return the actual data. Access properties define functions that are called when the corresponding property is read and written. Data properties refer to properties that can be accessed directly.

Complete Code - Examples/Part_134/main.js...

 const item = {
   number: '',               // item number
   _name: '',
   /* Here the other properties */
   set name(newName) {
     if(typeof newName === 'string') {
       console.log('Set new name');
       this._name = newName;
     } else {
       throw new TypeError('Name must be a character string.')
     }
   },
   get name() {
     console.log('Return name');
     return this._name;
   }
   /* Analog for the other properties */
 }

 // Data property
 item.number = '246813579';
 console.log(item.number);

 // Possible, but not desired, as access should be via set and get.
 item._name = 'Nike Dunk Low Retro';
 console.log(item._name);

 // Access property 
 item.name = 'Nike Dunk Low Retro - Sold out';
 console.log(item.name);
                                

Here, the number property is a data property, while the name property is an access property. Read and write access is the same in both cases.


8. Add or overwrite object properties and object methods

JavaScript is a dynamic programming language, so new properties and methods can be added to objects in JavaScript at any time (this is not possible in Java).


Create object properties and object methods using dot notation

Adding a new property or a new method to an object works in a similar way to the initialization of variables, except that the name of the object is written on the left-hand side of the assignment, followed by the member operator (the designation for the dot in dot notation, as properties and methods of an object are also referred to as member).

  • Add new methods:
Complete Code - Examples/Part_135/main.js...

 item.brand = 'Nike Dunk Low Retro';
 item.order = function() {
   console.log('The item was ordered successfully.');
 }
                            

  • Access to subsequently defined properties and methods:
Complete Code - Examples/Part_136/main.js...

 item.brand = 'Nike Dunk Low Retro';
 item.order = function() {
   console.log('The item was ordered successfully.');
 }

 console.log(item.brand);        // output: Nike Dunk Low Retro
 item.order();                   // output: The item was ordered successfully.
                            

  • Overwriting properties and methods
Complete Code - Examples/Part_137/main.js...

 item.brand = 'Nike Dunk Low Retro';
 item.order = function() {
   console.log('The item was ordered successfully.');
 }
 console.log(item.brand);        // output: Nike Dunk Low Retro
 item.order();                   // output: The item was ordered successfully.

 // Override the property
 item.brand = 'Nike Air Force 1';
 // Override the method
 item.order = function() {
   console.log(`The article ${this.brand} was ordered successfully.`);
 }
 console.log(item.brand);        // output: Nike Air Force 1
 item.order();                   // output: The item Nike Air Force 1 was successfully ordered.
                            

In principle, it does not matter whether all properties and methods are specified directly when an object is created using the literal notation or whether they are added individually afterwards using the dot notation.


    Create objects and define properties and methods
Complete Code - Examples/Part_138/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: '9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }

 const item2 = {};
 item2.name = 'Nike Dunk Low Retro';
 item2.price = 119.99;
 item2.author = 'Shoes';
 item2.isbn = '9';
 item2.printDescription = function() {
   console.log(`${this.product}: ${this.name}`);
 }
                            

However, it should be noted that in literal notation, the name and value of the properties/methods are separated by a colon and the individual properties/methods are separated by commas. In dot notation, on the other hand, the equals sign and a semicolon are used to close the statement, as these are individual statements.


Create object properties and object methods using bracket notation

To create new properties and methods of an object, the name of the respective property/method is written in square brackets on the left-hand side of the respective assignment after the object name, and the value to be assigned is then written on the right-hand side.

Complete Code - Examples/Part_139/main.js...

 const item = {};
 item['name'] = 'Nike Dunk Low Retro';
 item['price'] = 119.99;
 item['product'] = 'Shoes';
 item['size'] = '9';
 item['printDescription'] = function() {
   console.log(`${this.product}: ${this.name}`);
 }
                            

Create object properties and object methods via helper methods

Since ES5, JavaScript has provided two helper methods for defining object properties and object methods. A single new property or method can be defined using Object.defineProberty(), and several can be defined using Object.defineProperties().

The first argument passed to the Object.defineProberty() method is the object for which the new property/method is to be added. The second argument is the name of the respective property/method. A configuration object for defining the property attributes is passed as the third argument.

Complete Code - Examples/Part_140/main.js...

 const item = {};
 Object.defineProperty(item, 'name', {
   value: 'Nike Dunk Low Retro'
 });
 Object.defineProperty(item, 'price', {
   value: 119.99  
 });
 Object.defineProperty(item, 'product', {
   value: 'Shoes'
 });
 Object.defineProperty(item, 'size', {
   value: '9' 
 });
 Object.defineProperty(item, 'printDescription', {
   value: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 });
 console.log(item.name);     // "Nike Dunk Low Retro"
 console.log(item.price);    // 119.99
 console.log(item.product);  // "Shoes"
 console.log(item.size);     // "9"
                            

The Object.defineProperties() method works in a similar way. The object that is to be assigned the new properties/methods is also passed to it as the first parameter. The second parameter is a configuration object. The property names of this configuration object represent the names of the property/method to be created, the configuration object stored in each case defines the property attributes:

Complete Code - Examples/Part_141/main.js...

 const item = {};
 Object.defineProperties(item, {
   name: {
     value: 'Nike Dunk Low Retro'
   },
   price: {
     value: 119.99
   },
   product: {
     value: 'Shoes'
   },
   size: {
     value: '9'
   },
   printDescription: {
     value: function() {
       console.log(`${this.product}: ${this.name}`);
     }
   }
 });
 console.log(item.name);     // "Nike Dunk Low Retro"
 console.log(item.price);    // 119.99
 console.log(item.product);  // "Shoes"
 console.log(item.size);     // "9"
                            

9. Delete object properties and object methods

Apart from dynamically adding object properties, it is also possible to dynamically remove object properties from an object. This is done using the delete operator, to which the object properties to be removed are passed as operands.

Complete Code - Examples/Part_142/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: ' 9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }
 console.log('price' in item);   // output: true
 console.log(item.price);        // output: 119.99
 delete item.price;              // delete the property
 console.log('price' in item);   // output: false
 console.log(item.price);        // output: undefined
                        

Here, the object item is defined with four properties and one method. The in operator is then used to check whether the item object has the price property 'price' in item. The delete item.price instruction ensures that the price property is deleted from the item object. The in operator then returns a false accordingly.

The in operator returns a true if the property that was passed as the first operand exists in the object passed as the second operand.


delete operator vs null and undefined

Using the delete operator is not the same as assigning the null value or the undefined value to an object property, this merely ensures that the property is assigned the value null or undefined. This means that assigning the values null or undefined to an object property does not delete the property from the object.

Complete Code - Examples/Part_143/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: ' 9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }

 console.log('price' in item);   // output: true
 console.log(item.price);        // output: 119.99
 item.price = null;              
 console.log('price' in item);   // output: true
 console.log(item.price);        // output: null
 item.price = undefined;         
 console.log('price' in item);   // output: true
 console.log(item.price);        // output: undefined
                            

10. Output object properties and object methods

There are various ways to output all the properties and methods of an object:

  • the so-called for-in loop
  • the helper methods Object.keys(), Object.values() and Object.entries()

for-in loop


Complete Code - Examples/Part_144/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: ' 9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }
 for(let property in item) {
   console.log(`Name: ${property}`);
   console.log(`Value: ${item[property]}`);
 }
                            

In each iteration of the for-in loop, the variable property is assigned the corresponding name of the property or method of the object item. Using the parenthesis notation, it is then possible, for example, to access the respective values of the properties via the variable.


Helper method Object.keys()

The for-in loop offers a simple way to iterate over all enumerable properties and methods of an object. Alternatively, since ES5 there is the method Object.keys(), which returns the names of all enumerable properties and methods for an object as an array. This method can be used instead of the for-in loop whenever the names of the properties and methods of an object are required in list form, for example to pass them to a function or to further process the array using array methods.

Complete Code - Examples/Part_145/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: '9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }
 const properties = Object.keys(item);
 for(let i=0; i<properties.length; i++) {
   const property = properties[i];
   console.log(`Name: ${property}`);
   console.log(`Value: ${item[property]}`);
 }
 printArray(properties);
 function printArray(array) {
   for(let i=0; i<array.length; i++) {
     console.log(array[i]);
   }
 }
                            

Here, the result of the call to Object.keys() is assigned to the array variable properties, which in turn is passed as an argument to the printArray() function. Within this function, a normal for loop is then used to iterate over the names in this array and each name is output.


Helper method Object.values() and Object.entries()

Since ES2017, there are also the methods Object.values() and Object.entries(). Object.values() returns the values of all enumerable properties and methods as an array. Object.entries() returns the name-value pairs of all enumerable properties and methods.

Complete Code - Examples/Part_146/main.js...

 const item = {
   name: 'Nike Dunk Low Retro',
   price: 119.99,
   product: 'Shoes',
   size: '9',
   printDescription: function() {
     console.log(`${this.product}: ${this.name}`);
   }
 }

 const keys = Object.keys(item);
 console.log(keys);
 // [
 // 'name',
 // 'price',
 // 'product',
 // 'size',
 // 'printDescription'
 // ]
 const values = Object.values(item);
 console.log(values);
 // [
 // 'Nike Dunk Low Retro',
 // 119.99,
 // 'Shoes',
 // '9',
 // [Function: printDescription]
 // ]
 const entries = Object.entries(item);
 console.log(entries);
 // [
 // [ 'name', 'Nike Dunk Low Retro' ],
 // [ 'price', 119.99 ],
 // [ 'product', 'Shoes' ],
 // [ 'size', '9' ],
 // [ 'printDescription', [Function: printDescription] ]
 // ]
                            

If the property names and method names of an object or their values are required as an array, the methods Object.keys(), Object.values() and Object.entries() should be used. If, on the other hand, the names only need to be iterated over once, the for-in loop should be used.


11. Use symbols to define unique object properties

Symbols can be used to define unique names for object properties. Object properties that are defined using a symbol can then only be read by specifying this symbol.

Complete Code - Examples/Part_147/main.js...

 const firstName = Symbol('firstName');
 const lastName = Symbol('lastName');
 const person = {};
 person[firstName] = 'Rick';
 person[lastName] = 'Sample';
 console.log(person[firstName]);     // "Rick"
 console.log(person[lastName]);      // "Sample"
 console.log(person[0]);             // undefined
 console.log(person[1]);             // undefined
 console.log(person.firstName);      // undefined
 console.log(person.lastName);       // undefined
 console.log(person['firstName']);   // undefined
 console.log(person['lastName']);    // undefined
                        

Here, the two symbols firstName and lastName are used as properties for the object person. The values of the properties can then only be accessed using these symbols.

By way of comparison, the next example shows that the behaviour is different when using character strings as properties:

Complete Code - Examples/Part_148/main.js...

 const firstName = 'firstName';
 const lastName = 'lastName';
 const person = {};
 person[firstName] = 'Rick';
 person[lastName] = 'Sample';
 console.log(person[firstName]);     // "Rick"
 console.log(person[lastName]);      // "Sample"
 console.log(person[0]);             // undefined
 console.log(person[1]);             // undefined
 console.log(person.firstName);      // "Rick"
 console.log(person.lastName);       // "Sample"
 console.log(person['firstName']);   // "Rick"
 console.log(person['lastName']);    // "Sample"
                        

Here, although not via the index, it can be accessed via the name of the property.


12. Prevent changes to objects

In some cases, it can be useful to protect objects from changes, i.e. to prevent new properties or methods from being added to an object. JavaScript offers three different options for this:

  • Prevent extensions to objects
  • "Seal" objects
  • "Freeze" objects

Prevent extensions to objects

One way to protect objects from changes is to use the Object.preventExtensions() method. If an object is passed to this method, no new extensions can be made to the object. This means that no new properties and methods can be added. If you try to do so anyway, this will result in an error. However, values of existing properties and methods can be changed.

Complete Code - Examples/Part_149/main.js...

 const rick = {
   firstName: 'Rick',
   lastName: 'Sample'
 }
 console.log(Object.isExtensible(rick));         // true
 rick.age = 62;                                  // Define new property
 console.log(rick.age);                          // 62
 Object.preventExtensions(rick);                 // Prevent extensions
 console.log(Object.isExtensible(rick));         // false
 rick.firstName = 'Morty';                       // Allowed: change existing property
 console.log(rick.firstName);                    // "Morty"
 console.log(Object.getOwnPropertyDescriptor(rick, 'firstName').enumerable); // true
 Object.defineProperty(rick, 'firstName', {      // Allowed: Change property attributes
   enumerable: false
 });
 console.log(Object.getOwnPropertyDescriptor(rick, 'firstName').enumerable); // false
 rick.weight = 76;                               // TypeError: Can't add property weight,
 // object is not extensible     
                            

Here, a new value is assigned to the firstName property after the Object.preventExtensions() method is called. This instruction is permitted and therefore does not generate an error. Changing property attributes (enumerable of the firstName property) is also permitted.

The Object.isExtensible() method can be used to check whether an object is extensible or not. This method expects the object for which the corresponding test is to be performed as a parameter.


"Seal" objects

Another option for preventing changes to an object is the Object.seal() method, which can be used to seal objects. Sealed objects cannot be extended, nor can the existing properties be configured.

Complete Code - Examples/Part_150/main.js...

 const rick = {
   firstName: 'Rick',
   lastName: 'Sample'
 }
 console.log(Object.isExtensible(rick));     // true
 console.log(Object.isSealed(rick));         // false
 rick.age = 62;                              // Define new property
 console.log(rick.age);                      // 62
 Object.seal(rick);                          // Seal object
 console.log(Object.isExtensible(rick));     // false
 console.log(Object.isSealed(rick));         // true
 rick.firstName = 'Morty';                   // Allowed: change existing property
 console.log(rick.firstName);                // "Morty"
 console.log(Object.getOwnPropertyDescriptor(rick, 'firstName').enumerable); // true
 Object.defineProperty(rick, 'firstName', {  // Uncaught TypeError: Cannot redefine
 // property: firstName
   enumerable: false
 });
                            

The rick object is sealed here by calling the Object.seal() method. The firstname property of the rick object can then no longer be configured.


"Freeze" objects

Another option is the Object.freeze() method. This "freezes" objects, i.e. like Object.preventExtensions() it ensures that objects cannot be extended by new properties and methods and, like the Object.seal() method, it ensures that property attributes cannot be changed. In addition, other values of existing properties and methods cannot be changed. You can use Object.isFrozen() to determine whether an object is "frozen" or not.

Complete Code - Examples/Part_151/main.js...

 const rick = {
   firstName: 'Rick',
   lastName: 'Sample'
 }
 console.log(Object.isExtensible(rick));     // true
 console.log(Object.isSealed(rick));         // false
 console.log(Object.isFrozen(rick));         // false
 rick.age = 62;                              // New property
 console.log(rick.age);                      // 62
 Object.freeze(rick);                        // Freeze object
 console.log(Object.isExtensible(rick));     // false
 console.log(Object.isSealed(rick));         // true
 console.log(Object.isFrozen(rick));         // true
 rick.firstName = 'Morty';                   // TypeError: Cannot assign to read only
                                             // property 'firstName' of #<Object>
                            

Here, the rick object is frozen by calling the Object.freeze() method, which means that it is no longer possible to assign a new value to the firstName property. Calling Object.isExtensible() returns the value false for the frozen object, calling Object.isSealed() returns the value true, because frozen objects are always sealed and cannot be extended.


Comparison of the methods

Object.preventExtensions() ensures that no new properties can be added to the respective object. Furthermore, Object.seal() ensures that additional existing properties cannot be configured. And Object.freeze() also ensures that the values of existing properties cannot be changed.


Related links: