JavaScript Course 4: Objects
References
- Code with Mosh: The Ultimate JavaScript Mastery Series – Part 1
- Geeks for Geeks: Creating objects in JavaScript (4 Different Ways)
- MDM Web Docs: JavaScript > JavaScript reference > Classes
- W3School: JavaScript Objects
- W3School: JavaScript Classes
- W3School: JavaScript Object Accessors – Getters and Setters
See also:
Basics
Object oriented programming (OOP) is a style of programming where we see a program as a collection of Objects, that talk to each other to perform some functionality. The Objects has:
- Properties – primitives defined by
"keys":
and"values",
- Methods – internal
functions()
that operates with the properties of the object.
We can access the Properties of an Object via:
- Dot notation:
object.proprtyName;
- Bracket notation:
object['proprtyName'];
Using Dot notation is cleaner and easier, but the Bracket notation could be used when we want to pass variable in the place of the key – i.e. when when using a loop or so.
We can access the Methods of an Object via:
- Dot notation (only):
object.methodName();
object.methodName = function() {…};
In JavaScript Objects are dynamic, so we can add additional properties and methods to them, despite of they are defined by keywords var
, let
or const
. When we define an Object by const
that means the type of the variable that holds the object is const
and we can't change that type, but we can modify the created Object itself – changing the properties and methods of the Object .
In order to delete a member (property or method) of an Object we can use the delete operator in the following way.
delete circle.name;
Ways to create an Object in JavaScript
With JavaScript, you can define and create your own objects. There are different ways to create new objects:
- Create a single object, using an Object literal.
- Create a single object, with the keyword
new
. - Define Object Factory function, and then generate a new object by this function.
- Define an Object Constructor function, and then create objects of the constructed type.
- Create an object using
Object.create()
. - ES6 Classes.
Literal syntax of Object definition
const circle = {
"name": "Circle 1", // Property of type String
"radius": 1, // Property of type Number
"location": { // Property of type Object
"x": 1,
"y": 1
},
"isVisible": true, // Property of type Boolean
"draw": function() { // Method: a property of type Function
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
console.log(circle);
{name: 'Circle 1', radius: 1, location: {…}, isVisible: true, draw: ƒ}
Access Properties and Methods
Dot notation
Call and assign a Property with Dot notation.
console.log(circle.name);
Circle 1
circle.isVisible = false;
console.log(circle.isVisible);
false
Bracket notation
Call and assign a Property with Bracket notation.
console.log(circle['location']);
{x: 1, y: 1}
circle.location['x'] = 2;
circle.location.y = 3;
console.log(circle.location.x, circle.location['y']);
2 3
Methods and Dot notation
When a Function is a part of an Object in the terms of OOP we calling this Function a Method. Methods could be called and defined only with Dot notation.
circle.draw();
Draw a circle with: 2 3 1
circle.radius = 2;
circle.area = function() {
return this.radius * this.radius * 3.14;
};
console.log(circle.area());
12.56
Factory Functions for Objects definition
Just like a factory produce products, the Factory functions produce Objects. Here is how to transform the Object literal definition into a factory function.
The naming convention for the Factory functions is: kamelNotation
Step 1, move the object definition inside :the Factory function and return it as output of that function:
function createCircle() {
const circle = {
"name": "Circle 1", // Property of type String
"radius": 1, // Property of type Number
"location": { // Property of type Object
"x": 1,
"y": 1
},
"isVisible": true, // Property of type Boolean
"draw": function() { // Method: a property of type Function
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
return circle;
}
Step 2, simplify the above and just return the object definition.
function createCircle() {
return {
"name": "Circle 1", // Property of type String
"radius": 1, // Property of type Number
"location": { // Property of type Object
"x": 1,
"y": 1
},
"isVisible": true, // Property of type Boolean
"draw": function() { // Method: a property of type Function
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
}
Step 3, convert hard codded values as parameters, which values will be supply as arguments when we calling the Factory function.
function createCircle(name, radius, location, isVisible) {
return {
"name": name,
"radius": radius,
"location": location,
"isVisible": visible,
"draw": function() {
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
}
Step 4, in modern JS if the key and the value are the same we can make our code shorter, and removing the value and simply adding the key.
function createCircle(name, radius, location, isVisible) {
return {
name,
radius,
location,
visible,
"draw": function() {
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
}
Step 5, simplify the Methods definitions, like we define a function outside of an object, but we can dropping the function keyword.
function createCircle(name, radius, location, isVisible) {
return {
name,
radius,
location,
visible,
draw() {
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
};
}
Use the Factory function.
const circle1 = createCircle("Circle 1", 2, {x: 1, y: 2}, true);
console.log(circle1);
{name: 'Circle 1', radius: 2, location: {…}, visible: true, draw: ƒ}
const circle2 = createCircle("Circle 2", 4, {x: 2, y: 2}, true);
console.log(circle2);
{name: 'Circle 2', radius: 4, location: {…}, visible: true, draw: ƒ}
Constructor Functions for Objects definition
The purpose of the Constructor functions is the same as the Factory functions – they produce Objects, it's just another approach.
The naming convention for the Factory functions is: PascalNotation
function Circle(name, radius, location, isVisible) {
this.name = name;
this.radius = radius;
this.location = location;
this.isVisible = isVisible;
this.draw = function() {
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
};
}
Use the Constructor function.
const circle3 = new Circle("Circle 3", 6, {x: 2, y: 4}, true);
console.log(circle3);
Circle {name: 'Circle 3', radius: 6, location: {…}, isVisible: true, draw: ƒ}
circle3.draw();
Draw a circle with: 2 4 6
When we use the new
operator three things happen:
- The
new
operator creates an empty JavaScript Object, something like this:constant x = {};
- Then
new
setthis
of the Constructor function to point to the new empty Object. - Finally
new
returns the new Object from the constructor function, but we don't need explicitly add areturn
statement.
Classes for Objects definition
The purpose of the Classes is the same as the Factory and Constructor functions – they produce Objects. It's just another modern approach defined in ES6 (ES2015).
Classes are a template for creating objects. They encapsulate data with code to work on that data. Classes in JS are built on prototypes but also have some syntax and semantics that are not shared with ES5 class-like semantics.
Classes are in fact "special functions", and just as you can define function expressions and function declarations, the class syntax has two components: class expressions and class declarations.
An important difference between function declarations and class declarations is that while functions can be called in code that appears before they are defined, classes must be defined before they can be constructed. Code like the following will throw a ReferenceError
:
The naming convention for the Classes (as the Factory functions) is: PascalNotation
class Circle {
constructor(name, radius, location, isVisible) {
this.name = name;
this.radius = radius;
this.location = location;
this.isVisible = isVisible;
}
draw() {
console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
}
}
Use the Constructor function. The usage is the same as the Constructor functions by using the new
operator.
const circle4 = new Circle("Circle 4", 8, {x: 6, y: 6}, false);
console.log(circle4);
Circle {name: 'Circle 4', radius: 8, location: {…}, isVisible: false, draw: ƒ}
Constructor Property
Every Object has a constructor property that show the function that we use to create the object.
let x = {};
console.log(x.constructor);
ƒ Object() {[native code]}
let x = new String('ABC');
console.log(x.constructor);
ƒ String() {[native code]}
let x = 'ABC';
console.log(x.constructor);
ƒ String() {[native code]}
Functions and Arrays are Objects
In JavaScript the Functions (and Arrays) are also Objects. There is a built-in constructor function called Function that creates the objects of the functions. So the Functions also have methods and properties.
function Circle(radius) {
this.radius = radius;
this.draw = function() {
console.log("Draw circle radius:", this.radius);
}
}
console.log(Circle.name);
'Circle'
console.log(Circle.length); // will return the number of arguments
1
console.log(Circle.constructor); // will return the constructor function
ƒ Function() {[native code]}
See also Call and Apply methods:
- W3School: JavaScript Function Call(), that will call a function with specific context and will pass custom arguments to it.
- W3School: JavaScript Function Apply(), that will call a function with specific context and will pass an array of custom arguments to it.
Value vs Reference types
The Value types, also called Primitive types – Strings, Numbers, Booleans, undefined, null, Symbol – holds the value inside the variable of the primitive. In other hand the Reference types – Objects, Arrays, Functions – holds a reverence to an internal variable(s) where the actual value is stored.
Primitives are copied by their value. Objects (reference types) are copied by their reference.
1.A. Example with primitives – when we copy the value of a Primitive type we create an independent variable:
let x = 10;
let y = x;
x = 20;
console.log(x, y); // We can see 'x' and 'y' are two indepentant variables
20 10
1.B. Example with references – when we copy the value or Reference type we copy the reference, not the value:
let x = {value: 10};
let y = x;
x.value = 20;
console.log(x);
{value: 20}
console.log(y);
{value: 20}
2.A. Another example with Primitive types:
let number = 10;
function increase(num) {
num++;
}
increase(number);
console.log(number);
10
When we pass the variable number
as an argument of the function increase(number)
, there is created a local variable (inside the function) that is completely different of the global variable number
, so the value of the global variable is not changed.
2.B. Another example with Reference types:
let object = {value: 10};
function increase(obj) {
obj.value++;
}
increase(object);
console.log(object);
{value: 11}
In this example wen we pass the object
as argument to the function it is passed as its reference. So we have two variables object
and obj
that pointing to the same (internal JS) object.
Enumerating Properties of an Object
const circle = {
"radius": 1,
draw() {console.log("Draw...");}
};
for (let key in circle)
console.log(`${key} : ${circle[key]}`);
radius : 1
draw : draw() {console.log("Draw...");}
// Object.keys(circle) returns an Array of keys of the circle 'object', which array is iterative
for (let key of Object.keys(circle))
console.log(`${key} : ${circle[key]}`);
radius : 1
draw : draw() {console.log("Draw...");}
// Object.keys(circle) returns an Array of keys of the circle 'object', which array is iterative
for (let entry of Object.entries(circle))
console.log(entry);
(2) ['radius', 1]
(2) ['draw', ƒ]
If given property or method exists example:
if ('radius' in circle) console.log('YES');
YES
Clone an Object
const circle = {
"radius": 1,
draw() {console.log("Draw...");}
};
The old approach – iterate over the keys of an exiting object and copy their values to the new object.
const another = {};
for (let key in circle)
another[key] = circle[key];
console.log(another);
{radius: 1, draw: ƒ}
The modern approach – use Object.assign()
which copies all properties and methods from one or more objects to a new object.
const another = Object.assign({}, circle);
console.log(another);
{radius: 1, draw: ƒ}
Clone an Object and add an additional property or method to the new object.
const another = Object.assign({"color": "yellow"}, circle);
console.log(another);
{color: 'yellow', radius: 1, draw: ƒ}
A simile and elegant way to clone an object – by using the Spread operator: ...
const another = {...circle};
console.log(another);
{radius: 1, draw: ƒ}