DEV Community

Cover image for Solved: What mistakes have you seen .NET/Java developers make when working with JS/TS?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: What mistakes have you seen .NET/Java developers make when working with JS/TS?

🚀 Executive Summary

TL;DR: The JavaScript ‘this’ keyword behaves dynamically, unlike the lexically scoped ‘this’ in C# or Java, causing confusion for backend developers when methods are used as callbacks. This article provides three solutions: explicit binding with .bind(this), using modern ES6 arrow functions for lexical ‘this’ inheritance, and the legacy ‘const self = this;’ workaround.

🎯 Key Takeaways

  • JavaScript’s ‘this’ keyword is dynamically scoped, determined by the function’s call-site, contrasting with C#/Java’s lexically scoped ‘this’ which always refers to the class instance.
  • ES6 arrow functions (=>) are the recommended modern solution for ‘this’ context issues, as they lexically inherit ‘this’ from their parent scope, behaving as C#/Java developers expect.
  • Explicitly binding ‘this’ using .bind(this) in the constructor is an older, verbose method to ensure correct context, often seen in legacy React class components.

Senior DevOps Engineer Darian Vance explores why the ‘this’ keyword in JavaScript is a major stumbling block for .NET/Java developers and offers three practical, battle-tested solutions to fix it.

From C# to ‘this’: A Backend Developer’s Survival Guide to JavaScript Scope

It was 2 AM. A critical deployment was blocked. The logs on prod-api-gateway-02 were clean, but the front-end was throwing a cryptic TypeError: Cannot read properties of undefined. A junior dev, a brilliant C# engineer new to TypeScript, was on the verge of a breakdown. “The method is right there!” he kept saying, pointing at a perfectly valid class method in his React component. “Why is it telling me ‘this’ is undefined when I click the button?” We’ve all been there. It’s the classic rite of passage for backend developers entering the wild west of JavaScript: the battle with the this keyword.

The “Why”: It’s Not a Bug, It’s a (Weird) Feature

In the structured, predictable worlds of C# and Java, the this keyword (or this in Java) is your loyal friend. It always refers to the current instance of the class. You can pass methods around, and this reliably points back home. It’s lexically scoped.

JavaScript decided to take a different path. In a traditional JavaScript function, the value of this is determined by how the function is called (the “call-site”). It’s dynamic. When you pass a class method as an event handler (like for an onClick), you lose the original context. The function is executed by the event listener, and this becomes something else—often the global window object or, in strict mode, undefined. This is the core mismatch that drives C# developers mad.

C# / Java this JavaScript this (in a standard function)
Bound to the class instance at compile time. Bound at runtime, based on the execution context.
Predictable and consistent. Changes depending on whether it’s a direct call, an event, etc.

So, how do we fix this without tearing our hair out? I’ve seen three main approaches in the wild.

Solution 1: The Quick Fix – Explicit Binding

This is the old-school, manual way. You can explicitly bind the context of this to your function in the class constructor. It works, it’s clear what you’re doing, but it feels a bit clunky and verbose in modern code.

Think of it as telling JavaScript, “Hey, no matter who calls this function later, I want this to always mean *this specific instance* of my class.”

class DataFetcher {
  constructor() {
    this.data = { user: "Darian" };
    // Force 'this.fetchData' to always have the correct 'this' context
    this.fetchData = this.fetchData.bind(this);
  }

  fetchData() {
    console.log(`Fetching data for: ${this.data.user}`);
    // Without .bind(this), 'this' would be undefined here if
    // this method was used as a callback.
  }

  render() {
    // someButton.addEventListener('click', this.fetchData); // Now this works!
  }
}
Enter fullscreen mode Exit fullscreen mode

Pro Tip: This is a pattern you’ll see a lot in older React class components. It’s a good thing to recognize, even if you don’t write it yourself anymore. It’s a clear signal that the developer was fighting a context problem.

Solution 2: The Permanent Fix – Arrow Functions

This is the modern, idiomatic solution and the one I push my teams to use. ES6 arrow functions (=>) are a game-changer because they do not have their own this context. Instead, they lexically inherit this from their parent scope. In a class, this means they automatically capture the class instance this, which is exactly the behavior a C# or Java developer expects.

You define your method as a class property assigned to an arrow function.

class DataFetcher {
  data = { user: "Darian" };

  // Use an arrow function to define the method
  fetchData = () => {
    // 'this' is automatically the instance of DataFetcher. No binding needed!
    console.log(`Fetching data for: ${this.data.user}`);
  }

  render() {
    // someButton.addEventListener('click', this.fetchData); // It just works.
  }
}
Enter fullscreen mode Exit fullscreen mode

This is cleaner, less error-prone, and the behavior is intuitive for anyone coming from an object-oriented background. 99% of the time, this is the right answer.

Solution 3: The ‘Nuclear’ Option – that = this

Sometimes you’re stuck in a gnarly old piece of JavaScript, maybe inside a chain of callbacks for a library that was written before 2015. You can’t use arrow functions, and binding feels wrong. The last-resort, “it-just-works” pattern is to save your context in a variable.

You’ll often see this as const self = this; or const that = this; at the top of a method. It feels like a hack, and it is, but it’s a very readable hack that explicitly solves the problem.

class LegacyApiHandler {
  constructor() {
    this.endpoint = "/api/v1/status";
  }

  checkStatus() {
    const self = this; // Save the context!

    // Imagine this is some old library that uses 'function' callbacks
    oldHttpClient.get('/check', function(response) {
      // If we used 'this' here, it would be the context of oldHttpClient.
      // But 'self' still correctly refers to our LegacyApiHandler instance.
      console.log(`Status for ${self.endpoint} is ${response.status}`);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Warning: Use this sparingly. If you find yourself writing const self = this; in new React/Vue/Angular code, you’re almost certainly doing something wrong. Go back to Solution 2. But when you’re maintaining a legacy system on dev-legacy-app-01, sometimes you have to do what you have to do to get the job done.

Ultimately, understanding JavaScript’s execution context is key. It’s a different mental model, but once it clicks, you’ll stop fighting the language and start leveraging its flexibility. And you’ll save yourself a few 2 AM debugging sessions.


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)