OOP in JavaScript

OOP in a nutshell

OOP in a nutshell

Overview

The basic idea of OOP is that we use objects to model real world things that we want to represent inside our programs.
Objects can contain related data and code, which represent information about the thing you are trying to model, and functionality or behavior that you want it to have.
Reference: Object-oriented JavaScript for beginners @MDN

Example structure of drinks ERP system

OOP Pros

Real-World Modelling
Support and Maintenance
Well structured code

The typical use case of OOP is in big software systems on which works multiple developers.

OOP Cons

Overwhelmed code (for small systems)
Jailed to development processes, "best practises", frameworks.

The notion of class and instance in classic OOP

Class: a "blueprint" which describes objects and their behaviour:
images/classAsBlueprint
Object (instance of a Class): the real entities with which we work.
instancesOfClass

Class-based vs. Prototype-based OOP paradigms

Class-based paradigm
Objects are instanses of classes
Classes inherit from classes
Classes are immutable
Prototype-based paradigm
Objects are instanses of objects
Objects inherit from objects
Objects are mutable

OOP in JavaScript

Java JavaScript
Classes Functions
Constructors Functions
Methods Functions

Tables excerpt from Douglas Crockford's "Classical Inheritance in JavaScript"

Object creation

Object creation

Two main ways of object creation:
Object creation ex nihilo ("from nothing")
Object creation by cloning an existing object

Object Literal (object initializer)

An object literal, also known as object initializer, is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces {}.

			var obj = {
				property1: value1,
				property2: value2,
				...
				propertyN: valueN
			};
		
The value of a property can by any JS data type: a primitive, function or an object

By object literals in JavaScript, we can create associative arrays/dictionaries data structures.

Object Literal example


			var apple = {
				color: "red",
				price: [23.5, 22, 25],

				calcMinPrice: function(){
					return  Math.min( ...this.price ); // from ES6
				},
				calcMaxPrice: function(){
					return  Math.max( ...this.price ); // from ES6
				}
			}

			console.log( "apple object", apple );
			console.log( "apple min price", apple.calcMinPrice() );
		

Object Literal: Pros&Cons

Pros
Simple and clear syntax for one instance
Cons
To much "copy-paste" syntax for multitude instances

The Factory Pattern

"Factory" - a function which creates object instances!


			let carFactory = function(model, year){
				var obj = {};

				obj.model = model;
				obj.year = year;

				return obj;
			}

			// create new car object:
			let car1 = carFactory('FORD Mustang', 1998);
		

Factory Pattern - simple example


			var objFactory = function(name){
				var obj = {};

				obj.name = name;
				obj.sayName = function(){
					console.log(`I'm object: ${obj.name}`)
				};

				return obj;
			}

			var obj1 = objFactory('Object 1');
			var obj2 = objFactory('Object 2');
			obj1.sayName();
			obj2.sayName();
			// I'm object: Object 1
			// I'm object: Object 2
		

The Factory as mechanism to enforce encapsulation

As we saw, the only way to "hide" a variable in JavaScript is by function scopes and closures
And Factory is just a function!

			"use strict";
			let objFactory = function(name){
				// "private members"
				let _name = name;

				let obj = {
					// public members:
					sayName: function(){
						console.log(`I'm object: ${_name}`)
					}
				};

				return obj;
			}

			let obj1 = objFactory('Object 1');
			obj1.sayName();

			// but obj1 name can not be accessed directly:
			console.log(`obj1.name: ${obj1.name}`);

			// *** OUTPUT ***
			// I'm object: Object 1
			// obj1.name: undefined
		

TASK: The Factory as mechanism to enforce encapsulation

Implement the devFactory factory function, which creates objects:


			// properties
			name: string,
			salary: number,

			// methods
			getSalary()
				returns salary
			increaseSalary(incrValue, pass)
				 increments salary with incrValue, if pass == 'abracadabra'
		

			"use strict";
			let devFactory = function(name, salary){
				// YOUR CODE HERE
			}

			let dev1 = devFactory('Peter', 1000);
			let dev2 = devFactory('Maria', 1200);

			console.log(`${dev1.name} salary is ${dev1.getSalary()}`);
			console.log(`${dev2.name} salary is ${dev2.getSalary()}`);

			dev1.increaseSalary(500, 'abracadabra');
			dev2.increaseSalary(100, '123');

			console.log(`${dev1.name} salary after promotion is ${dev1.getSalary()}`);
			console.log(`${dev2.name} salary after promotion is ${dev2.getSalary()}`);

			// *** OUTPUT ***
			// Peter salary is 1000
			// Maria salary is 1200

			// Wrong password! Maria salary will not be increased!

			// Peter salary after promotion is 1500
			// Maria salary after promotion is 1200
		

Factory Pattern: Pros&Cons

Pros
Great flexibility and implementation independence.
Can create objects as literal, by constructor, by Object.create()
Cons
Not familiar for "classical" OOP programmers

Constructor Function

Constructor function is a function which constructs objects. I.e. defines objects and their features
JavaScript emulates classes via constructor functions
Every function in JS can act as a Constructor function.
What make difference is the way you call the constructor function!
The naming convention for a constructor function is to start it with CapitalLetter - this convention is used to make constructor functions easier to recognize in code.

Constructor Function - example


			// the Constructor Function:
			let AppleConstructor = function( color, prices ){
				console.log("AppleConstructor is called!");
				this.color = color;
				this.prices = prices;

				this.calcMinPrice = function(){
					return  Math.min( ...this.prices ); // from ES6
				};
				this.calcMaxPrice = function(){
					return  Math.max( ...this.prices ); // from ES6
				};
			}

			// objects constructing:
			let apple1 = new AppleConstructor("red", [3.5, 2.05, 2.5]);
			let appleN = new AppleConstructor("green", [1.80, 2.10, 2.40]);

			// objects usage:
			console.log( "apple1 min price: ", apple1.calcMinPrice());
		

Constructor Function: Pros&Cons

Pros
Easy to create multiple instances.
Cons
new and this problems!

Factory Pattern with Constructors - flexibility example


			"use strict";
			// constructors:
			function AudioPlayer(name){
				this.name = name;
				this.play = function(){
					console.log(`${this.name} is playing as Audio!`);
				}
			}
			function VideoPlayer(name){
				this.name = name;
				this.play = function(){
					console.log(`${this.name} is playing as Video!`);
				}
			}

			// the factory - explicitly returns an object:
			function mediaPlayerMaker(name) {
				// if filename ends in 'mp3' or 'ogg' or 'flack' => return AudioPlayer object
				if(name.match(/\.(?:mp3|ogg|flack)$/i) ){
					return new AudioPlayer(name);
				}
				// if filename ends in 'mp4' or 'avi' or 'divx' => return VideoPlayer object
				if (name.match(/\.(?:mp4|avi|divx)$/i)) {
					return new VideoPlayer(name);
				}
			}

			// the instancies
			const player1 = mediaPlayerMaker('time_lapse.mp3');
			const player2 = mediaPlayerMaker('micahel_nyman_band_live.avi');

			// the usage
			player1.play();
			player2.play();

			// time_lapse.mp3 is playing as Audio!
			// micahel_nyman_band_live.avi is playing as Video!
		

Object properties

Object properties

Property names

Property names must be valid JavaScript string, or anything that can be converted to a string, including the empty string.
If they are not, JavaScript try to typecast them

Accessing properties - the dot notation


			get = object.property;
			object.property = set;
		
When using the dot notation the property name must be a valid identifier!

			let obj = {};

			obj.name = 'Ada';  // valid
			obj._id = 1;  	   // valid
			obj.$1 = "first";  // valid
			obj.1  = "first";  // invalid
		

Accessing properties - square brackets notation


			get = object[property_name];
			object[property_name] = set;
		
property_name is a string, or expression that evaluates to string!
property_name can be any string, not necessarily valid identifier.
The typical use case for square brackets notation is when you have the property name in a variable!

			let obj = {};
			let keyName = 2;

			obj["1"] = "first"; 		// valid
			obj[keyName]   = "second"; 	// valid

			console.log(obj)
			// Object {1: "first", 2: "second"}
		

Duplicate property names

An object can not have 2/more properties with a same name!
When 2/more properties with a same name are given, then the last of them will overwrite the others.

			let obj={
				prop1: 1,
				prop2: 2,
				prop1: 3,
				prop4: 4
			}
			console.log(obj);
			// Object {prop1: 3, prop2: 2, prop4: 4}
		

Computed property names (ES2015)

ES2015 spec allow an expression to be placed inside square brackets, which will be computed as the property name

			let i = 0;
			let obj = {
				[`key${++i}`] : i,
				[`key${++i}`] : i,
				[`key${++i}`] : i,
			};
			console.dir(obj);
			// Object
			// 	key1: 1
			// 	key2: 2
			// 	key3: 3

		

Shorthand property names (ES2015)


			let name = 'Ada', age = 21;

			let user = {name, age};

			console.log(user);
			// { name: 'Ada', age: 21 }

		

Shorthand method notation (ES2015)

We can omit the function keyword, when defining methods after ES2015
This shorthand notation is widely used, especially in React/Vue/Angular

			var obj = {
				id: 1,
				sayID(){
					console.log(this.id)
				},
			}
			obj.sayID();
			// 1
		

Useful Object operations and methods

Useful Object operations and methods

for...in statement

The for...in statement iterates over all enumerable properties of an object, including inherited enumerable properties.

				let user = {
					'name':'Ada',
					'age': 21,
					'address':{
						'town': 'Varna',
						'zip': 1234
					}
				}

				for (const key in user) {
					console.log(key);
				}

				// name
				// age
				// address
			
Note: do not confuse the for..in statement with for...of statement which is used to iterate over arrays, strings and other iterable objects

Object.keys()

The Object.keys() method returns an array of a given object's own enumerable property names.
Reference: Object.keys() @mdn

				let user = {
					'name':'Ada',
					'age': 21,
					'address':{
						'town': 'Varna',
						'zip': 1234
					}
				}

				const keys = Object.keys(user)
				console.log(keys);

				// [ 'name', 'age', 'address' ]
			

Object.values()

The Object.values() method returns an array of a given object's own enumerable property values.
Reference: Object.values() @mdn

				let user = {
					'name':'Ada',
					'age': 21,
					'address':{
						'town': 'Varna',
						'zip': 1234
					}
				}

				const keys = Object.values(user)

				// [ 'Ada', 21, { town: 'Varna', zip: 1234 } ]
			

Exercises

Exercises:

Test_your_skills:_Object_basics @mdn
JavaScript Object - Exercises, Practice, Solution

These slides are based on

customised version of

Hakimel's reveal.js

framework