# Object Oriented Programming

OOP - The Trillion Dollar Disaster ?

  • Source
  • In Real World, You do not inherit “behaviors” from your parents, you develop your own behaviors. And you’re unable to “override” your parents’ behaviors.
  • Fundamental error - Objects bind functions and data structures together in indivisible units.
  • Design Patterns - OOP Guidelines like SOLID principle, dependency injection, design patterns, and others are just bandaids created to fill shortcomings of OOP.
  • GoLang - Some modern programming languages avoid inheritance altogether.

Functional languages

  • Elixir, Elm, F#, scala, clojure
  • With the right guidance and linting, JavaScript can be a good functional language.

Types of Programming

JavaScript is multi-paradigms.

  1. Procedural
  2. OOP
  3. Functional
  • JavaScript OOP is not like Java's OOP. There are Differences.
  • JavaScript is Prototypical OOP & Classless

# OOP Principles

# Encapsulation

  • Binding data & methods (acting on data) together into single entity.
  • Eg: A Car has data(petrol, kms) & method(mileage) which will use data to calculate.
  • Total encapsulation - Make date private. We can use closures or get/set.
// private or not is a different issue. -
let obj = {
  x: 10,
  y: 20,
  add: () => {
    return this.x + this.y;
  },
};

# Abstraction

  • Concept
    • Hides the implementation details from the user.
    • We cannot create any instance. It's child must do it.
    • Abstract methods - methods without body.
    • But javascript OOP is not java OOP.

# Inheritance

# # Object Literal (not a convention. Use constructors.)

let parent = {x: 10};
let child = Object.create(parent, {y: 20});

# # Constructors

  1. Constructor -> Instance
obj = new Parent();
  1. Constructor -> Constructor -> Instance - (Extend)
function Parent(x) {
  this.x = x;
}
Parent.prototype.sayHi = () => 'Hi';

function Child(x, y) {
  Parent.call(this, x); // Run & Inherit
  this.y = y;
}

// Child.prototype = Parent.prototype; // wrong - Overriding is not possible
Child.prototype = Object.create(Parent.prototype); // new object { constructor: Parent(), __proto__: Parent.prototype }
Child.prototype.constructor = Child; // Override - { constructor: Child(), __proto__: Parent.prototype }

let o = new Child(10, 20);

c(o instanceof Child); // true
c(o instanceof Parent); // true

c(Object.getPrototypeOf(o)); // Parent

# Using Class

class Parent {
  constructor(x) {
    this.x = x;
  }
  sayHi() {
    return 'hi';
  }
}

class Child extends Parent {
  constructor(x, y) {
    super(x);
    this.y = y;
  }
}
let obj = new Child(3, 4);

# Polymorphism

MDN - "The fancy word for the ability of multiple object types to implement the same functionality is polymorphism."

JavaScript is a dynamic language with duck typing. Polymorphism is not an issue. (opens new window)

let student = {
  greet: function() {
    return 'Hello';
  },
};

let teacher = {
  greet: function() {
    return 'Good Morning';
  },
};

c(student.greet());
c(teacher.greet());

Concept

  • Polymorphism - Objects with Same name methods but different logic
    1. Dynamic (Overriding)(runtime) - Subclass method overrides superclass method
    2. Static (Overloading)(compiletime) - A class with n methods with same name but different arguments

Polymorphism allows us to change program behavior at runtime.

// Overriding
let person = {
  prototype: {},
};
person.prototype.greet = () => 'Hello.';

let student = {};
student.prototype = Object.create(person.prototype);
student.prototype.greet = () => 'Hello student.';

let teacher = {};
teacher.prototype = Object.create(person.prototype);
teacher.prototype.greet = () => 'Good morning.';

c(student.greet());
c(teacher.greet());

# Instanceof

obj instanceof Constructor

  • Will get Constructor.prototype
  • Then check the prototype chain of obj for ref to Constructor.prototype
  • Return true/false (if found anywhere in the chain)
let a = [2, 3];

c(a instanceof Array); // true
c(a instanceof Object); // true

# Creating Objects

# Object

let obj = new Object();

# Object Literals

let obj = {
  x: 3,
  y: 4,
  add: () => {
    return this.x + this.y;
  },
};

# Factory Function

function Foo(x, y) {
  return {
    x,
    y,
    add: function() {
      return this.x + this.y;
    },
  };
}

let o = Foo(2, 3); // just call don't use 'new'

# Constructor

// first letter is capital
function Foo(x, y) {
  this.x = x;
  this.y = y;
  this.add = () => {
    return this.x + this.y;
  };
}
let obj = new Foo(2, 3);

# Classes

class Foo {
  constructor(x) {
    this.x = x;
  }
  num() {
    return this.x;
  }
}
let obj = new Foo(5);

# Passed by reference

TIP

Objects are Mutable because they are always addressed by reference.

Example

// passed by value - number/string/boolean/symbol/undefined/null
// passed by reference - objects (array,functions,etc)
let x = {n: 10};
let y = x;
x.n = 20;

console.log(x, y); // {n:20} {n:20}

# Copy Object

  1. Reference Copy
  2. Shallow Copy - (Top level properties are copied. For Nested objects, ref is copied)
    • Spread operator
    • Assign - Object.assign ({}, obj)
  3. Deep Copy
    • Lodash
    • Json - JSON.parse(JSON.stringify(obj)) (undefined, symbols, methods are skipped)

Note : Methods can't be copied reliably in javascript. So share them.

let foo = {
  x: 3,
  y: {
    z: 4,
  },
};

// shallow copy
let bar = {
  a: 10,
  ...foo,
};
c(bar); // {a: 10, x: 3, y: {…}}
c(foo.y === bar.y); // true - ref to same object

// deep copy
let deep = JSON.parse(JSON.stringify(foo));
c(foo.y === deep.y); // false

# This

  • Based on location where it is used
c(this); // this = window object

function foo() {
  c(this); // owner of function ie 'Window'
}

In method

let p = {
  foo: 23,
  bar: function() {
    c(this); // this = owner of function = p object
  },
  barArrow: () => {
    c(this); // this = window object
  },
  barMix: function() {
    let arrow = () => {
      c(this); // lexical scoping - takes this from outer context = p object
    };
    return arrow();
  },
};

p.bar(); // p object
p.barArrow(); // window object
p.barMix(); // p object

In Event handlers

<!-- this = [object HTMLButtonElement] -->
<button onclick="alert(this);">
  click
</button>

# bind()

  • We use this bind() to bind this to the funtion context.
let foo = function() {
  c(this);
};
let myThis = {
  x: 10,
};

let newFoo = foo.bind(myThis);

newFoo(); // myThis
foo(); // window

# Property

  • Types of Property
    1. Data property - has value
    2. Accessor property - has no value but get/set
// Both enumerable & non-enumerable props
Object.getOwnPropertyNames(obj);

// Enumerable props
for (let x in obj);

Object.keys(obj); // all keys
Object.values(obj); // all values
  • Property Attributes
    • Data & Accessor property has some Attributes used internally by JSEngine. Eg: [[Prototype]]
    • Data property - [[Value]], [[Enumerable]], [[Writable]], [[Configurable]]
    • Accessor property - [[Get]], [[Set]], [[Enumerable]], [[Configurable]]
// get flags
Object.getOwnPropertyDescriptor(obj, propName); // {value: , writable: true, enumerable: true, configurable: true, }

// change flags
Object.defineProperty(obj, propName, {value: 'foo', enumerable: false}); // If flags are absent then all flags = false.

# Add, Delete property

// add
obj.newProp = 'value';
obj['new-prop'] = 'value';

// dynamic key
let obj = {
  [someVariable]: 'umesh',
};
obj[someVariable];

// delete
delete obj.newProp;

# Property - Get/Set

  • Accessor property
    • Has no value but get/set methods.
    • This methods are accessed just like a property without parenthesis ()
    • set() allows only one parameter.
  • Property Attributes - [[Get]], [[Set]], [[Enumerable]], [[Configurable]]

# Using Object Literal

  • Two ways
    1. Using keywords get/set inside literal object.
    2. Using Object.defineProperty outside literal object
// first, last = data property
// user = accessor property
var obj = {
  first: 'umesh',
  last: 'kadam',
  get user() {
    return `${this.first} ${this.last}`;
  },
  set user(name) {
    let [first, last] = name.split(' '); // array destructuring
    this.first = first;
    this.last = last;
  },
};

// Object.defineProperty can be used here

obj.user = 'Foo Bar'; // set
console.log(obj.user); // get

# Using Constructor - Property

  • One way
    1. Not possible - Using keywords get/set inside Constructor.
    2. Using Object.defineProperty inside/outside Constructor by passing context this/obj
function Foo(name) {
  this.name = name;

  Object.defineProperty(this, 'user', {
    get: () => {
      return this.name;
    },
    set: (name) => {
      if (name.length > 3) {
        this.name = name;
      }
    },
  });
}

let obj = new Foo('Harry');
obj.user = 'Potter'; // set
c(obj.user); // get - Potter

# Using Class

class Foo {
  constructor(name) {
    this.name = name;
  }

  get user() {
    return this.name;
  }
  set user(name) {
    this.name = name;
  }
}

let obj = new Foo('Umesh');
obj.user = 'Harry'; // set
c(obj.user); // get

# Using keywords

Not a get/set Syntax

This is not an example of get/set Syntax. It just uses keywords getXxxx() / setXxxx(). They are just simple methods (not accessor property).

Advantage - Arguments can be of any length & not restricted as in get/set

function Foo(name) {
  this.name = name;
  this.getName = () => {
    return this.name.toUpperCase();
  };
  this.setName = (name) => {
    this.name = name;
  };
}
let obj = new Foo('umesh');
obj.getName();
obj.setName('Harry');

# Private/Protected

  • Private - #propName (almost a js standard now. Visible only inside class.) (No Inheritance for child class.)
  • Protected - _propName (a convention but not official. It is assecible from anywhere outside. But _ reminds us not to use outside class.)

# In Constructor

function Foo() {
  let x = 3; // private member (not property)
  this.num = () => x;
}

let o = new Foo();

c(o.num()); // 3
o.x = 55; // is creating property and not private member

# In Class

class Foo {
  //  let x = 3; // error can't use let/const inside class
  x = 3; // public
  _y = 5; // public (protected as convention)
  #z = 4; // private

  num() {
    return this.#priNum();
  }

  #priNum() {
    return this.x + this._y + this.#z;
  }
}

let o = new Foo();
c(o); // Foo {x: 3, _y: 5, #priNum: ƒ, #z: 4}

c(o.x); // 3
c(o._y); // 5
c(o.z); // undefined
// c(o.#z); // error

c(o.num()); // 12

# Method Chaining

class User {
  constructor(name) {
    this.name = name;
  }
  one() {
    console.log('one');
    return this;
  }
  two() {
    console.log('two');
    return this;
  }
}

let user = new User('umesh');
user.one().two();

# Static methods

  • They are just utility/helper methods relevant to class
  • They can be accessed using classname only and not class instances/objects
  • this keyword is never used inside static methods
  • eg: Object.keys(), Object.values(), etc
class Foo {
  x = 3;
  static num() {
    return this.x;
  }
  static square(x) {
    return x ** 2;
  }
}

c(Foo.x); // undefined
c(Foo.num()); // undefined - (we can't use "this" in static)
c(Foo.square(10)); // 100

# Mixins

  • It's like adding extra props/methods to prototype of any class.
  • Js does not allow multiple Inheritance. But then mixin can be a great alternative.
let mixin = {
  msg: "I'm form Mixin",
};

class Person {}
class Teacher extends Person {}

let obj = new Teacher();

console.log(Teacher.prototype); // Person {...}

Object.assign(Teacher.prototype, mixin); // add mixin to teacher
console.log(Teacher.prototype); // Person {msg: "...." , ...}
Last Updated: 12/24/2021, 9:56:33 AM