User Tools

Site Tools


java-script:function-context

Java Script Function Context

This

this keyword relates to the context on which it is run.
person.sayHello() ⇒ context here is person
person.spouse.sayHello(); ⇒ context is spouse
sayHello() ⇒ context is window for web browsers, globalThis for Node.js >=12 or global for Node.js < 12.

Given this example:

function-context-this.js
function sayHello() {
  console.log(this.name, this.age);
}
 
const person = {
  name: 'Alice',
  age: 30,
  sayHello: sayHello,
  spouse: {
    age: 32,
    sayHello: sayHello
  }
};
 
person.sayHello(); // Alice, 30
person.spouse.sayHello(); // undefined, 32
sayHello(); // undefined, undefined

When you call person.spouse.sayHello(), this.name evaluates to undefined since there's no name property on the spouse object, and this.age evaluates to 32.

In non-strict mode, this refers to the global object, and since there's no name or age property on the global object, both will be undefined.
So sayHello(); in non strict mode will display undefined, undefined. But in strict mode, it will throw an error.

In strict mode, this inside the sayHello function will be undefined when called without an object, leading to a TypeError when attempting to access properties on it:
"use strict";
 
function sayHello() {
  console.log(this.name, this.age);
}
 
// ... rest of the code ...
 
sayHello(); // TypeError: Cannot read properties of undefined (reading 'name')

New

The new operator in JavaScript is used to create a new instance of an object that's based on a constructor function. When a function is called with the new operator, the context of this inside the function is set to the newly created object.
Example:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
  };
}
 
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 32);
 
alice.sayHello(); // Hello, my name is Alice and I am 30 years old.
bob.sayHello();   // Hello, my name is Bob and I am 32 years old.

When new Person('Alice', 30) is called, a new object is created, and the context of this inside the Person function is set to that new object. The properties name, age, and sayHello are then added to the new object.
The result is two distinct objects alice and bob, each with their own properties and methods. When the sayHello method is called on one of these objects, the context of this inside the method is set to the object on which the method was called.

Using the new operator in this way allows you to create reusable constructor functions that can create objects with similar properties and methods, but with different values for those properties. It's a powerful feature that enables object-oriented programming patterns in JavaScript.

Call, Apply, Bind

The call, apply, and bind methods in JavaScript are used to control the value of this inside a function.
They allow you to call a function with a specific context for this, and also pass arguments to the function.
By using call, apply, or bind, you can ensure that the function behaves correctly regardless of how it's called.
We'll use this code to show how these functions are called:

function sayHello(greeting, punctuation) {
  console.log(greeting, this.name, punctuation, this.age);
}
 
const person = {
  name: 'Alice',
  age: 30
};
 
const spouse = {
  name: 'Bob',
  age: 32
};

Call

The call method allows you to pass individual arguments after specifying the value for this.

sayHello.call(person, 'Hello', '!'); // Hello Alice ! 30
sayHello.call(spouse, 'Hi', '...'); // Hi Bob ... 32

Apply

The apply method is similar to call, but it takes an array-like object for the arguments, instead of individual arguments.

sayHello.apply(person, ['Hello', '!']); // Hello Alice ! 30
sayHello.apply(spouse, ['Hi', '...']); // Hi Bob ... 32

Bind

The bind method allows you to create a new function with a bound context for this, and you can then call that function with any arguments you like.

const sayHelloToPerson = sayHello.bind(person);
const sayHelloToSpouse = sayHello.bind(spouse);
 
sayHelloToPerson('Hello', '!'); // Hello Alice ! 30
sayHelloToSpouse('Hi', '...'); // Hi Bob ... 32
These methods are particularly useful when working with functions that rely on a specific context for this, such as event handlers or methods that need to be reused with different objects.

Imagine you have two buttons on a web page, each representing different users, and you want to greet the user with their name when the button is clicked.
HTML:

<button id="buttonAlice">Greet Alice</button>
<button id="buttonBob">Greet Bob</button>

JAVA SCRIPT:

function greetUser(greeting) {
  alert(greeting + ' ' + this.name + '!');
}
 
const userAlice = {
  name: 'Alice'
};
 
const userBob = {
  name: 'Bob'
};
 
// Using bind to create functions with the correct context for this
const greetAlice = greetUser.bind(userAlice, 'Hello');
const greetBob = greetUser.bind(userBob, 'Hi');
 
// Adding event listeners
document.getElementById('buttonAlice').addEventListener('click', greetAlice);
document.getElementById('buttonBob').addEventListener('click', greetBob);

Here, we're using the bind method to create two new functions, greetAlice and greetBob, with the correct context for this and a specific greeting. When the corresponding button is clicked, the user is greeted with their name.
This example illustrates how the bind method can be used to create reusable functions with different contexts for this, allowing you to write more modular and maintainable code.
Similar principles apply when using call and apply to control the context of this in other situations.

Arrow Functions Context

Unlike normal (or named functions), the arrow functions does not have their own context, they're enclosed in the surrounding context.

In regular functions, the value of this is determined by how the function is called, while in arrow functions, this is lexically bound, meaning it retains the value of this from the surrounding code where the arrow function was defined.

Example:

ArrowFunctionContext.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Arrow Functions Context</title>
</head>
<body>
<div id="text1"> <br /> </div>
<div id="text2"></div>
<script>
    let person = {
        name: 'Preston',
        sayHello: () => {
            return `Hello - ${this.name}`;
        }
    };
 
    document.getElementById('text1').innerHTML = person.sayHello(); // Hello - 
 
    person = {
        name: 'Preston',
        sayHello: function() {
            return `Hello - ${this.name}`;
        }
    };
 
    document.getElementById('text2').innerHTML = person.sayHello(); // Hello - Preston
 
</script>
</body>
</html>
In the first part, we have defined sayHello as an arrow function. Arrow functions don't have their own this, so the this keyword inside the arrow function refers to the lexical context, which in the case of a script running in a browser would be the global window object. Since window.name is undefined, the first div (text1) will have the content “Hello - ”.
In the second example, we have defined sayHello as a regular function. Regular functions have their own this context, which refers to the object they are called on. So, when we call person.sayHello(), this inside the sayHello method refers to the person object, and this.name evaluates to 'Preston'. The second div (text2) will have the content “Hello - Preston”.

How to access the context of a parent object inside a nested (child) named function

Here we'll need to assign the context of the parent object to a variable that the nested function can access.
This is commonly done by assigning this to a variable, often named self or that, in the parent object's scope.

Example:

const parentObject = {
  parentValue: 'Hello from parent!',
  childFunction: function() {
    const self = this; // Storing parent's context
 
    function namedFunction() {
      // Accessing parent's context through 'self'
      console.log(self.parentValue);
    }
 
    namedFunction();
  }
};
 
parentObject.childFunction(); // Outputs: Hello from parent!

How to access the context of a parent object inside a nested (child) arrow function

Using an arrow function can simplify the code because arrow functions don't have their own this context; they inherit it from the enclosing function. This means you don't need to store the parent's context in a separate variable.
Same example with arrow function:

const parentObject = {
  parentValue: 'Hello from parent!',
  childFunction: function() {
    const arrowFunction = () => {
      // Accessing parent's context through 'this' directly
      console.log(this.parentValue);
    };
 
    arrowFunction();
  }
};
 
parentObject.childFunction(); // Outputs: Hello from parent!

By using an arrow function for the inner function, we automatically capture the value of this from the enclosing childFunction method. This allows the inner function to access the properties and methods of the parentObject without needing to assign this to a separate variable.
It's a cleaner and more concise way to write code that depends on maintaining the context of this across different levels of nested functions.

java-script/function-context.txt · Last modified: 2023/08/09 14:52 by odefta