A deep dive into JavaScript OOP

How to create an object?

let user = {
  credentials: [
    'ahmedosama',
    'ahmedosama@sectheater.io',
    '123456789',
  ],

  signout() {
    /* Some business logic */
  },

  updateProfile() {
    // Some business logic
  },
}

How to create an object?

let user = Object.create({})

user.credentials = []

user.credentials.push(
  'ahmedosama',
  'ahmedosama@sectheater.io',
  '123456789',
)

user.register = function () {}
user.login = function () {}
user.logout = function () {}

console.log(user)

How to create an object?

let user = {...}

function createUser(username, email, password) {
  let user = {
    credentials: [],
  }

  user.credentials.push(username, email, password)

  user.register = function () {}
  user.login = function () {}
  user.logout = function () {}

  return user
}

let user = createUser(
  'ahmedosama',
  'ahmedosama@sectheater.io',
  '123456789',
)

console.log(user)

Prototypes

Prototypes

let user = {
  credentials: [
    'ahmedosama',
    'ahmedosama@sectheater.io',
    '123456789',
  ],

  signout() {
    /* Some business logic */
  },

  updateProfile() {
    // Some business logic
  },
}

Prototypes

function createUser(username, email, password) {
  let user = Object.create(userFunctions)

  user.credentials = []

  user.credentials.push(username, email, password)

  return user
}

const userFunctions = {
  login() {},
  logout() {},
  register() {},
}

const user1 = createUser(
  'ahmedosama',
  'ahmedosama@sectheater.io',
  '123456789',
)

const user2 = createUser(
  'mohamedosama',
  'mohamedosama@sectheater.io',
  'creative_password',
)

New keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

let user = new UserFactory(
  'ahmedosama',
  'ahmedosama@sectheater.io',
  '123456789',
)

console.log(user)

Prototypes

function double(x) {
  return x * 2
}

double.y = 3

double(3) // 6

double.y // 3
double.prototype // {}

New keyword

function UserFactory(username, email, password) {...}

let user = new UserFactory(...)

new UserFactory()

Thread of execution

Local Memory

Return value

New keyword

function UserFactory(username, email, password) {...}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)

new UserFactory()

Thread of execution

Local Memory

Return value

username: 'ahmedosama'

email: 'ahmedosama@sectheater.io'

password: '123456789'

  • Create an empty object `this`

this: {}

New keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)
  • Create an empty object `this`
  • Invoke the function

new UserFactory()

Thread of execution

Local Memory

Return value

username: 'ahmedosama'

email: 'ahmedosama@sectheater.io'

password: '123456789'

this: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

}

Global Memory

UserFactory: => f() =>

                      +

{

     prototype: {} 

}

user:

New keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

UserFactory.prototype.register = function () {}
UserFactory.prototype.login = function () {}
UserFactory.prototype.logout = function () {}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)
  • Create an empty object `this`
  • Invoke the function
  • Link __proto__ to fn's prototype

new UserFactory()

Thread of execution

Local Memory

Return value

username: 'ahmedosama'

email: 'ahmedosama@sectheater.io'

password: '123456789'

this: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

    __proto__: UserFactory

}

Global Memory

UserFactory: => f() =>

                      +

{

     prototype: {

        register: => f() =>

        login: => f() =>

        logout: => f() =>

     }

}

user:

New keyword

  • Create an empty object `this`
  • Invoke the function
  • Link __proto__ to fn's prototype
  • Return `this` if another object isn't returned 

new UserFactory()

Thread of execution

Local Memory

Return value

username: 'ahmedosama'

email: 'ahmedosama@sectheater.io'

password: '123456789'

this: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

    __proto__: UserFactory

}

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

UserFactory.prototype.register = function () {}
UserFactory.prototype.login = function () {}
UserFactory.prototype.logout = function () {}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)

Global Memory

UserFactory: => f() =>

                      +

{

     prototype: {

        register: => f() =>

        login: => f() =>

        logout: => f() =>

     }

}

user: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

    __proto__: UserFactory

}

New keyword

  • Create an empty object `this`
  • Link __proto__ to fn's prototype
  • Return `this` if an another object isn't returned 

new UserFactory()

Thread of execution

Local Memory

Return value

username: 'ahmedosama'

email: 'ahmedosama@sectheater.io'

password: '123456789'

this: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

    __proto__: UserFactory

}

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

UserFactory.prototype.register = function () {}
UserFactory.prototype.login = function () {}
UserFactory.prototype.logout = function () {}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)

Global Memory

UserFactory: => f() =>

                      +

{

     prototype: {

        register: => f() =>

        login: => f() =>

        logout: => f() =>

     }

}

user: {

    username: 'ahmedosama',

    email: 'ahmedosama@sectheater.io',

    password: '123456789',

    __proto__: UserFactory

}

Object default proto

const obj = {
  num: 3,
  getNum: function () {
    return this.num
  },
}

console.log(obj.getNum()) // 3
console.log(obj.hasOwnProperty('num')) // true

This keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

UserFactory.prototype.register = function () {}
UserFactory.prototype.login = function () {}
UserFactory.prototype.logout = function () {}

let user = new UserFactory(
  "ahmedosama",
  "ahmedosama@sectheater.io",
  "1234567890"
)

This keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

...

What is `this`?

What isn't `this`?

Self referencing fn

Fn's lexical scope

This keyword

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

...

What is `this`?

Self referencing fn

Contextual binding

This keyword

function getName() {
  console.log(this.first_name)
}

var first_name = "ahmed"

getName() // ahmed

What is `this`?

Self referencing fn

Default binding

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName,
}

user.getName() // ahmed

What is `this`?

Self referencing fn

Implicit binding

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName,
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
  getName,
}

user.getName() // ahmed
anotherUser.getName() // mahmoud

What is `this`?

Self referencing fn

Implicit binding

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName,
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
  getName,
}

setTimeout(user.getName, 1000) // undefined
anotherUser.getName() // mahmoud

What is `this`?

Self referencing fn

Losing implicit context

This keyword

const getName = () => console.log(this.first_name)

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName,
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
  getName,
}

setTimeout(user.getName, 1000) // undefined
anotherUser.getName() // undefined

What is `this`?

Self referencing fn

Losing implicit context

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
}

getName.call(user) // ahmed
getName.apply(anotherUser) // mahmoud

What is `this`?

Self referencing fn

Explicit binding

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
}

const getUserName = getName.bind(user)
const getAnotherUserName = getName.bind(anotherUser)

getUserName() // ahmed
getAnotherUserName() // mahmoud

What is `this`?

Self referencing fn

Hard binding

This keyword

function getName() {
  console.log(this.first_name)
}

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
}

const getUserName = getName.bind(user)
const getAnotherUserName = getName.bind(anotherUser)

setTimeout(getUserName, 1000) // ahmed
getAnotherUserName() // mahmoud

What is `this`?

Self referencing fn

Hard binding

This keyword

function User(first_name, last_name) {
  this.first_name = first_name
  this.last_name = last_name
}

User.prototype.getName = function () {
  return `${this.first_name} ${this.last_name}`
}

let firstUser = new User('ahmed', 'osama')
let secondUser = new User('mahmoud', 'khaled')

console.log(firstUser.getName()) // ahmed osama
console.log(secondUser.getName()) // mahmoud khaled

What is `this`?

Self referencing fn

New binding

This keyword

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName: function getName() {
    console.log(`${this.first_name} ${this.last_name}`)
  },
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
}

new (user.getName.bind(anotherUser))() // getName instance
user.getName() // ahmed osama
user.getName.bind(anotherUser)() // mahmoud khaled
user.getName.call(anotherUser) // mahmoud khaled

Binding precedence

Self referencing fn

New binding

This keyword

let user = {
  first_name: 'ahmed',
  last_name: 'osama',
  getName: function getName() {
    console.log(`${this.first_name} ${this.last_name}`)
  },
}

let anotherUser = {
  first_name: 'mahmoud',
  last_name: 'khaled',
}

new (user.getName.bind(anotherUser))() // undefined undefined
user.getName() // ahmed osama
user.getName.bind(anotherUser)() // mahmoud khaled
user.getName.call(anotherUser) // mahmoud khaled

Self referencing fn

  1. New keyword?
  2. Explicit/Hard binding
  3. Implicit binding
  4. Default binding

Binding precedence

This keyword

let workshop = {
  instructor: 'ahmed osama',
  course: 'OOP, the hard way',
  students: ['mahmoud khaled', 'ismael mostafa', 'amira hany'],

  printStudents: function () {
    this.students.forEach(function (student) {
      console.log(
        `${student} is taking "${this.course}" with [${this.instructor}]`,
      )
    })
  },
}

workshop.printStudents() // mahmoud khaled is taking "undefined" with [undefined]

Self referencing fn

  1. New keyword?
  2. Explicit/Hard binding
  3. Implicit binding
  4. Default binding

Lexical this

This keyword

let workshop = {
  instructor: 'ahmed osama',
  course: 'OOP, the hard way',
  students: [
    'mahmoud khaled',
    'ismael mostafa',
    'amira hany',
  ],

  printStudents: function () {
    this.students.forEach(
      function (student) {
        console.log(
          `${student} is taking "${this.course}" with [${this.instructor}]`,
        )
      }.bind(this),
    )
  },
}

workshop.printStudents()

Self referencing fn

Lexical this

This keyword

let workshop = {
  instructor: 'ahmed osama',
  course: 'OOP, the hard way',
  students: [
    'mahmoud khaled',
    'ismael mostafa',
    'amira hany',
  ],

  printStudents: function () {
    this.students.forEach((student) =>
      console.log(
        `${student} takes "${this.course}" with [${this.instructor}]`,
      ),
    )
  },
}

workshop.printStudents()

Self referencing fn

Lexical this

Inheritance/Sub classing

UserFactory

PremiumUserFactory

DiamondUserFactory

Inheritance/Sub classing

function UserFactory(username, email, password) {
  let newUser = Object.create(userFunctions)

  newUser.email = email
  newUser.username = username
  newUser.password = password

  return newUser
}

const userFunctions = {
  login: function () {},
  logout: function () {},
  register: function () {},
}

Inheritance/Sub classing

function PremiumUserFactory(
  username,
  email,
  password,
  balance,
) {
  let newPremiumUser = Object.create(premiumUserFunctions)

  newPremiumUser.email = email
  newPremiumUser.username = username
  newPremiumUser.password = password
  newPremiumUser.balance = balance

  return newPremiumUser
}

const premiumUserFunctions = {
  accessCourses: function () {},
  addToBookmark: function () {},
}

Object.setPrototypeOf(premiumUserFunctions, userFunctions)

Inheritance/Sub classing

function PremiumUserFactory(
  username,
  email,
  password,
  balance,
) {
  let newPremiumUser = userFactory(
    username,
    email,
    password,
  )

  Object.setPrototypeOf(
    newPremiumUser,
    premiumUserFunctions,
  )

  newPremiumUser.balance = balance

  return newPremiumUser
}

const premiumUserFunctions = {
  accessCourses: function () {},
  addToBookmark: function () {},
}

Object.setPrototypeOf(premiumUserFunctions, userFunctions)

Inheritance/Sub classing

function UserFactory(username, email, password) {
  this.username = username
  this.email = email
  this.password = password
}

UserFactory.prototype.login = function () {}
UserFactory.prototype.logout = function () {}
UserFactory.prototype.register = function () {}

Inheritance/Sub classing

function PremiumUserFactory(
  username,
  email,
  password,
  balance,
) {
  UserFactory.call(this, username, email, password)
  // UserFactory.apply(this, [username, email, password])

  this.balance = balance
}

PremiumUserFactory.prototype = Object.create(UserFactory.prototype)

PremiumUserFactory.prototype.accessCourses = function () {}

Inheritance/Sub classing

class UserFactory {
  constructor(username, email, password) {
    this.username = username
    this.email = email
    this.password = password
  }

  register() {}
  login() {}
  logout() {}
}

Inheritance/Sub classing

class UserFactory {
  constructor(username, email, password) {
    this.username = username
    this.email = email
    this.password = password
  }

  register() {}
  login() {}
  logout() {}
}
class PremiumUserFactory extends UserFactory {
  constructor(username, email, password, balance) {
    super(username, email, password)

    this.balance = balance
  }

  accessCourses() {}
}

Wrapping Up

Wrapping Up

  • Objects in JS are not class instances
  • A prototype is where objects' functions are stored
  • A __proto__ is a reference to objects' functions
  • A function is both a callable and an object
  • The new keyword does four things
    • Creates an empty object `this`
    • Invoke the function with `this` as its context
    • Links function's prototype to this's __proto__
    • Returns `this` if another object isn't returned.
  • Contextual -this- binding
  • Inheritance using the prototypal model

What's next?

  • Deep dive into OOP pillars
  • Software design patterns & principles
    • SOLID principles
    • Design patterns
  • Data structures & Algorithms
  • Testing

What's next?

SecTheater

  • Deep dive into OOP pillars
  • Software design patterns & principles
    • SOLID principles
    • Design patterns
  • Data structures & Algorithms
  • Testing

SecTheater

Raise up your techinal skills

SecTheater

The advanced concepts in JavaScript's OOP system

By Security Theater

The advanced concepts in JavaScript's OOP system

The slides used in our course for exploring the deep concepts of using OOP in JavaScript.

  • 80