Object-Oriented Programming in JavaScript

by Gautham Pai

Functions in Objects

In JavaScript, functions are first-class citizens, which means they can be assigned to variables, passed as arguments, and returned from other functions. This flexibility allows us to include functions within objects as properties, creating methods that belong to that object.

Let’s explore how functions can be used as properties in objects:

  1. Assigning a Named Function to an Object Key

    We can assign a function, like sayHello, to a key within an object.

    var obj = {};
    function sayHello() {
      console.log('Hello');
    }
    obj.sayHello = sayHello;
    console.log(typeof(obj.sayHello)); // "function"
    obj.sayHello(); // "Hello"
    
  2. Using Anonymous Functions

    Instead of defining a function separately, we can use an anonymous function and assign it directly to the object key.

    var obj = {};
    obj.sayHello = function() {
      console.log('Hello');
    };
    console.log(typeof(obj.sayHello)); // "function"
    obj.sayHello(); // "Hello"
    
  3. Defining Functions Inside Object Initialization

    We can also initialize an object with a function as one of its properties.

    var obj = {
      sayHello: function() {
        console.log('Hello');
      }
    };
    console.log(typeof(obj.sayHello)); // "function"
    obj.sayHello(); // "Hello"
    
  4. Using Simplified Syntax for Object Methods

    JavaScript provides a shorthand syntax for defining functions within an object.

    var obj = {
      sayHello() {
        console.log('Hello');
      }
    };
    console.log(typeof(obj.sayHello)); // "function"
    obj.sayHello(); // "Hello"
    
  5. Using Computed Property Names for Function Keys

    If the key name is stored in a variable, we can use bracket notation to assign it dynamically.

    var key = 'sayHello';
    var obj = {
      [key]() {
        console.log('Hello');
      }
    };
    console.log(typeof(obj.sayHello)); // "function"
    obj.sayHello(); // "Hello"
    
  6. Ensuring Understanding with Another Example

    Here’s a final example to reinforce the concept:

    var obj = { ['foo']() {} };
    obj.foo(); // Calls the function
    

This approach enables you to define methods dynamically and use functions as properties in JavaScript objects efficiently.

this Keyword - An Early Look

In JavaScript, functions within objects often need access to the object’s own properties. The reason we place functions inside objects is usually because the function is closely related to that object's data or behavior. To allow a function to access the object’s properties, we use the this keyword.

Understanding this

In JavaScript, this represents the object on which a function was called. Let’s explore this with some examples:

  1. Basic Example of this

    When a function is called on an object, this refers to that object. Here’s an example:

    function printFoo() {
      console.log(this);
    }
    printFoo(); // Logs the global object (or undefined in strict mode)
    
    var obj = {};
    obj.printFoo = printFoo;
    obj.printFoo(); // Logs the obj itself, as `this` refers to `obj`
    
  2. Accessing Object Properties Using this

    Using the same concept, we can create a function inside an object that accesses the object’s properties using this.

    var obj = {};
    obj.name = 'Raghav';
    obj.hello = function() {
      console.log('Hello ' + this.name);
    };
    obj.hello(); // "Hello Raghav"
    

In the second example, this inside hello refers to obj, allowing us to access obj.name. This flexibility enables us to create methods that dynamically respond based on the object's state.

Functions as Objects

In JavaScript, functions are more than just blocks of reusable code—they are also objects. This might seem unusual at first, but it opens up interesting possibilities. Let’s break down what this means and how it works.

What Does It Mean for a Function to Be an Object?

In programming, an object typically has both state and behavior (properties and methods). Since functions in JavaScript are also objects, they can have their own properties (state) and methods (behavior). In other words, we can add "fields" and "functions" to a function itself.

Let’s see an example:

  1. Adding Properties and Methods to a Function

    Here, we define a function sum and add a new method printFunctionName to it.

    function sum(a, b) {
      return a + b;
    }
    sum.printFunctionName = function() {
      console.log('sum');
    };
    
    console.log(sum(4, 6)); // 10
    sum.printFunctionName(); // "sum"
    

In this example, sum behaves like an object with its own method printFunctionName. While this might seem unusual initially, getting comfortable with the idea of functions as objects can reveal interesting patterns and uses in JavaScript. We'll explore these applications further as we delve deeper into JavaScript.

call() and apply() in JavaScript

In JavaScript, functions are objects, so we can use methods like call() and apply() to control how they execute, especially to specify the value of this within the function. Let’s explore how call() and apply() work with examples.

Understanding call() and apply()

Both call() and apply() methods allow us to invoke a function with a specified this value, enabling us to control the context in which the function is executed. The difference between them lies in how they handle function arguments:

  • call() accepts arguments individually.
  • apply() accepts arguments as an array.

Let’s go through examples to illustrate this.

  1. Basic Function Call

    Here’s a simple function that logs its context (this) and its arguments:

    function sum(a, b) {
        console.log(this);
        console.log(arguments);
        console.log(a + b);
        return a + b;
    }
    
    sum(3, 4); // Uses default `this` (global context or undefined in strict mode)
    
  2. Using call()

    With call(), we can specify the this context explicitly and pass individual arguments.

    sum.call(this, 3, 4); // `this` refers to the global or current context
    sum.call({}, 3, 4);   // `this` is an empty object
    
  3. Using apply()

    With apply(), we specify this and pass arguments as an array.

    sum.apply(null, [3, 4]); // `this` refers to `null` (or global in non-strict mode)
    

These methods provide flexibility in JavaScript by allowing control over the this context and handling of arguments, which can be particularly useful in scenarios where context needs to be switched dynamically.

prototype - A Built-in Object Inside Every Function

In JavaScript, every function has a built-in object associated with it called prototype. This object allows us to add properties and methods that can be shared across instances created by that function, making it a powerful tool in JavaScript’s inheritance model.

Understanding prototype

When a function is created, JavaScript automatically assigns it a prototype object. Initially, this object is empty, but it can be customized by adding properties and methods.

Let’s explore prototype with some examples:

  1. Checking the Default Prototype

    By default, the prototype object is empty. Here’s how we can observe it:

    function sum(a, b) {
        return a + b;
    }
    
    console.log(sum(3, 4)); // 7
    console.log(sum.prototype); // Initially an empty object
    
  2. Adding Properties to the Prototype

    We can add properties to the prototype object. These properties can then be accessed by all instances created with the function, as prototype is shared among them.

    function sum(a, b) {
        return a + b;
    }
    
    sum.prototype.x = 10; // Adding a property to the prototype
    console.log(sum.prototype); // { x: 10 }
    

Constructor Functions

In JavaScript, we can create objects using special functions called constructor functions. Constructor functions look like regular functions but are called with the new keyword, which initiates the process of creating a new object with a link to the function’s prototype.

How Constructor Functions Work

  1. Defining a Constructor Function

    Here’s how to create a constructor function and add properties to its prototype.

    function Constructor() {
    }
    Constructor.prototype.place = 'Bangalore';
    Constructor.prototype.hello = function() {
      console.log('Hello');
    };
    
    console.log(Constructor.prototype); // Logs the prototype with place and hello properties
    
  2. Creating an Object with new

    When you call the constructor function with the new keyword, a new object is created with a “secret” link to Constructor.prototype.

    var obj1 = new Constructor();
    

    In this example, obj1 is a new object linked to Constructor.prototype.

  3. Prototype Linking and Property Lookup

    With the new keyword, any object created through the constructor function will look up its properties in the constructor’s prototype if they aren’t found directly on the object itself.

    function Constructor() {
    }
    Constructor.prototype.place = 'Bangalore';
    Constructor.prototype.hello = function() {
      console.log('Hello');
    };
    
    var obj1 = new Constructor();
    console.log(obj1.place); // "Bangalore"
    obj1.hello(); // "Hello"
    
  4. Creating Multiple Objects

    You can create multiple instances from the same constructor function, each sharing the properties and methods defined in the prototype.

    var obj1 = new Constructor();
    console.log(obj1.place); // "Bangalore"
    obj1.hello(); // "Hello"
    
    var obj2 = new Constructor();
    console.log(obj2.place); // "Bangalore"
    obj2.hello(); // "Hello"
    

this in Constructor Functions

In JavaScript, constructor functions are used to create objects, and when you call a function with the new keyword, a new object is created, with this inside the function referring to that new object. This allows you to define properties and methods on the new object using this.

How this Works in Constructor Functions

  1. Setting Properties on this

    When a constructor function is called with new, this refers to the newly created object. We can use this to assign properties directly to the new object, making each instance created with the constructor function unique.

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    
    var person1 = new Person('Alice', 30);
    console.log(person1.name); // "Alice"
    console.log(person1.age);  // 30
    

    Here, this.name and this.age are properties that get set on the new Person instance.

  2. Adding Methods Using this

    You can also add methods directly on this, making each instance have its own copy of the method (though this is less common than adding methods to the prototype).

    function Person(name) {
      this.name = name;
      this.sayHello = function() {
        console.log('Hello, my name is ' + this.name);
      };
    }
    
    var person1 = new Person('Alice');
    person1.sayHello(); // "Hello, my name is Alice"
    
  3. Creating Multiple Instances with Different this Contexts

    Every time you call a constructor function with new, a new object is created, and this in the function refers to that new instance. This allows each instance to have its own state.

    var person1 = new Person('Alice', 30);
    var person2 = new Person('Bob', 25);
    
    console.log(person1.name); // "Alice"
    console.log(person2.name); // "Bob"
    
  4. Using this in Combination with Prototypes

    While properties are often set directly on this inside the constructor, methods are typically added to the function’s prototype, allowing all instances to share the same method.

    function Person(name) {
      this.name = name;
    }
    Person.prototype.sayHello = function() {
      console.log('Hello, my name is ' + this.name);
    };
    
    var person1 = new Person('Alice');
    var person2 = new Person('Bob');
    
    person1.sayHello(); // "Hello, my name is Alice"
    person2.sayHello(); // "Hello, my name is Bob"
    

Test Your Knowledge

No quiz available

Tags